diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..8dd399ab5 --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 88 +extend-ignore = E203 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 59369f515..fcedb2e0d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -124,7 +124,10 @@ jobs: python-version: '3.7' - name: Install dependencies - run: pip install flake8 + run: pip install flake8 black==21.8b0 - name: Run flake8 run: flake8 ramp-* + + - name: Run black + run: black --check . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e490fa43c..3341d2c8c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,6 +5,10 @@ repos: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace +- repo: https://github.com/psf/black + rev: 21.8b0 + hooks: + - id: black - repo: https://gitlab.com/pycqa/flake8 rev: 3.7.8 hooks: diff --git a/conftest.py b/conftest.py index b6c947bd7..6b3ce023b 100644 --- a/conftest.py +++ b/conftest.py @@ -1,61 +1,64 @@ -import contextlib -import os import pytest -import smtpd import warnings from sqlalchemy import create_engine, exc -from threading import Thread from ramp_utils.testing import database_config_template from yaml import safe_load from ramp_utils import read_config -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def database_connection(): """ Create a Postgres database for the tests, and drop it when the tests are done. """ config = safe_load(open("db_engine.yml")) - dbowner = config.get('db_owner') + dbowner = config.get("db_owner") - engine = create_engine(f'postgresql://{dbowner}:@localhost/postgres', - isolation_level='AUTOCOMMIT') + engine = create_engine( + f"postgresql://{dbowner}:@localhost/postgres", + isolation_level="AUTOCOMMIT", + ) connection = engine.connect() database_config = read_config(database_config_template()) - username = database_config['sqlalchemy']['username'] - database_name = database_config['sqlalchemy']['database'] + username = database_config["sqlalchemy"]["username"] + database_name = database_config["sqlalchemy"]["database"] try: - connection.execute(f"""CREATE USER {username} + connection.execute( + f"""CREATE USER {username} WITH PASSWORD '{username}'; - ALTER USER {username} WITH SUPERUSER""") + ALTER USER {username} WITH SUPERUSER""" + ) except exc.ProgrammingError: - warnings.warn(f'user {username} already exists') + warnings.warn(f"user {username} already exists") try: - connection.execute(f'CREATE DATABASE {database_name} OWNER {username}') + connection.execute(f"CREATE DATABASE {database_name} OWNER {username}") except exc.ProgrammingError as e: raise ValueError( - f'{database_name} database used for testing already exists' + f"{database_name} database used for testing already exists" ) from e # close the connection and remove the database in the end yield - connection.execute("""SELECT pg_terminate_backend(pid) + connection.execute( + """SELECT pg_terminate_backend(pid) FROM pg_stat_activity - WHERE datname = 'databoard_test';""") - connection.execute(f'DROP DATABASE {database_name}') - connection.execute(f'DROP USER {username}') + WHERE datname = 'databoard_test';""" + ) + connection.execute(f"DROP DATABASE {database_name}") + connection.execute(f"DROP USER {username}") print(f"deleted database 'databoard_test' and removed user '{username}'") -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def dask_scheduler(): try: from dask.distributed import LocalCluster + cluster = LocalCluster(n_workers=4) yield cluster.scheduler_address cluster.close() diff --git a/doc/conf.py b/doc/conf.py index ebaee89cf..9c85d1568 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -12,6 +12,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. +# flake8: noqa + import os import sys import sphinx_rtd_theme @@ -19,7 +21,7 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath('sphinxext')) +sys.path.insert(0, os.path.abspath("sphinxext")) from github_link import make_linkcode_resolve import generate_database_schema @@ -32,13 +34,13 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.doctest', - 'sphinx.ext.intersphinx', - 'sphinx_click.ext', - 'numpydoc', - 'sphinx_issues', + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx_click.ext", + "numpydoc", + "sphinx_issues", ] # this is needed for some reason... @@ -48,21 +50,22 @@ # pngmath / imgmath compatibility layer for different sphinx versions import sphinx from distutils.version import LooseVersion -if LooseVersion(sphinx.__version__) < LooseVersion('1.4'): - extensions.append('sphinx.ext.pngmath') + +if LooseVersion(sphinx.__version__) < LooseVersion("1.4"): + extensions.append("sphinx.ext.pngmath") else: - extensions.append('sphinx.ext.imgmath') + extensions.append("sphinx.ext.imgmath") -autodoc_default_flags = ['members', 'inherited-members'] +autodoc_default_flags = ["members", "inherited-members"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # generate autosummary even if no references autosummary_generate = True # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. # source_encoding = 'utf-8-sig' @@ -71,11 +74,11 @@ plot_gallery = True # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'RAMP' -copyright = '2015 - 2019, Paris-Saclay Center for Data Science' +project = "RAMP" +copyright = "2015 - 2019, Paris-Saclay Center for Data Science" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -83,6 +86,7 @@ # # The short X.Y version. from ramp_database import __version__ + version = __version__ # The full version, including alpha/beta/rc tags. release = __version__ @@ -99,11 +103,11 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build', '_templates'] +exclude_patterns = ["_build", "_templates"] # The reST default role (used for this markup: `text`) to use for all # documents. -default_role = 'literal' +default_role = "literal" # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = False @@ -117,10 +121,10 @@ # show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # Custom style -html_style = 'css/ramp.css' +html_style = "css/ramp.css" # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -132,7 +136,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -161,7 +165,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -210,17 +214,15 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'ramp-learndoc' +htmlhelp_basename = "ramp-learndoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # 'preamble': '', } @@ -229,8 +231,13 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - ('index', 'ramp.tex', 'RAMP Documentation', - 'Paris-Saclay Center for Data Science', 'manual'), + ( + "index", + "ramp.tex", + "RAMP Documentation", + "Paris-Saclay Center for Data Science", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of @@ -252,12 +259,14 @@ # intersphinx configuration intersphinx_mapping = { - 'python': ('https://docs.python.org/{.major}'.format( - sys.version_info), None), - 'numpy': ('https://docs.scipy.org/doc/numpy/', None), - 'scipy': ('https://docs.scipy.org/doc/scipy/reference', None), - 'matplotlib': ('https://matplotlib.org/', None), - 'sklearn': ('http://scikit-learn.org/stable', None) + "python": ( + "https://docs.python.org/{.major}".format(sys.version_info), + None, + ), + "numpy": ("https://docs.scipy.org/doc/numpy/", None), + "scipy": ("https://docs.scipy.org/doc/scipy/reference", None), + "matplotlib": ("https://matplotlib.org/", None), + "sklearn": ("http://scikit-learn.org/stable", None), } # -- Options for manual page output --------------------------------------- @@ -268,8 +277,15 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [('index', 'RAMP', 'RAMP Documentation', - ['Paris-Saclay Center for Data Science'], 1)] +man_pages = [ + ( + "index", + "RAMP", + "RAMP Documentation", + ["Paris-Saclay Center for Data Science"], + 1, + ) +] # If true, show URL addresses after external links. # man_show_urls = False @@ -280,9 +296,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'RAMP', 'RAMP Documentation', - 'Paris-Saclay Center for Data Science', 'RAMP', - 'Rapid Analytics and Model Prototyping', 'Machine Learning'), + ( + "index", + "RAMP", + "RAMP Documentation", + "Paris-Saclay Center for Data Science", + "RAMP", + "Rapid Analytics and Model Prototyping", + "Machine Learning", + ), ] @@ -298,9 +320,9 @@ # Config for sphinx_issues -issues_uri = 'https://github.com/paris-saclay-cds/ramp-board/issues/{issue}' -issues_github_path = 'paris-saclay-cds/ramp-board' -issues_user_uri = 'https://github.com/{user}' +issues_uri = "https://github.com/paris-saclay-cds/ramp-board/issues/{issue}" +issues_github_path = "paris-saclay-cds/ramp-board" +issues_user_uri = "https://github.com/{user}" # Temporary work-around for spacing problem between parameter and parameter @@ -310,7 +332,7 @@ # In an ideal world, this would get fixed in this PR: # https://github.com/readthedocs/sphinx_rtd_theme/pull/747/files def setup(app): - app.add_javascript('js/copybutton.js') + app.add_javascript("js/copybutton.js") app.add_stylesheet("basic.css") # app.connect('autodoc-process-docstring', generate_example_rst) diff --git a/doc/sphinxext/generate_database_schema.py b/doc/sphinxext/generate_database_schema.py index ed1d46e28..977bc3ca8 100644 --- a/doc/sphinxext/generate_database_schema.py +++ b/doc/sphinxext/generate_database_schema.py @@ -10,20 +10,22 @@ def main(): database_config = read_config( - database_config_template(), filter_section='sqlalchemy' + database_config_template(), filter_section="sqlalchemy" ) setup_db(database_config) render_er( - "{}://{}:{}@{}:{}/{}" - .format( - database_config['drivername'], database_config['username'], - database_config['password'], database_config['host'], - database_config['port'], database_config['database'] + "{}://{}:{}@{}:{}/{}".format( + database_config["drivername"], + database_config["username"], + database_config["password"], + database_config["host"], + database_config["port"], + database_config["database"], ), - os.path.join('..', '_static', 'img', 'schema_db.png') + os.path.join("..", "_static", "img", "schema_db.png"), ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/doc/sphinxext/github_link.py b/doc/sphinxext/github_link.py index 38d048687..44d3bcd97 100644 --- a/doc/sphinxext/github_link.py +++ b/doc/sphinxext/github_link.py @@ -5,16 +5,16 @@ import sys from functools import partial -REVISION_CMD = 'git rev-parse --short HEAD' +REVISION_CMD = "git rev-parse --short HEAD" def _get_git_revision(): try: revision = subprocess.check_output(REVISION_CMD.split()).strip() except (subprocess.CalledProcessError, OSError): - print('Failed to execute git to get revision') + print("Failed to execute git to get revision") return None - return revision.decode('utf-8') + return revision.decode("utf-8") def _linkcode_resolve(domain, info, package, url_fmt, revision): @@ -34,17 +34,17 @@ def _linkcode_resolve(domain, info, package, url_fmt, revision): if revision is None: return - if domain not in ('py', 'pyx'): + if domain not in ("py", "pyx"): return - if not info.get('module') or not info.get('fullname'): + if not info.get("module") or not info.get("fullname"): return - class_name = info['fullname'].split('.')[0] + class_name = info["fullname"].split(".")[0] if type(class_name) != str: # Python 2 only - class_name = class_name.encode('utf-8') - module = __import__(info['module'], fromlist=[class_name]) - obj = attrgetter(info['fullname'])(module) + class_name = class_name.encode("utf-8") + module = __import__(info["module"], fromlist=[class_name]) + obj = attrgetter(info["fullname"])(module) try: fn = inspect.getsourcefile(obj) @@ -58,14 +58,12 @@ def _linkcode_resolve(domain, info, package, url_fmt, revision): if not fn: return - fn = os.path.relpath(fn, - start=os.path.dirname(__import__(package).__file__)) + fn = os.path.relpath(fn, start=os.path.dirname(__import__(package).__file__)) try: lineno = inspect.getsourcelines(obj)[1] except Exception: - lineno = '' - return url_fmt.format(revision=revision, package=package, - path=fn, lineno=lineno) + lineno = "" + return url_fmt.format(revision=revision, package=package, path=fn, lineno=lineno) def make_linkcode_resolve(package, url_fmt): @@ -80,5 +78,6 @@ def make_linkcode_resolve(package, url_fmt): '{path}#L{lineno}') """ revision = _get_git_revision() - return partial(_linkcode_resolve, revision=revision, package=package, - url_fmt=url_fmt) + return partial( + _linkcode_resolve, revision=revision, package=package, url_fmt=url_fmt + ) diff --git a/doc/sphinxext/sphinx_issues.py b/doc/sphinxext/sphinx_issues.py index ba14de62d..9ad5941c2 100644 --- a/doc/sphinxext/sphinx_issues.py +++ b/doc/sphinxext/sphinx_issues.py @@ -80,7 +80,11 @@ class IssueRole(object): EXTERNAL_REPO_REGEX = re.compile(r"^(\w+)/(.+)([#@])([\w]+)$") def __init__( - self, uri_config_option, format_kwarg, github_uri_template, format_text=None + self, + uri_config_option, + format_kwarg, + github_uri_template, + format_text=None, ): self.uri_config_option = uri_config_option self.format_kwarg = format_kwarg @@ -103,7 +107,9 @@ def make_node(self, name, issue_no, config, options=None): ) path = name_map.get(name) ref = "https://github.com/{issues_github_path}/{path}/{n}".format( - issues_github_path="{}/{}".format(username, repo), path=path, n=issue + issues_github_path="{}/{}".format(username, repo), + path=path, + n=issue, ) formatted_issue = self.format_text(issue).lstrip("#") text = "{username}/{repo}{symbol}{formatted_issue}".format(**locals()) diff --git a/mypy.ini b/mypy.ini index 94651a546..ae30aa7ea 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,3 +1,3 @@ [mypy] ignore_missing_imports = true -exclude = .*(tests/data|tests/kit|setup.py|doc/).* +exclude = .*(tests/data|tests/kit|setup.py|doc|iris_kit).* diff --git a/ramp-database/ramp_database/__init__.py b/ramp-database/ramp_database/__init__.py index af9c8f0b5..9515001bf 100644 --- a/ramp-database/ramp_database/__init__.py +++ b/ramp-database/ramp_database/__init__.py @@ -1,5 +1,3 @@ from ._version import __version__ # noqa -all = [ - '__version__' -] +all = ["__version__"] diff --git a/ramp-database/ramp_database/_version.py b/ramp-database/ramp_database/_version.py index d03bd515a..a1002a148 100644 --- a/ramp-database/ramp_database/_version.py +++ b/ramp-database/ramp_database/_version.py @@ -21,4 +21,4 @@ # 'X.Y.dev0' is the canonical version of 'X.Y.dev' # -__version__ = '0.9.0.dev0' +__version__ = "0.9.0.dev0" diff --git a/ramp-database/ramp_database/cli.py b/ramp-database/ramp_database/cli.py index c87ea41eb..e5fc93bf1 100644 --- a/ramp-database/ramp_database/cli.py +++ b/ramp-database/ramp_database/cli.py @@ -17,7 +17,7 @@ from .tools import team as team_module from .tools import user as user_module -CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) +CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @click.group(context_settings=CONTEXT_SETTINGS) @@ -27,234 +27,332 @@ def main(): @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--event", - help='The name of the event') -@click.option('--path', help='path to the .csv file where to save ' - 'the submissions and their score') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--event", help="The name of the event") +@click.option( + "--path", + help="path to the .csv file where to save " "the submissions and their score", +) def export_leaderboards(config, event, path): """Export all the scored submissions.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: data = leaderboard_module.get_leaderboard_all_info(session, event) if not len(data): - click.echo('No score was found on the submission {}'.format( - event)) + click.echo("No score was found on the submission {}".format(event)) else: data.to_csv(path) - click.echo('Data saved to {}'.format(path)) + click.echo("Data saved to {}".format(path)) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option('--login', help='Login') -@click.option('--password', help='Password') -@click.option('--lastname', help="User's last name") -@click.option('--firstname', help="User's first name") -@click.option('--email', help="User's email") -@click.option('--access-level', default='user', show_default=True, - help="User's administration rights") -@click.option('--hidden-notes', default='', show_default=True, - help="User's additional hidden notes") -def add_user(config, login, password, lastname, firstname, email, - access_level, hidden_notes): +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--login", help="Login") +@click.option("--password", help="Password") +@click.option("--lastname", help="User's last name") +@click.option("--firstname", help="User's first name") +@click.option("--email", help="User's email") +@click.option( + "--access-level", + default="user", + show_default=True, + help="User's administration rights", +) +@click.option( + "--hidden-notes", + default="", + show_default=True, + help="User's additional hidden notes", +) +def add_user( + config, + login, + password, + lastname, + firstname, + email, + access_level, + hidden_notes, +): """Add a new user in the database.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: - user_module.add_user(session, login, password, lastname, firstname, - email, access_level, hidden_notes) + with session_scope(config["sqlalchemy"]) as session: + user_module.add_user( + session, + login, + password, + lastname, + firstname, + email, + access_level, + hidden_notes, + ) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option('--login', help="User's login to be removed") +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--login", help="User's login to be removed") def delete_user(config, login): """Delete a user which asked to sign-up to RAMP studio.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: user_module.delete_user(session, login) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option('--login', help="User's login to be approved") +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--login", help="User's login to be approved") def approve_user(config, login): """Approve a user which asked to sign-up to RAMP studio.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: user_module.approve_user(session, login) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option('--login', help="User's login to be made admin") +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--login", help="User's login to be made admin") def make_user_admin(config, login): """Make a user a RAMP admin.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: user_module.make_user_admin(session, login) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option('--login', help="User's login to be made admin") -@click.option('--access-level', help="The access level to grant the user." - "One of {'asked', 'user', 'admin'}", default='user', - show_default=True) +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--login", help="User's login to be made admin") +@click.option( + "--access-level", + help="The access level to grant the user." "One of {'asked', 'user', 'admin'}", + default="user", + show_default=True, +) def set_user_access_level(config, login, access_level): """Change the access level of a RAMP user.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: user_module.set_user_access_level(session, login, access_level) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option('--event', help='Name of the event') -@click.option('--team', help='Name of the team') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--event", help="Name of the event") +@click.option("--team", help="Name of the team") def sign_up_team(config, event, team): """Sign-up a user (or team) for a RAMP event.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: team_module.sign_up_team(session, event, team) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option('--event', help='Name of the event') -@click.option('--team', help='Name of the team') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--event", help="Name of the event") +@click.option("--team", help="Name of the team") def delete_event_team(config, event, team): """Delete a link between a user (or team) and a RAMP event.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: team_module.delete_event_team(session, event, team) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option('--problem', help='Name of the problem') -@click.option('--kit-dir', help='Path to the RAMP kit') -@click.option('--data-dir', help='Path to the RAMP data') -@click.option('--force', default=False, show_default=True, - help='Whether or not to overwrite the problem if it exists') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--problem", help="Name of the problem") +@click.option("--kit-dir", help="Path to the RAMP kit") +@click.option("--data-dir", help="Path to the RAMP data") +@click.option( + "--force", + default=False, + show_default=True, + help="Whether or not to overwrite the problem if it exists", +) def add_problem(config, problem, kit_dir, data_dir, force): """Add a RAMP problem in the database.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: event_module.add_problem(session, problem, kit_dir, data_dir, force) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--problem", help='Name of the problem') -@click.option("--event", help='Name of the event') -@click.option("--title", help='Title of the event') -@click.option("--sandbox", default='starting-kit', help='Name of the sandbox') -@click.option('--submissions-dir', - help='Path to the deployment RAMP submissions path.') -@click.option('--is-public', default=False, show_default=True, - help='Whether or not the event should be public') -@click.option('--force', default=False, show_default=True, - help='Whether or not to overwrite the problem if it exists') -def add_event(config, problem, event, title, sandbox, submissions_dir, - is_public, force): +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--problem", help="Name of the problem") +@click.option("--event", help="Name of the event") +@click.option("--title", help="Title of the event") +@click.option("--sandbox", default="starting-kit", help="Name of the sandbox") +@click.option("--submissions-dir", help="Path to the deployment RAMP submissions path.") +@click.option( + "--is-public", + default=False, + show_default=True, + help="Whether or not the event should be public", +) +@click.option( + "--force", + default=False, + show_default=True, + help="Whether or not to overwrite the problem if it exists", +) +def add_event( + config, problem, event, title, sandbox, submissions_dir, is_public, force +): """Add an event in the database.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: - event_module.add_event(session, problem, event, title, sandbox, - submissions_dir, is_public, force) + with session_scope(config["sqlalchemy"]) as session: + event_module.add_event( + session, + problem, + event, + title, + sandbox, + submissions_dir, + is_public, + force, + ) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--event", help='Name of the event') -@click.option("--user", help='Name of the user becoming event admin') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--event", help="Name of the event") +@click.option("--user", help="Name of the user becoming event admin") def add_event_admin(config, event, user): """Make a user admin of a specific RAMP event.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: event_module.add_event_admin(session, event, user) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--event", help='Name of the event') -@click.option("--team", help='Name of the team') -@click.option("--submission", help='Name of the submission') -@click.option("--path", help='Path to the submission') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--event", help="Name of the event") +@click.option("--team", help="Name of the team") +@click.option("--submission", help="Name of the submission") +@click.option("--path", help="Path to the submission") def add_submission(config, event, team, submission, path): """Add a submission to the database.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: - submission_module.add_submission(session, event, team, submission, - path) + with session_scope(config["sqlalchemy"]) as session: + submission_module.add_submission(session, event, team, submission, path) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--config-event", required=True, - help='Path to configuration file YAML format ' - 'containing the database information, eg config.yml') -@click.option('--dry-run', is_flag=True, - help='Emulate the removal without taking action. Basically, ' - 'only the printing information will be shown. The deletion will ' - 'not be done.') -@click.option('--from-disk', is_flag=True, - help='Flag to remove the event folder from the disk as well.') -@click.option('--force', is_flag=True, - help='Flag to force a removal, even from the disk, when an ' - 'event is not in the database.') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option( + "--config-event", + required=True, + help="Path to configuration file YAML format " + "containing the database information, eg config.yml", +) +@click.option( + "--dry-run", + is_flag=True, + help="Emulate the removal without taking action. Basically, " + "only the printing information will be shown. The deletion will " + "not be done.", +) +@click.option( + "--from-disk", + is_flag=True, + help="Flag to remove the event folder from the disk as well.", +) +@click.option( + "--force", + is_flag=True, + help="Flag to force a removal, even from the disk, when an " + "event is not in the database.", +) def delete_event(config, config_event, dry_run, from_disk, force): """Delete event.""" internal_config = read_config(config) ramp_config = generate_ramp_config(config_event, config) event_name = ramp_config["event_name"] - with session_scope(internal_config['sqlalchemy']) as session: + with session_scope(internal_config["sqlalchemy"]) as session: db_event = event_module.get_event(session, event_name) if db_event: if not dry_run: event_module.delete_event(session, event_name) - click.echo( - '{} was removed from the database' - .format(event_name) - ) + click.echo("{} was removed from the database".format(event_name)) if from_disk: if not db_event and not force: - err_msg = ('{} event not found in the database. If you want ' - 'to force removing event files from the disk, add ' - 'the option "--force".' - .format(event_name)) + err_msg = ( + "{} event not found in the database. If you want " + "to force removing event files from the disk, add " + 'the option "--force".'.format(event_name) + ) raise click.ClickException(err_msg) - for key in ("ramp_submissions_dir", "ramp_predictions_dir", - "ramp_logs_dir"): + for key in ( + "ramp_submissions_dir", + "ramp_predictions_dir", + "ramp_logs_dir", + ): dir_to_remove = ramp_config[key] if os.path.exists(dir_to_remove): if not dry_run: @@ -272,29 +370,39 @@ def delete_event(config, config_event, dry_run, from_disk, force): @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--config-event", required=True, - help='Path to configuration file YAML format ' - 'containing the database information, eg config.yml') -@click.option('--force', is_flag=True, - help='Flag to force a removal of the predictions from the disk, ' - 'when an event is not in the database.') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option( + "--config-event", + required=True, + help="Path to configuration file YAML format " + "containing the database information, eg config.yml", +) +@click.option( + "--force", + is_flag=True, + help="Flag to force a removal of the predictions from the disk, " + "when an event is not in the database.", +) def delete_predictions(config, config_event, force): """Delete event predictions from the disk.""" internal_config = read_config(config) ramp_config = generate_ramp_config(config_event, config) event_name = ramp_config["event_name"] - with session_scope(internal_config['sqlalchemy']) as session: + with session_scope(internal_config["sqlalchemy"]) as session: db_event = event_module.get_event(session, event_name) if not db_event and not force: - err_msg = ('{} event not found in the database. If you want ' - 'to force removing perdiction files from the disk use ' - 'the option "--force".' - .format(event_name)) + err_msg = ( + "{} event not found in the database. If you want " + "to force removing perdiction files from the disk use " + 'the option "--force".'.format(event_name) + ) raise click.ClickException(err_msg) dir_to_remove = ramp_config["ramp_predictions_dir"] @@ -302,109 +410,128 @@ def delete_predictions(config, config_event, force): shutil.rmtree(dir_to_remove) click.echo("Removed directory:\n{}".format(dir_to_remove)) else: - err_msg = ("Directory not found:\n{}".format(dir_to_remove)) + err_msg = "Directory not found:\n{}".format(dir_to_remove) raise click.ClickException(err_msg) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--event", help='Name of the event') -@click.option("--state", help='The state of the submissions to display') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--event", help="Name of the event") +@click.option("--state", help="The state of the submissions to display") def get_submissions_by_state(config, event, state): """Display the list of submission for an event in a particular state.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: submissions = submission_module.get_submissions(session, event, state) if submissions: data = defaultdict(list) for sub_info in submissions: - sub = submission_module.get_submission_by_id( - session, sub_info[0] - ) - data['ID'].append(sub.id) - data['name'].append(sub.name) - data['team'].append(sub.team) - data['path'].append(sub.path) - data['state'].append(sub.state) - click.echo(pd.DataFrame(data).set_index('ID')) + sub = submission_module.get_submission_by_id(session, sub_info[0]) + data["ID"].append(sub.id) + data["name"].append(sub.name) + data["team"].append(sub.team) + data["path"].append(sub.path) + data["state"].append(sub.state) + click.echo(pd.DataFrame(data).set_index("ID")) else: - click.echo('No submission for this event and this state') + click.echo("No submission for this event and this state") @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--submission-id", help='The submission ID') -@click.option("--state", help='The state to affect to the submission') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--submission-id", help="The submission ID") +@click.option("--state", help="The state to affect to the submission") def set_submission_state(config, submission_id, state): """Set the state of a submission.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: submission_module.set_submission_state(session, submission_id, state) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--event", help='The event name') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--event", help="The event name") def update_leaderboards(config, event): """Update the leaderboards for a given event.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: leaderboard_module.update_leaderboards(session, event) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--event", help='The event name') -@click.option("--user", help='The user name') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--event", help="The event name") +@click.option("--user", help="The user name") def update_user_leaderboards(config, event, user): """Update the user leaderboards for a given event.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: leaderboard_module.update_user_leaderboards(session, event, user) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--event", help='The event name') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--event", help="The event name") def update_all_users_leaderboards(config, event): """Update the leaderboards of all users for a given event.""" config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: leaderboard_module.update_all_user_leaderboards(session, event) @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file YAML format containing the database ' - 'information') -@click.option("--config-event", required=True, - help='The event config file name.') -@click.option("--min-improvement", default='0.0', - help='The minimum score improvement ' - 'to continue building the ensemble') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file YAML format containing the database " "information", +) +@click.option("--config-event", required=True, help="The event config file name.") +@click.option( + "--min-improvement", + default="0.0", + help="The minimum score improvement " "to continue building the ensemble", +) def compute_contributivity(config, config_event, min_improvement): """Blend submissions, compute combined score and contributivities.""" config_event = generate_ramp_config(config_event, config) - event_name = config_event['event_name'] + event_name = config_event["event_name"] config = read_config(config) - with session_scope(config['sqlalchemy']) as session: + with session_scope(config["sqlalchemy"]) as session: contributivity_module.compute_contributivity( - session, event_name, config_event['ramp_kit_dir'], - config_event['ramp_data_dir'], - config_event['ramp_predictions_dir'], float(min_improvement)) - contributivity_module.compute_historical_contributivity( - session, event_name + session, + event_name, + config_event["ramp_kit_dir"], + config_event["ramp_data_dir"], + config_event["ramp_predictions_dir"], + float(min_improvement), ) + contributivity_module.compute_historical_contributivity(session, event_name) leaderboard_module.update_leaderboards(session, event_name) leaderboard_module.update_all_user_leaderboards(session, event_name) @@ -413,5 +540,5 @@ def start(): main() -if __name__ == '__main__': +if __name__ == "__main__": start() diff --git a/ramp-database/ramp_database/exceptions.py b/ramp-database/ramp_database/exceptions.py index c2922fb09..513586dd8 100644 --- a/ramp-database/ramp_database/exceptions.py +++ b/ramp-database/ramp_database/exceptions.py @@ -4,47 +4,54 @@ """ __all__ = [ - 'DuplicateSubmissionError', - 'MergeTeamError', - 'MissingExtensionError', - 'MissingSubmissionFileError', - 'NameClashError', - 'TooEarlySubmissionError', - 'UnknownStateError' - ] + "DuplicateSubmissionError", + "MergeTeamError", + "MissingExtensionError", + "MissingSubmissionFileError", + "NameClashError", + "TooEarlySubmissionError", + "UnknownStateError", +] class DuplicateSubmissionError(Exception): """Error to raise when a submission is already present in the database.""" + pass class MergeTeamError(Exception): """Error to raise when the merging of teams is failing.""" + pass class MissingSubmissionFileError(Exception): """Error to raise when the file submitted is not present in the supposed location.""" + pass class MissingExtensionError(Exception): """Error to raise when the extension is not registered in the database.""" + pass class NameClashError(Exception): """Error to raise when there is a duplicate in submission name.""" + pass class TooEarlySubmissionError(Exception): """Error to raise when a submission was submitted to early.""" + pass class UnknownStateError(Exception): """Error to raise when the state of the submission is unknown.""" + pass diff --git a/ramp-database/ramp_database/model/base.py b/ramp-database/ramp_database/model/base.py index 410d6acca..7c225851a 100644 --- a/ramp-database/ramp_database/model/base.py +++ b/ramp-database/ramp_database/model/base.py @@ -4,8 +4,8 @@ from sqlalchemy.ext.declarative import declarative_base __all__ = [ - 'Model', - 'set_query_property', + "Model", + "set_query_property", ] @@ -44,14 +44,16 @@ def __init__(self, query, page, per_page, total, items): def prev(self, error_out=False): """Returns a `Pagination` object for the previous page.""" - assert self.query is not None, \ - 'a query object is required for this method to work' + assert ( + self.query is not None + ), "a query object is required for this method to work" return self.query.paginate(self.page - 1, self.per_page, error_out) def next(self, error_out=False): """Returns a `Pagination` object for the next page.""" - assert self.query is not None, \ - 'a query object is required for this method to work' + assert ( + self.query is not None + ), "a query object is required for this method to work" return self.query.paginate(self.page + 1, self.per_page, error_out) @@ -62,6 +64,7 @@ class BaseQuery(orm.Query): standard SQLAlchemy sqlalchemy.orm.query.Query class and has all the methods of a standard query as well. """ + def paginate(self, page, per_page=20, error_out=True): """Return `Pagination` instance using already defined query parameters. @@ -91,6 +94,7 @@ class QueryProperty: """Query property accessor which gives a model access to query capabilities via `ModelBase.query` which is equivalent to ``session.query(Model)``. """ + def __init__(self, session): self.session = session @@ -98,7 +102,7 @@ def __get__(self, model, Model): mapper = orm.class_mapper(Model) if mapper: - if not getattr(Model, 'query_class', None): + if not getattr(Model, "query_class", None): Model.query_class = BaseQuery query_property = Model.query_class(mapper, session=self.session) @@ -108,6 +112,7 @@ def __get__(self, model, Model): class ModelBase: """Baseclass for custom user models.""" + #: the query class used. The `query` attribute is an instance #: of this class. By default a `BaseQuery` is used. query_class = BaseQuery diff --git a/ramp-database/ramp_database/model/datatype.py b/ramp-database/ramp_database/model/datatype.py index 5b939d0cd..b6c11c021 100644 --- a/ramp-database/ramp_database/model/datatype.py +++ b/ramp-database/ramp_database/model/datatype.py @@ -6,11 +6,12 @@ from sqlalchemy import LargeBinary from sqlalchemy import TypeDecorator -__all__ = ['NumpyType'] +__all__ = ["NumpyType"] class NumpyType(TypeDecorator): """Storing zipped numpy arrays.""" + impl = LargeBinary def process_bind_param(self, value, dialect): diff --git a/ramp-database/ramp_database/model/event.py b/ramp-database/ramp_database/model/event.py index 98b037fd5..659e6547a 100644 --- a/ramp-database/ramp_database/model/event.py +++ b/ramp-database/ramp_database/model/event.py @@ -17,10 +17,10 @@ from .score import ScoreType __all__ = [ - 'Event', - 'EventTeam', - 'EventAdmin', - 'EventScoreType', + "Event", + "EventTeam", + "EventAdmin", + "EventScoreType", ] @@ -125,16 +125,17 @@ class Event(Model): cv_folds : list of :class:`ramp_database.model.CVFold` A back-reference to the CV folds for the event. """ - __tablename__ = 'events' + + __tablename__ = "events" id = Column(Integer, primary_key=True) name = Column(String, nullable=False, unique=True) title = Column(String, nullable=False) - problem_id = Column(Integer, ForeignKey('problems.id'), nullable=False) - problem = relationship('Problem', - backref=backref('events', - cascade='all, delete-orphan')) + problem_id = Column(Integer, ForeignKey("problems.id"), nullable=False) + problem = relationship( + "Problem", backref=backref("events", cascade="all, delete-orphan") + ) max_members_per_team = Column(Integer, default=1) # max number of submissions in Caruana's ensemble @@ -148,13 +149,12 @@ class Event(Model): is_competitive = Column(Boolean, default=False) min_duration_between_submissions = Column(Integer, default=15 * 60) - opening_timestamp = Column( - DateTime, default=datetime.datetime(2000, 1, 1, 0, 0, 0)) + opening_timestamp = Column(DateTime, default=datetime.datetime(2000, 1, 1, 0, 0, 0)) # before links to submissions in leaderboard are not alive public_opening_timestamp = Column( - DateTime, default=datetime.datetime(2100, 1, 1, 0, 0, 0)) - closing_timestamp = Column( - DateTime, default=datetime.datetime(2100, 1, 1, 0, 0, 0)) + DateTime, default=datetime.datetime(2100, 1, 1, 0, 0, 0) + ) + closing_timestamp = Column(DateTime, default=datetime.datetime(2100, 1, 1, 0, 0, 0)) # the name of the score in self.event_score_types which is used for # ensembling and contributivity. @@ -177,25 +177,33 @@ class Event(Model): private_competition_leaderboard_html = Column(String, default=None) # big change in the database - ramp_sandbox_name = Column(String, nullable=False, unique=False, - default='starting-kit') + ramp_sandbox_name = Column( + String, nullable=False, unique=False, default="starting-kit" + ) path_ramp_submissions = Column(String, nullable=False, unique=False) - def __init__(self, problem_name, name, event_title, - ramp_sandbox_name, path_ramp_submissions, session=None): + def __init__( + self, + problem_name, + name, + event_title, + ramp_sandbox_name, + path_ramp_submissions, + session=None, + ): self.name = name self.ramp_sandbox_name = ramp_sandbox_name self.path_ramp_submissions = path_ramp_submissions if session is None: self.problem = Problem.query.filter_by(name=problem_name).one() else: - self.problem = (session.query(Problem) - .filter(Problem.name == problem_name) - .one()) + self.problem = ( + session.query(Problem).filter(Problem.name == problem_name).one() + ) self.title = event_title def __repr__(self): - return 'Event({})'.format(self.name) + return "Event({})".format(self.name) def set_n_submissions(self): """Set the number of submissions for the current event by checking @@ -221,10 +229,9 @@ def workflow(self): def official_score_type(self): """:class:`ramp_database.model.EventScoreType`: The score type for the current event.""" - return (EventScoreType.query - .filter_by(event=self, - name=self.official_score_name) - .one()) + return EventScoreType.query.filter_by( + event=self, name=self.official_score_name + ).one() def get_official_score_type(self, session): """Get the type of the default score used for the current event. @@ -238,10 +245,12 @@ def get_official_score_type(self, session): event_type_score : :class:`ramp_database.model.EventTypeScore` The default type score for the current event. """ - return (session.query(EventScoreType) - .filter(EventScoreType.event == self) - .filter(EventScoreType.name == self.official_score_name) - .one()) + return ( + session.query(EventScoreType) + .filter(EventScoreType.event == self) + .filter(EventScoreType.name == self.official_score_name) + .one() + ) @property def official_score_function(self): @@ -251,30 +260,58 @@ def official_score_function(self): @property def combined_combined_valid_score_str(self): """str: Convert to string the combined public score for all folds.""" - return (None if self.combined_combined_valid_score is None - else str(round(self.combined_combined_valid_score, - self.official_score_type.precision))) + return ( + None + if self.combined_combined_valid_score is None + else str( + round( + self.combined_combined_valid_score, + self.official_score_type.precision, + ) + ) + ) @property def combined_combined_test_score_str(self): """str: Convert to string the combined private score for all folds.""" - return (None if self.combined_combined_test_score is None - else str(round(self.combined_combined_test_score, - self.official_score_type.precision))) + return ( + None + if self.combined_combined_test_score is None + else str( + round( + self.combined_combined_test_score, + self.official_score_type.precision, + ) + ) + ) @property def combined_foldwise_valid_score_str(self): """str: Convert to string the combined public score for each fold.""" - return (None if self.combined_foldwise_valid_score is None - else str(round(self.combined_foldwise_valid_score, - self.official_score_type.precision))) + return ( + None + if self.combined_foldwise_valid_score is None + else str( + round( + self.combined_foldwise_valid_score, + self.official_score_type.precision, + ) + ) + ) @property def combined_foldwise_test_score_str(self): """str: Convert to string the combined public score for each fold.""" - return (None if self.combined_foldwise_test_score is None - else str(round(self.combined_foldwise_test_score, - self.official_score_type.precision))) + return ( + None + if self.combined_foldwise_test_score is None + else str( + round( + self.combined_foldwise_test_score, + self.official_score_type.precision, + ) + ) + ) @property def is_open(self): @@ -297,7 +334,7 @@ def is_closed(self): @property def n_jobs(self): """int: The number of cv fold which can be used as number of jobs.""" - return sum(1 for cv_fold in self.cv_folds if cv_fold.type == 'live') + return sum(1 for cv_fold in self.cv_folds if cv_fold.type == "live") @property def n_participants(self): @@ -339,27 +376,27 @@ class EventScoreType(Model): submissions : list of :class:`ramp_database.model.SubmissionScore` A back-reference of the submissions for the event/score type. """ - __tablename__ = 'event_score_types' + + __tablename__ = "event_score_types" id = Column(Integer, primary_key=True) # Can be renamed, default is the same as score_type.name name = Column(String, nullable=False) - event_id = Column(Integer, ForeignKey('events.id'), nullable=False) - event = relationship('Event', - backref=backref('score_types', - cascade='all, delete-orphan')) + event_id = Column(Integer, ForeignKey("events.id"), nullable=False) + event = relationship( + "Event", backref=backref("score_types", cascade="all, delete-orphan") + ) - score_type_id = Column(Integer, ForeignKey('score_types.id'), - nullable=False) - score_type = relationship('ScoreType', backref=backref('events')) + score_type_id = Column(Integer, ForeignKey("score_types.id"), nullable=False) + score_type = relationship("ScoreType", backref=backref("events")) # display precision in n_digits # default is the same as score_type.precision precision = Column(Integer) - UniqueConstraint(event_id, score_type_id, name='es_constraint') - UniqueConstraint(event_id, name, name='en_constraint') + UniqueConstraint(event_id, score_type_id, name="es_constraint") + UniqueConstraint(event_id, name, name="en_constraint") def __init__(self, event, score_type_object): self.event = event @@ -371,7 +408,7 @@ def __init__(self, event, score_type_object): self.precision = score_type_object.precision def __repr__(self): - return '{}: {}'.format(self.name, self.event) + return "{}: {}".format(self.name, self.event) @property def score_type_object(self): @@ -433,19 +470,20 @@ class EventAdmin(Model): admin : :class:`ramp_database.model.User` The user instance. """ - __tablename__ = 'event_admins' + + __tablename__ = "event_admins" id = Column(Integer, primary_key=True) - event_id = Column(Integer, ForeignKey('events.id'), nullable=False) - event = relationship('Event', - backref=backref('event_admins', - cascade='all, delete-orphan')) + event_id = Column(Integer, ForeignKey("events.id"), nullable=False) + event = relationship( + "Event", backref=backref("event_admins", cascade="all, delete-orphan") + ) - admin_id = Column(Integer, ForeignKey('users.id'), nullable=False) - admin = relationship('User', - backref=backref('admined_events', - cascade='all, delete-orphan')) + admin_id = Column(Integer, ForeignKey("users.id"), nullable=False) + admin = relationship( + "User", backref=backref("admined_events", cascade="all, delete-orphan") + ) class EventTeam(Model): @@ -489,19 +527,20 @@ class EventTeam(Model): submissions : list of :class:`ramp_database.model.Submission` A back-reference to the submissions associated with this event/team. """ - __tablename__ = 'event_teams' + + __tablename__ = "event_teams" id = Column(Integer, primary_key=True) - event_id = Column(Integer, ForeignKey('events.id'), nullable=False) - event = relationship('Event', - backref=backref('event_teams', - cascade='all, delete-orphan')) + event_id = Column(Integer, ForeignKey("events.id"), nullable=False) + event = relationship( + "Event", backref=backref("event_teams", cascade="all, delete-orphan") + ) - team_id = Column(Integer, ForeignKey('teams.id'), nullable=False) - team = relationship('Team', - backref=backref('team_events', - cascade='all, delete-orphan')) + team_id = Column(Integer, ForeignKey("teams.id"), nullable=False) + team = relationship( + "Team", backref=backref("team_events", cascade="all, delete-orphan") + ) is_active = Column(Boolean, default=True) last_submission_name = Column(String, default=None) @@ -512,7 +551,7 @@ class EventTeam(Model): failed_leaderboard_html = Column(String, default=None) new_leaderboard_html = Column(String, default=None) - UniqueConstraint(event_id, team_id, name='et_constraint') + UniqueConstraint(event_id, team_id, name="et_constraint") def __init__(self, event, team): self.event = event @@ -520,4 +559,4 @@ def __init__(self, event, team): self.signup_timestamp = datetime.datetime.utcnow() def __repr__(self): - return '{}/{}'.format(self.event, self.team) + return "{}/{}".format(self.event, self.team) diff --git a/ramp-database/ramp_database/model/fold.py b/ramp-database/ramp_database/model/fold.py index ec107716b..312eaf329 100644 --- a/ramp-database/ramp_database/model/fold.py +++ b/ramp-database/ramp_database/model/fold.py @@ -9,11 +9,11 @@ from .datatype import NumpyType __all__ = [ - 'CVFold', + "CVFold", ] -cv_fold_types = Enum('live', 'test', name='cv_fold_types') +cv_fold_types = Enum("live", "test", name="cv_fold_types") class CVFold(Model): @@ -41,29 +41,28 @@ class CVFold(Model): A back-reference to the submission linked with this fold. """ - __tablename__ = 'cv_folds' + __tablename__ = "cv_folds" id = Column(Integer, primary_key=True) - type = Column(cv_fold_types, default='live') + type = Column(cv_fold_types, default="live") train_is = Column(NumpyType, nullable=False) test_is = Column(NumpyType, nullable=False) - event_id = Column(Integer, ForeignKey('events.id'), nullable=False) - event = relationship('Event', - backref=backref('cv_folds', - cascade='all, delete-orphan')) + event_id = Column(Integer, ForeignKey("events.id"), nullable=False) + event = relationship( + "Event", backref=backref("cv_folds", cascade="all, delete-orphan") + ) @staticmethod def _pretty_printing(array): """Make pretty printing of an array by skipping portion when it is too large.""" if array.size > 10: - return 'fold {} ... {}'.format(str(array[:5])[:-1], - str(array[-5:])[1:]) - return 'fold {}'.format(array) + return "fold {} ... {}".format(str(array[:5])[:-1], str(array[-5:])[1:]) + return "fold {}".format(array) def __repr__(self): train_repr = self._pretty_printing(self.train_is) test_repr = self._pretty_printing(self.test_is) - return 'train ' + train_repr + '\n' + ' test ' + test_repr + return "train " + train_repr + "\n" + " test " + test_repr diff --git a/ramp-database/ramp_database/model/problem.py b/ramp-database/ramp_database/model/problem.py index 8755f1569..70c357558 100644 --- a/ramp-database/ramp_database/model/problem.py +++ b/ramp-database/ramp_database/model/problem.py @@ -16,10 +16,10 @@ __all__ = [ - 'Problem', - 'HistoricalContributivity', - 'Keyword', - 'ProblemKeyword', + "Problem", + "HistoricalContributivity", + "Keyword", + "ProblemKeyword", ] @@ -60,13 +60,14 @@ class Problem(Model): keywords : list of :class:`ramp_database.model.ProblemKeyword` A back-reference to the keywords associated with the problem. """ - __tablename__ = 'problems' + + __tablename__ = "problems" id = Column(Integer, primary_key=True) name = Column(String, nullable=False, unique=True) - workflow_id = Column(Integer, ForeignKey('workflows.id'), nullable=False) - workflow = relationship('Workflow', backref=backref('problems')) + workflow_id = Column(Integer, ForeignKey("workflows.id"), nullable=False) + workflow = relationship("Workflow", backref=backref("problems")) # XXX: big change in the database path_ramp_kit = Column(String, nullable=False, unique=False) @@ -79,28 +80,28 @@ def __init__(self, name, path_ramp_kit, path_ramp_data, session=None): self.reset(session) def __repr__(self): - return 'Problem({})\n{}'.format(self.name, self.workflow) + return "Problem({})\n{}".format(self.name, self.workflow) def reset(self, session): """Reset the workflow.""" if session is not None: - self.workflow = \ - (session.query(Workflow) - .filter(Workflow.name == # noqa - type(self.module.workflow).__name__) # noqa - .one()) + self.workflow = ( + session.query(Workflow) + .filter( + Workflow.name == type(self.module.workflow).__name__ # noqa + ) # noqa + .one() + ) else: - self.workflow = \ - (Workflow.query - .filter_by(name=type(self.module.workflow).__name__) - .one()) + self.workflow = Workflow.query.filter_by( + name=type(self.module.workflow).__name__ + ).one() @property def module(self): """module: Get the problem module.""" return import_module_from_source( - os.path.join(self.path_ramp_kit, 'problem.py'), - 'problem' + os.path.join(self.path_ramp_kit, "problem.py"), "problem" ) @property @@ -162,14 +163,16 @@ class HistoricalContributivity(Model): historical_contributivity : float The historical contributivity. """ - __tablename__ = 'historical_contributivity' + + __tablename__ = "historical_contributivity" id = Column(Integer, primary_key=True, autoincrement=True) timestamp = Column(DateTime, nullable=False) - submission_id = Column(Integer, ForeignKey('submissions.id')) - submission = relationship('Submission', - backref=backref('historical_contributivitys', - cascade='all, delete-orphan')) + submission_id = Column(Integer, ForeignKey("submissions.id")) + submission = relationship( + "Submission", + backref=backref("historical_contributivitys", cascade="all, delete-orphan"), + ) contributivity = Column(Float, default=0.0) historical_contributivity = Column(Float, default=0.0) @@ -193,7 +196,8 @@ class Keyword(Model): problems : list of :class:`ramp_database.model.ProblemKeyword` A back-reference to the problems associated with the keyword. """ - __tablename__ = 'keywords' + + __tablename__ = "keywords" id = Column(Integer, primary_key=True, autoincrement=True) name = Column(String(), nullable=False, unique=True) @@ -224,17 +228,18 @@ class ProblemKeyword(Model): keyword : :class:`ramp_database.model.Keyword` The keyword instance. """ - __tablename__ = 'problem_keywords' + + __tablename__ = "problem_keywords" id = Column(Integer, primary_key=True) description = Column(String) - problem_id = Column(Integer, ForeignKey('problems.id'), nullable=False) - problem = relationship('Problem', - backref=backref('keywords', - cascade='all, delete-orphan')) + problem_id = Column(Integer, ForeignKey("problems.id"), nullable=False) + problem = relationship( + "Problem", backref=backref("keywords", cascade="all, delete-orphan") + ) - keyword_id = Column(Integer, ForeignKey('keywords.id'), nullable=False) - keyword = relationship('Keyword', - backref=backref('problems', - cascade='all, delete-orphan')) + keyword_id = Column(Integer, ForeignKey("keywords.id"), nullable=False) + keyword = relationship( + "Keyword", backref=backref("problems", cascade="all, delete-orphan") + ) diff --git a/ramp-database/ramp_database/model/score.py b/ramp-database/ramp_database/model/score.py index 36233966e..002e0b25d 100644 --- a/ramp-database/ramp_database/model/score.py +++ b/ramp-database/ramp_database/model/score.py @@ -6,7 +6,7 @@ from .base import Model -__all__ = ['ScoreType'] +__all__ = ["ScoreType"] # XXX: Should probably be addressed at some point? @@ -39,7 +39,8 @@ class ScoreType(Model): events : list of :class:`ramp_database.model.EventScoreType` A back-reference to the event using the score. """ - __tablename__ = 'score_types' + + __tablename__ = "score_types" id = Column(Integer, primary_key=True) name = Column(String, nullable=False, unique=True) @@ -54,4 +55,4 @@ def __init__(self, name, is_lower_the_better, minimum, maximum): self.maximum = maximum def __repr__(self): - return 'ScoreType(name={})'.format(self.name) + return "ScoreType(name={})".format(self.name) diff --git a/ramp-database/ramp_database/model/submission.py b/ramp-database/ramp_database/model/submission.py index bd676eb3e..8e916cc27 100644 --- a/ramp-database/ramp_database/model/submission.py +++ b/ramp-database/ramp_database/model/submission.py @@ -22,38 +22,39 @@ from .datatype import NumpyType __all__ = [ - 'Submission', - 'SubmissionScore', - 'SubmissionFile', - 'SubmissionFileType', - 'SubmissionFileTypeExtension', - 'Extension', - 'SubmissionScoreOnCVFold', - 'SubmissionOnCVFold', - 'SubmissionSimilarity', + "Submission", + "SubmissionScore", + "SubmissionFile", + "SubmissionFileType", + "SubmissionFileTypeExtension", + "Extension", + "SubmissionScoreOnCVFold", + "SubmissionOnCVFold", + "SubmissionSimilarity", ] # evaluate right after train/test, so no need for 'scored' states submission_states = Enum( - 'new', # submitted by user to frontend server - 'checked', # not used, checking is part of the workflow now - 'checking_error', # not used, checking is part of the workflow now - 'trained', # training finished normally on the backend server - 'training_error', # training finished abnormally on the backend server - 'validated', # validation finished normally on the backend server - 'validating_error', # validation finished abnormally on the backend server - 'tested', # testing finished normally on the backend server - 'testing_error', # testing finished abnormally on the backend server - 'training', # training is running normally on the backend server - 'sent_to_training', # frontend server sent submission to backend server - 'scored', # submission scored on the frontend server.Final state - name='submission_states') - -submission_types = Enum('live', 'test', name='submission_types') + "new", # submitted by user to frontend server + "checked", # not used, checking is part of the workflow now + "checking_error", # not used, checking is part of the workflow now + "trained", # training finished normally on the backend server + "training_error", # training finished abnormally on the backend server + "validated", # validation finished normally on the backend server + "validating_error", # validation finished abnormally on the backend server + "tested", # testing finished normally on the backend server + "testing_error", # testing finished abnormally on the backend server + "training", # training is running normally on the backend server + "sent_to_training", # frontend server sent submission to backend server + "scored", # submission scored on the frontend server.Final state + name="submission_states", +) + +submission_types = Enum("live", "test", name="submission_types") def _encode_string(text): - return bytes(text, 'utf-8') if isinstance(text, str) else text + return bytes(text, "utf-8") if isinstance(text, str) else text class Submission(Model): @@ -134,15 +135,16 @@ class Submission(Model): on_cv_folds : list of :class:`ramp_database.model.SubmissionOnCVFold` A back-reference of the CV fold for this submission. """ - __tablename__ = 'submissions' + + __tablename__ = "submissions" id = Column(Integer, primary_key=True) - event_team_id = Column(Integer, ForeignKey('event_teams.id'), - nullable=False) - event_team = relationship('EventTeam', - backref=backref('submissions', - cascade='all, delete-orphan')) + event_team_id = Column(Integer, ForeignKey("event_teams.id"), nullable=False) + event_team = relationship( + "EventTeam", + backref=backref("submissions", cascade="all, delete-orphan"), + ) name = Column(String(20), nullable=False) hash_ = Column(String, nullable=False, index=True, unique=True) @@ -153,10 +155,10 @@ class Submission(Model): contributivity = Column(Float, default=0.0) historical_contributivity = Column(Float, default=0.0) - type = Column(submission_types, default='live') - state = Column(String, default='new') + type = Column(submission_types, default="live") + state = Column(String, default="new") # TODO: hide absolute path in error - error_msg = Column(String, default='') + error_msg = Column(String, default="") # user can delete but we keep is_valid = Column(Boolean, default=True) # We can forget bad models. @@ -166,7 +168,7 @@ class Submission(Model): # with which they want to participate in the competition is_in_competition = Column(Boolean, default=True) - notes = Column(String, default='') # eg, why is it disqualified + notes = Column(String, default="") # eg, why is it disqualified train_time_cv_mean = Column(Float, default=0.0) valid_time_cv_mean = Column(Float, default=0.0) @@ -177,7 +179,7 @@ class Submission(Model): # the maximum memory size used when training/testing, in MB max_ram = Column(Float, default=0.0) # later also ramp_id - UniqueConstraint(event_team_id, name, name='ts_constraint') + UniqueConstraint(event_team_id, name, name="ts_constraint") def __init__(self, name, event_team, session=None): self.name = name @@ -189,31 +191,38 @@ def __init__(self, name, event_team, session=None): sha_hasher.update(_encode_string(self.name)) # We considered using the id, but then it will be given away in the # url which is maybe not a good idea. - self.hash_ = '{}'.format(sha_hasher.hexdigest()) + self.hash_ = "{}".format(sha_hasher.hexdigest()) self.submission_timestamp = datetime.datetime.utcnow() if session is None: - event_score_types = \ - (EventScoreType.query.filter_by(event=event_team.event)) + event_score_types = EventScoreType.query.filter_by(event=event_team.event) else: - event_score_types = (session.query(EventScoreType) - .filter(EventScoreType.event == - event_team.event) - .all()) + event_score_types = ( + session.query(EventScoreType) + .filter(EventScoreType.event == event_team.event) + .all() + ) for event_score_type in event_score_types: submission_score = SubmissionScore( - submission=self, event_score_type=event_score_type) + submission=self, event_score_type=event_score_type + ) self.session.add(submission_score) self.reset() def __str__(self): - return 'Submission({}/{}/{})'.format( - self.event.name, self.team.name, self.name) + return "Submission({}/{}/{})".format(self.event.name, self.team.name, self.name) def __repr__(self): - return ('Submission(event_name={}, team_name={}, name={}, files={}, ' - 'state={}, train_time={})' - .format(self.event.name, self.team.name, self.name, - self.files, self.state, self.train_time_cv_mean)) + return ( + "Submission(event_name={}, team_name={}, name={}, files={}, " + "state={}, train_time={})".format( + self.event.name, + self.team.name, + self.name, + self.files, + self.state, + self.train_time_cv_mean, + ) + ) @hybrid_property def team(self): @@ -263,25 +272,25 @@ def is_not_sandbox(self): @hybrid_property def is_error(self): """bool: Whether the training of the submission failed.""" - return 'error' in self.state + return "error" in self.state @hybrid_property def is_new(self): """bool: Whether the submission is a new submission.""" - return (self.state in ['new', 'training', 'sent_to_training'] and - self.is_not_sandbox) + return ( + self.state in ["new", "training", "sent_to_training"] + and self.is_not_sandbox + ) @hybrid_property def is_public_leaderboard(self): """bool: Whether the submission is part of the public leaderboard.""" - return (self.is_not_sandbox and self.is_valid and - (self.state == 'scored')) + return self.is_not_sandbox and self.is_valid and (self.state == "scored") @hybrid_property def is_private_leaderboard(self): """bool: Whether the submission is part of the private leaderboard.""" - return (self.is_not_sandbox and self.is_valid and - (self.state == 'scored')) + return self.is_not_sandbox and self.is_valid and (self.state == "scored") @property def path(self): @@ -291,12 +300,12 @@ def path(self): @property def basename(self): """str: The base name of the submission.""" - return 'submission_' + '{:09d}'.format(self.id) + return "submission_" + "{:09d}".format(self.id) @property def module(self): """str: Path of the submission as a module.""" - return self.path.lstrip('./').replace('/', '.') + return self.path.lstrip("./").replace("/", ".") @property def f_names(self): @@ -316,8 +325,9 @@ def full_name_with_link(self): The hyperlink forward to the first submission file while the text corresponds to the event, team, and submission name. """ - return '{}/{}/{}'.format( - self.link, self.event.name, self.team.name, self.name[:20]) + return "{}/{}/{}".format( + self.link, self.event.name, self.team.name, self.name[:20] + ) @property def name_with_link(self): @@ -327,13 +337,14 @@ def name_with_link(self): The hyperlink forward to the first submission file while the text corresponds to submission name. """ - return '{}'.format(self.link, self.name[:20]) + return "{}".format(self.link, self.name[:20]) @property def state_with_link(self): """str: HTML hyperlink to the state file to report error.""" - return '{}'.format( - os.path.join(self.hash_, 'error.txt'), self.state) + return "{}".format( + os.path.join(self.hash_, "error.txt"), self.state + ) def ordered_scores(self, score_names): """Generator yielding :class:`ramp_database.model.SubmissionScore`. @@ -405,9 +416,9 @@ def set_state(self, state, session=None): if session is None: all_cv_folds = self.on_cv_folds else: - all_cv_folds = (session.query(SubmissionOnCVFold) - .filter_by(submission_id=self.id) - .all()) + all_cv_folds = ( + session.query(SubmissionOnCVFold).filter_by(submission_id=self.id).all() + ) all_cv_folds = sorted(all_cv_folds, key=lambda x: x.id) for submission_on_cv_fold in all_cv_folds: submission_on_cv_fold.state = state @@ -419,8 +430,8 @@ def reset(self): values. """ self.contributivity = 0.0 - self.state = 'new' - self.error_msg = '' + self.state = "new" + self.error_msg = "" for score in self.scores: score.valid_score_cv_bag = score.event_score_type.worst score.test_score_cv_bag = score.event_score_type.worst @@ -447,9 +458,9 @@ def set_error(self, error, error_msg, session=None): if session is None: all_cv_folds = self.on_cv_folds else: - all_cv_folds = (session.query(SubmissionOnCVFold) - .filter_by(submission_id=self.id) - .all()) + all_cv_folds = ( + session.query(SubmissionOnCVFold).filter_by(submission_id=self.id).all() + ) all_cv_folds = sorted(all_cv_folds, key=lambda x: x.id) for submission_on_cv_fold in all_cv_folds: submission_on_cv_fold.set_error(error, error_msg) @@ -469,14 +480,17 @@ def set_contributivity(self, session=None): if session is None: all_cv_folds = self.on_cv_folds else: - all_cv_folds = (session.query(SubmissionOnCVFold) - .filter_by(submission_id=self.id) - .all()) + all_cv_folds = ( + session.query(SubmissionOnCVFold) + .filter_by(submission_id=self.id) + .all() + ) all_cv_folds = sorted(all_cv_folds, key=lambda x: x.id) - unit_contributivity = 1. / len(all_cv_folds) + unit_contributivity = 1.0 / len(all_cv_folds) for submission_on_cv_fold in all_cv_folds: - self.contributivity += (unit_contributivity * - submission_on_cv_fold.contributivity) + self.contributivity += ( + unit_contributivity * submission_on_cv_fold.contributivity + ) def set_state_after_training(self, session=None): """Set the state of a submission depending of the state of the fold @@ -486,33 +500,31 @@ def set_state_after_training(self, session=None): if session is None: all_cv_folds = self.on_cv_folds else: - all_cv_folds = (session.query(SubmissionOnCVFold) - .filter_by(submission_id=self.id) - .all()) + all_cv_folds = ( + session.query(SubmissionOnCVFold).filter_by(submission_id=self.id).all() + ) all_cv_folds = sorted(all_cv_folds, key=lambda x: x.id) - states = [submission_on_cv_fold.state - for submission_on_cv_fold in all_cv_folds] - if all(state == 'tested' for state in states): - self.state = 'tested' - elif all(state in ['tested', 'validated'] for state in states): - self.state = 'validated' - elif all(state in ['tested', 'validated', 'trained'] - for state in states): - self.state = 'trained' - elif any(state == 'training_error' for state in states): - self.state = 'training_error' - i = states.index('training_error') + states = [submission_on_cv_fold.state for submission_on_cv_fold in all_cv_folds] + if all(state == "tested" for state in states): + self.state = "tested" + elif all(state in ["tested", "validated"] for state in states): + self.state = "validated" + elif all(state in ["tested", "validated", "trained"] for state in states): + self.state = "trained" + elif any(state == "training_error" for state in states): + self.state = "training_error" + i = states.index("training_error") self.error_msg = all_cv_folds[i].error_msg - elif any(state == 'validating_error' for state in states): - self.state = 'validating_error' - i = states.index('validating_error') + elif any(state == "validating_error" for state in states): + self.state = "validating_error" + i = states.index("validating_error") self.error_msg = all_cv_folds[i].error_msg - elif any(state == 'testing_error' for state in states): - self.state = 'testing_error' - i = states.index('testing_error') + elif any(state == "testing_error" for state in states): + self.state = "testing_error" + i = states.index("testing_error") self.error_msg = all_cv_folds[i].error_msg - if 'error' not in self.state: - self.error_msg = '' + if "error" not in self.state: + self.error_msg = "" class SubmissionScore(Model): @@ -541,19 +553,19 @@ class SubmissionScore(Model): on_cv_folds : list of :class:`ramp_database.model.SubmissionScoreOnCVFold` A back-reference the CV fold associated with the score. """ - __tablename__ = 'submission_scores' + + __tablename__ = "submission_scores" id = Column(Integer, primary_key=True) - submission_id = Column(Integer, ForeignKey('submissions.id'), - nullable=False) - submission = relationship('Submission', - backref=backref('scores', - cascade='all, delete-orphan')) + submission_id = Column(Integer, ForeignKey("submissions.id"), nullable=False) + submission = relationship( + "Submission", backref=backref("scores", cascade="all, delete-orphan") + ) - event_score_type_id = Column(Integer, ForeignKey('event_score_types.id'), - nullable=False) - event_score_type = relationship('EventScoreType', - backref=backref('submissions')) + event_score_type_id = Column( + Integer, ForeignKey("event_score_types.id"), nullable=False + ) + event_score_type = relationship("EventScoreType", backref=backref("submissions")) # These are cv-bagged scores. Individual scores are found in # SubmissionToTrain @@ -611,31 +623,35 @@ class SubmissionFile(Model): :class:`ramp_database.model.SubmissionFileTypeExtension` The associated submission file type extension instance. """ - __tablename__ = 'submission_files' + + __tablename__ = "submission_files" id = Column(Integer, primary_key=True) - submission_id = Column(Integer, ForeignKey('submissions.id'), - nullable=False) - submission = relationship('Submission', - backref=backref('files', - cascade='all, delete-orphan')) + submission_id = Column(Integer, ForeignKey("submissions.id"), nullable=False) + submission = relationship( + "Submission", backref=backref("files", cascade="all, delete-orphan") + ) - workflow_element_id = Column(Integer, ForeignKey('workflow_elements.id'), - nullable=False) - workflow_element = relationship('WorkflowElement', - backref=backref('submission_files')) + workflow_element_id = Column( + Integer, ForeignKey("workflow_elements.id"), nullable=False + ) + workflow_element = relationship( + "WorkflowElement", backref=backref("submission_files") + ) submission_file_type_extension_id = Column( - Integer, ForeignKey('submission_file_type_extensions.id'), - nullable=False + Integer, + ForeignKey("submission_file_type_extensions.id"), + nullable=False, ) submission_file_type_extension = relationship( - 'SubmissionFileTypeExtension', backref=backref('submission_files') + "SubmissionFileTypeExtension", backref=backref("submission_files") ) def __repr__(self): - return ('SubmissionFile(name={}, type={}, extension={}, path={})' - .format(self.name, self.type, self.extension, self.path)) + return "SubmissionFile(name={}, type={}, extension={}, path={})".format( + self.name, self.type, self.extension, self.path + ) @property def is_editable(self): @@ -661,13 +677,13 @@ def name(self): @property def f_name(self): """str: The corresponding file name.""" - return self.type + '.' + self.extension + return self.type + "." + self.extension @property def link(self): """str: A unique link to the file. The hash is generated by the Submission instance.""" - return '/' + os.path.join(self.submission.hash_, self.f_name) + return "/" + os.path.join(self.submission.hash_, self.f_name) @property def path(self): @@ -677,7 +693,7 @@ def path(self): @property def name_with_link(self): """str: The HTML hyperlink of the name of the submission file.""" - return '' + self.name + '' + return '' + self.name + "" def get_code(self): """str: Get the content of the file.""" @@ -693,7 +709,7 @@ def set_code(self, code): code : str The code to write into the submission file. """ - with open(self.path, 'w') as f: + with open(self.path, "w") as f: f.write(code) @@ -719,19 +735,18 @@ class SubmissionFileTypeExtension(Model): :class:`ramp_database.model.SubmissionFileTypeExtension` A back-reference to the submission files related to the type extension. """ - __tablename__ = 'submission_file_type_extensions' + + __tablename__ = "submission_file_type_extensions" id = Column(Integer, primary_key=True) - type_id = Column(Integer, ForeignKey('submission_file_types.id'), - nullable=False) - type = relationship('SubmissionFileType', backref=backref('extensions')) + type_id = Column(Integer, ForeignKey("submission_file_types.id"), nullable=False) + type = relationship("SubmissionFileType", backref=backref("extensions")) - extension_id = Column(Integer, ForeignKey('extensions.id'), nullable=False) - extension = relationship('Extension', - backref=backref('submission_file_types')) + extension_id = Column(Integer, ForeignKey("extensions.id"), nullable=False) + extension = relationship("Extension", backref=backref("submission_file_types")) - UniqueConstraint(type_id, extension_id, name='we_constraint') + UniqueConstraint(type_id, extension_id, name="we_constraint") @property def file_type(self): @@ -762,7 +777,8 @@ class SubmissionFileType(Model): A back-reference to the workflow element type for this submission file type. """ - __tablename__ = 'submission_file_types' + + __tablename__ = "submission_file_types" id = Column(Integer, primary_key=True) name = Column(String, nullable=False, unique=True) @@ -783,7 +799,8 @@ class Extension(Model): :class:`ramp_database.model.SubmissionFileTypeExtension` A back-reference to the submission file types for this extension. """ - __tablename__ = 'extensions' + + __tablename__ = "extensions" id = Column(Integer, primary_key=True) name = Column(String, nullable=False, unique=True) @@ -811,22 +828,24 @@ class SubmissionScoreOnCVFold(Model): test_score : float The testing score on the fold. """ - __tablename__ = 'submission_score_on_cv_folds' + + __tablename__ = "submission_score_on_cv_folds" id = Column(Integer, primary_key=True) submission_on_cv_fold_id = Column( - Integer, ForeignKey('submission_on_cv_folds.id'), nullable=False + Integer, ForeignKey("submission_on_cv_folds.id"), nullable=False ) submission_on_cv_fold = relationship( - 'SubmissionOnCVFold', - backref=backref('scores', cascade='all, delete-orphan') + "SubmissionOnCVFold", + backref=backref("scores", cascade="all, delete-orphan"), ) - submission_score_id = Column(Integer, ForeignKey('submission_scores.id'), - nullable=False) + submission_score_id = Column( + Integer, ForeignKey("submission_scores.id"), nullable=False + ) submission_score = relationship( - 'SubmissionScore', - backref=backref('on_cv_folds', cascade='all, delete-orphan') + "SubmissionScore", + backref=backref("on_cv_folds", cascade="all, delete-orphan"), ) train_score = Column(Float) @@ -834,7 +853,8 @@ class SubmissionScoreOnCVFold(Model): test_score = Column(Float) UniqueConstraint( - submission_on_cv_fold_id, submission_score_id, name='ss_constraint') + submission_on_cv_fold_id, submission_score_id, name="ss_constraint" + ) @property def name(self): @@ -904,20 +924,20 @@ class SubmissionOnCVFold(Model): predictions. In a sense substituting CPU time for storage. """ - __tablename__ = 'submission_on_cv_folds' + __tablename__ = "submission_on_cv_folds" id = Column(Integer, primary_key=True) - submission_id = Column(Integer, ForeignKey('submissions.id'), - nullable=False) - submission = relationship('Submission', - backref=backref('on_cv_folds', - cascade="all, delete-orphan")) + submission_id = Column(Integer, ForeignKey("submissions.id"), nullable=False) + submission = relationship( + "Submission", + backref=backref("on_cv_folds", cascade="all, delete-orphan"), + ) - cv_fold_id = Column(Integer, ForeignKey('cv_folds.id'), nullable=False) - cv_fold = relationship('CVFold', - backref=backref('submissions', - cascade="all, delete-orphan")) + cv_fold_id = Column(Integer, ForeignKey("cv_folds.id"), nullable=False) + cv_fold = relationship( + "CVFold", backref=backref("submissions", cascade="all, delete-orphan") + ) # filled by cv_fold.get_combined_predictions contributivity = Column(Float, default=0.0) @@ -928,10 +948,10 @@ class SubmissionOnCVFold(Model): train_time = Column(Float, default=0.0) valid_time = Column(Float, default=0.0) test_time = Column(Float, default=0.0) - state = Column(submission_states, default='new') - error_msg = Column(String, default='') + state = Column(submission_states, default="new") + error_msg = Column(String, default="") - UniqueConstraint(submission_id, cv_fold_id, name='sc_constraint') + UniqueConstraint(submission_id, cv_fold_id, name="sc_constraint") def __init__(self, submission, cv_fold): self.submission = submission @@ -939,40 +959,48 @@ def __init__(self, submission, cv_fold): self.session = inspect(submission).session for score in submission.scores: submission_score_on_cv_fold = SubmissionScoreOnCVFold( - submission_on_cv_fold=self, submission_score=score) + submission_on_cv_fold=self, submission_score=score + ) self.session.add(submission_score_on_cv_fold) self.reset() def __repr__(self): - return ('state = {}, c = {}, best = {}' - .format(self.state, self.contributivity, self.best)) + return "state = {}, c = {}, best = {}".format( + self.state, self.contributivity, self.best + ) @hybrid_property def is_public_leaderboard(self): """bool: Whether or not the submission is scored and ready to be on the public leaderboard.""" - return self.state == 'scored' + return self.state == "scored" @hybrid_property def is_trained(self): """bool: Whether or not the submission was trained.""" - return self.state in ('trained', 'validated', 'tested', - 'validating_error', 'testing_error', 'scored') + return self.state in ( + "trained", + "validated", + "tested", + "validating_error", + "testing_error", + "scored", + ) @hybrid_property def is_validated(self): """bool: Whether or not the submission was validated.""" - return self.state in ('validated', 'tested', 'testing_error', 'scored') + return self.state in ("validated", "tested", "testing_error", "scored") @hybrid_property def is_tested(self): """bool: Whether or not the submission was tested.""" - return self.state in ('tested', 'scored') + return self.state in ("tested", "scored") @hybrid_property def is_error(self): """bool: Whether or not the submission failed at one of the stage.""" - return 'error' in self.state + return "error" in self.state # TODO: the following 8 properties are never used and not tested. # They are present for historical reasons and should likely be @@ -983,7 +1011,9 @@ def path_predictions(self): return os.path.join( self.submission.event.path_ramp_submissions, self.submission.name, - 'training_output', 'fold_{}'.format(self.cv_fold_id)) + "training_output", + "fold_{}".format(self.cv_fold_id), + ) @property def full_train_y_pred(self): @@ -991,14 +1021,14 @@ def full_train_y_pred(self): including train and valid points. """ - return np.load( - os.path.join(self.path_predictions, 'y_pred_train.npz'))['y_pred'] + return np.load(os.path.join(self.path_predictions, "y_pred_train.npz"))[ + "y_pred" + ] @property def test_y_pred(self): """Load predictions on the test set""" - return np.load( - os.path.join(self.path_predictions, 'y_pred_test.npz'))['y_pred'] + return np.load(os.path.join(self.path_predictions, "y_pred_test.npz"))["y_pred"] # The following four functions are converting the stored numpy arrays # <>_y_pred into Prediction instances @@ -1013,14 +1043,16 @@ def train_predictions(self): """:class:`rampwf.prediction_types.Predictions`: Training predictions.""" return self.submission.Predictions( - y_pred=self.full_train_y_pred[self.cv_fold.train_is]) + y_pred=self.full_train_y_pred[self.cv_fold.train_is] + ) @property def valid_predictions(self): """:class:`rampwf.prediction_types.Predictions`: Validation predictions.""" return self.submission.Predictions( - y_pred=self.full_train_y_pred[self.cv_fold.test_is]) + y_pred=self.full_train_y_pred[self.cv_fold.test_is] + ) @property def test_predictions(self): @@ -1047,8 +1079,8 @@ def reset(self): self.train_time = 0.0 self.valid_time = 0.0 self.test_time = 0.0 - self.state = 'new' - self.error_msg = '' + self.state = "new" + self.error_msg = "" for score in self.scores: score.train_score = score.event_score_type.worst score.valid_score = score.event_score_type.worst @@ -1074,10 +1106,10 @@ def set_error(self, error, error_msg): submission_similarity_type = Enum( - 'target_credit', # credit given by one of the authors of target - 'source_credit', # credit given by one of the authors of source - 'thirdparty_credit', # credit given by an independent user - name='submission_similarity_type' + "target_credit", # credit given by one of the authors of target + "source_credit", # credit given by one of the authors of source + "thirdparty_credit", # credit given by an independent user + name="submission_similarity_type", ) @@ -1109,7 +1141,8 @@ class SubmissionSimilarity(Model): target_submission : :class:`ramp_database.model.Submission` The target submission instance. """ - __tablename__ = 'submission_similaritys' + + __tablename__ = "submission_similaritys" id = Column(Integer, primary_key=True) type = Column(submission_similarity_type, nullable=False) @@ -1117,28 +1150,32 @@ class SubmissionSimilarity(Model): timestamp = Column(DateTime, default=datetime.datetime.utcnow()) similarity = Column(Float, default=0.0) - user_id = Column(Integer, ForeignKey('users.id')) - user = relationship('User', - backref=backref('submission_similaritys', - cascade='all, delete-orphan')) + user_id = Column(Integer, ForeignKey("users.id")) + user = relationship( + "User", + backref=backref("submission_similaritys", cascade="all, delete-orphan"), + ) - source_submission_id = Column(Integer, ForeignKey('submissions.id')) + source_submission_id = Column(Integer, ForeignKey("submissions.id")) source_submission = relationship( - 'Submission', primaryjoin=( - 'SubmissionSimilarity.source_submission_id == Submission.id'), - backref=backref('sources', cascade='all, delete-orphan') + "Submission", + primaryjoin=("SubmissionSimilarity.source_submission_id == Submission.id"), + backref=backref("sources", cascade="all, delete-orphan"), ) - target_submission_id = Column(Integer, ForeignKey('submissions.id')) + target_submission_id = Column(Integer, ForeignKey("submissions.id")) target_submission = relationship( - 'Submission', primaryjoin=( - 'SubmissionSimilarity.target_submission_id == Submission.id'), - backref=backref('targets', cascade='all, delete-orphan')) + "Submission", + primaryjoin=("SubmissionSimilarity.target_submission_id == Submission.id"), + backref=backref("targets", cascade="all, delete-orphan"), + ) def __repr__(self): - text = ('type={}, user={}, source={}, target={} ' - .format(self.type, self.user, self.source_submission, - self.target_submission)) - text += 'similarity={}, timestamp={}'.format(self.similarity, - self.timestamp) + text = "type={}, user={}, source={}, target={} ".format( + self.type, + self.user, + self.source_submission, + self.target_submission, + ) + text += "similarity={}, timestamp={}".format(self.similarity, self.timestamp) return text diff --git a/ramp-database/ramp_database/model/team.py b/ramp-database/ramp_database/model/team.py index a681488d3..e23ddea66 100644 --- a/ramp-database/ramp_database/model/team.py +++ b/ramp-database/ramp_database/model/team.py @@ -10,7 +10,7 @@ from .base import Model -__all__ = ['Team'] +__all__ = ["Team"] class Team(Model): @@ -48,25 +48,26 @@ class Team(Model): team_events : :class:`ramp_database.model.EventTeam` A back-reference to the events to which the team is enroll. """ - __tablename__ = 'teams' + + __tablename__ = "teams" id = Column(Integer, primary_key=True) name = Column(String(20), nullable=False, unique=True) - admin_id = Column(Integer, ForeignKey('users.id')) - admin = relationship('User', - backref=backref('admined_teams', - cascade="all, delete")) + admin_id = Column(Integer, ForeignKey("users.id")) + admin = relationship( + "User", backref=backref("admined_teams", cascade="all, delete") + ) # initiator asks for merge, acceptor accepts - initiator_id = Column(Integer, ForeignKey('teams.id'), default=None) + initiator_id = Column(Integer, ForeignKey("teams.id"), default=None) initiator = relationship( - 'Team', primaryjoin=('Team.initiator_id == Team.id'), uselist=False + "Team", primaryjoin=("Team.initiator_id == Team.id"), uselist=False ) - acceptor_id = Column(Integer, ForeignKey('teams.id'), default=None) + acceptor_id = Column(Integer, ForeignKey("teams.id"), default=None) acceptor = relationship( - 'Team', primaryjoin=('Team.acceptor_id == Team.id'), uselist=False + "Team", primaryjoin=("Team.acceptor_id == Team.id"), uselist=False ) creation_timestamp = Column(DateTime, nullable=False) @@ -79,9 +80,9 @@ def __init__(self, name, admin, initiator=None, acceptor=None): self.creation_timestamp = datetime.datetime.utcnow() def __str__(self): - return 'Team({})'.format(self.name) + return "Team({})".format(self.name) def __repr__(self): - return ('Team(name={}, admin_name={}, initiator={}, acceptor={})' - .format(self.name, self.admin.name, - self.initiator, self.acceptor)) + return "Team(name={}, admin_name={}, initiator={}, acceptor={})".format( + self.name, self.admin.name, self.initiator, self.acceptor + ) diff --git a/ramp-database/ramp_database/model/tests/test_event.py b/ramp-database/ramp_database/model/tests/test_event.py index cff68115e..39d712abf 100644 --- a/ramp-database/ramp_database/model/tests/test_event.py +++ b/ramp-database/ramp_database/model/tests/test_event.py @@ -29,47 +29,75 @@ from ramp_database.tools.user import get_team_by_name -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_event_model_property(session_scope_module): - event = get_event(session_scope_module, 'iris_test') + event = get_event(session_scope_module, "iris_test") - assert repr(event) == 'Event(iris_test)' + assert repr(event) == "Event(iris_test)" assert issubclass(event.Predictions, BasePrediction) assert isinstance(event.workflow, Workflow) - assert event.workflow.name == 'Estimator' + assert event.workflow.name == "Estimator" assert event.n_participants == 2 assert event.n_jobs == 2 @pytest.mark.parametrize( "opening, public_opening, closure, properties, expected_values", - [(None, None, None, ['is_open'], [True]), - (None, None, datetime.datetime.utcnow(), ['is_open', 'is_closed'], - [False, True]), - (datetime.datetime.utcnow() + datetime.timedelta(days=1), None, None, - ['is_open', 'is_closed'], [False, False]), - (None, None, datetime.datetime.utcnow(), ['is_public_open', 'is_closed'], - [False, True]), - (None, datetime.datetime.utcnow() + datetime.timedelta(days=1), None, - ['is_public_open', 'is_closed'], [False, False])] + [ + (None, None, None, ["is_open"], [True]), + ( + None, + None, + datetime.datetime.utcnow(), + ["is_open", "is_closed"], + [False, True], + ), + ( + datetime.datetime.utcnow() + datetime.timedelta(days=1), + None, + None, + ["is_open", "is_closed"], + [False, False], + ), + ( + None, + None, + datetime.datetime.utcnow(), + ["is_public_open", "is_closed"], + [False, True], + ), + ( + None, + datetime.datetime.utcnow() + datetime.timedelta(days=1), + None, + ["is_public_open", "is_closed"], + [False, False], + ), + ], ) -def test_even_model_timestamp(session_scope_module, opening, public_opening, - closure, properties, expected_values): +def test_even_model_timestamp( + session_scope_module, + opening, + public_opening, + closure, + properties, + expected_values, +): # check the property linked to the opening/closure of the event. - event = get_event(session_scope_module, 'iris_test') + event = get_event(session_scope_module, "iris_test") # store the original timestamp before to force them init_opening = event.opening_timestamp @@ -78,9 +106,9 @@ def test_even_model_timestamp(session_scope_module, opening, public_opening, # set to non-default values the date if necessary event.opening_timestamp = opening if opening is not None else init_opening - event.public_opening_timestamp = (public_opening - if public_opening is not None - else init_public_opening) + event.public_opening_timestamp = ( + public_opening if public_opening is not None else init_public_opening + ) event.closing_timestamp = closure if closure is not None else init_closure for prop, exp_val in zip(properties, expected_values): @@ -97,17 +125,17 @@ def test_event_model_score(session_scope_module): # Make Model usable in declarative mode set_query_property(Model, session_scope_module) - event = get_event(session_scope_module, 'iris_test') + event = get_event(session_scope_module, "iris_test") - assert repr(event) == 'Event(iris_test)' + assert repr(event) == "Event(iris_test)" assert issubclass(event.Predictions, BasePrediction) assert isinstance(event.workflow, Workflow) - assert event.workflow.name == 'Estimator' + assert event.workflow.name == "Estimator" event_type_score = event.official_score_type - assert event_type_score.name == 'acc' + assert event_type_score.name == "acc" event_type_score = event.get_official_score_type(session_scope_module) - assert event_type_score.name == 'acc' + assert event_type_score.name == "acc" assert event.combined_combined_valid_score_str is None assert event.combined_combined_test_score_str is None @@ -119,21 +147,23 @@ def test_event_model_score(session_scope_module): event.combined_foldwise_valid_score = 0.3 event.combined_foldwise_test_score = 0.4 - assert event.combined_combined_valid_score_str == '0.1' - assert event.combined_combined_test_score_str == '0.2' - assert event.combined_foldwise_valid_score_str == '0.3' - assert event.combined_foldwise_test_score_str == '0.4' + assert event.combined_combined_valid_score_str == "0.1" + assert event.combined_combined_test_score_str == "0.2" + assert event.combined_foldwise_valid_score_str == "0.3" + assert event.combined_foldwise_test_score_str == "0.4" @pytest.mark.parametrize( - 'backref, expected_type', - [('score_types', EventScoreType), - ('event_admins', EventAdmin), - ('event_teams', EventTeam), - ('cv_folds', CVFold)] + "backref, expected_type", + [ + ("score_types", EventScoreType), + ("event_admins", EventAdmin), + ("event_teams", EventTeam), + ("cv_folds", CVFold), + ], ) def test_event_model_backref(session_scope_module, backref, expected_type): - event = get_event(session_scope_module, 'iris_test') + event = get_event(session_scope_module, "iris_test") backref_attr = getattr(event, backref) assert isinstance(backref_attr, list) # only check if the list is not empty @@ -142,13 +172,14 @@ def test_event_model_backref(session_scope_module, backref, expected_type): def test_event_score_type_model_property(session_scope_module): - event = get_event(session_scope_module, 'iris_test') + event = get_event(session_scope_module, "iris_test") # get only the accuracy score - event_type_score = \ - (session_scope_module.query(EventScoreType) - .filter(EventScoreType.event_id == event.id) - .filter(EventScoreType.name == 'acc') - .one()) + event_type_score = ( + session_scope_module.query(EventScoreType) + .filter(EventScoreType.event_id == event.id) + .filter(EventScoreType.name == "acc") + .one() + ) assert repr(event_type_score) == "acc: Event(iris_test)" assert isinstance(event_type_score.score_type_object, Accuracy) @@ -159,19 +190,16 @@ def test_event_score_type_model_property(session_scope_module): assert callable(event_type_score.score_type_object.score_function) -@pytest.mark.parametrize( - 'backref, expected_type', - [('submissions', SubmissionScore)] -) -def test_event_score_type_model_backref(session_scope_module, backref, - expected_type): - event = get_event(session_scope_module, 'iris_test') +@pytest.mark.parametrize("backref, expected_type", [("submissions", SubmissionScore)]) +def test_event_score_type_model_backref(session_scope_module, backref, expected_type): + event = get_event(session_scope_module, "iris_test") # get only the accuracy score - event_type_score = \ - (session_scope_module.query(EventScoreType) - .filter(EventScoreType.event_id == event.id) - .filter(EventScoreType.name == 'acc') - .one()) + event_type_score = ( + session_scope_module.query(EventScoreType) + .filter(EventScoreType.event_id == event.id) + .filter(EventScoreType.name == "acc") + .one() + ) backref_attr = getattr(event_type_score, backref) assert isinstance(backref_attr, list) # only check if the list is not empty @@ -180,28 +208,28 @@ def test_event_score_type_model_backref(session_scope_module, backref, def test_event_team_model(session_scope_module): - event = get_event(session_scope_module, 'iris_test') - team = get_team_by_name(session_scope_module, 'test_user') - - event_team = (session_scope_module.query(EventTeam) - .filter(EventTeam.event_id == event.id) - .filter(EventTeam.team_id == team.id) - .one()) - assert repr(event_team) == "Event(iris_test)/Team({})".format('test_user') - - -@pytest.mark.parametrize( - 'backref, expected_type', - [('submissions', Submission)] -) -def test_event_team_model_backref(session_scope_module, backref, - expected_type): - event = get_event(session_scope_module, 'iris_test') - team = get_team_by_name(session_scope_module, 'test_user') - event_team = (session_scope_module.query(EventTeam) - .filter(EventTeam.event_id == event.id) - .filter(EventTeam.team_id == team.id) - .one()) + event = get_event(session_scope_module, "iris_test") + team = get_team_by_name(session_scope_module, "test_user") + + event_team = ( + session_scope_module.query(EventTeam) + .filter(EventTeam.event_id == event.id) + .filter(EventTeam.team_id == team.id) + .one() + ) + assert repr(event_team) == "Event(iris_test)/Team({})".format("test_user") + + +@pytest.mark.parametrize("backref, expected_type", [("submissions", Submission)]) +def test_event_team_model_backref(session_scope_module, backref, expected_type): + event = get_event(session_scope_module, "iris_test") + team = get_team_by_name(session_scope_module, "test_user") + event_team = ( + session_scope_module.query(EventTeam) + .filter(EventTeam.event_id == event.id) + .filter(EventTeam.team_id == team.id) + .one() + ) backref_attr = getattr(event_team, backref) assert isinstance(backref_attr, list) # only check if the list is not empty diff --git a/ramp-database/ramp_database/model/tests/test_fold.py b/ramp-database/ramp_database/model/tests/test_fold.py index 313848cad..4873c52b2 100644 --- a/ramp-database/ramp_database/model/tests/test_fold.py +++ b/ramp-database/ramp_database/model/tests/test_fold.py @@ -17,37 +17,36 @@ from ramp_database.tools.event import get_event -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_cv_fold_model(session_scope_module): - event = get_event(session_scope_module, 'iris_test') - cv_fold = (session_scope_module.query(CVFold) - .filter(CVFold.event_id == event.id) - .all()) + event = get_event(session_scope_module, "iris_test") + cv_fold = ( + session_scope_module.query(CVFold).filter(CVFold.event_id == event.id).all() + ) assert "train fold [" in repr(cv_fold[0]) @pytest.mark.parametrize( - 'backref, expected_type', - [('submissions', SubmissionOnCVFold)] + "backref, expected_type", [("submissions", SubmissionOnCVFold)] ) def test_cv_fold_model_backref(session_scope_module, backref, expected_type): - event = get_event(session_scope_module, 'iris_test') - cv_fold = (session_scope_module.query(CVFold) - .filter(CVFold.event_id == event.id) - .first()) + event = get_event(session_scope_module, "iris_test") + cv_fold = ( + session_scope_module.query(CVFold).filter(CVFold.event_id == event.id).first() + ) backref_attr = getattr(cv_fold, backref) assert isinstance(backref_attr, list) # only check if the list is not empty diff --git a/ramp-database/ramp_database/model/tests/test_problem.py b/ramp-database/ramp_database/model/tests/test_problem.py index 6c6125ffe..f3c943755 100644 --- a/ramp-database/ramp_database/model/tests/test_problem.py +++ b/ramp-database/ramp_database/model/tests/test_problem.py @@ -20,32 +20,33 @@ from ramp_database.tools.event import get_problem -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_problem_model(session_scope_module): - problem = get_problem(session_scope_module, 'iris') + problem = get_problem(session_scope_module, "iris") - assert (repr(problem) == - "Problem({})\nWorkflow(Estimator)\n\tWorkflow(Estimator): " - "WorkflowElement(estimator)".format('iris')) + assert repr(problem) == ( + "Problem(iris)\nWorkflow(Estimator)\n\tWorkflow(Estimator): " + "WorkflowElement(estimator)" + ) # check that we can access the problem module and that we have one of the # expected function there. - assert hasattr(problem.module, 'get_train_data') + assert hasattr(problem.module, "get_train_data") - assert problem.title == 'Iris classification' + assert problem.title == "Iris classification" assert issubclass(problem.Predictions, BasePrediction) X_train, y_train = problem.get_train_data() assert X_train.shape == (120, 4) @@ -54,25 +55,23 @@ def test_problem_model(session_scope_module): assert X_test.shape == (30, 4) assert y_test.shape == (30,) gt_train = problem.ground_truths_train() - assert hasattr(gt_train, 'label_names') + assert hasattr(gt_train, "label_names") assert gt_train.y_pred.shape == (120, 3) gt_test = problem.ground_truths_test() - assert hasattr(gt_test, 'label_names') + assert hasattr(gt_test, "label_names") assert gt_test.y_pred.shape == (30, 3) gt_valid = problem.ground_truths_valid([0, 1, 2]) - assert hasattr(gt_valid, 'label_names') + assert hasattr(gt_valid, "label_names") assert gt_valid.y_pred.shape == (3, 3) assert isinstance(problem.workflow_object, Estimator) @pytest.mark.parametrize( - 'backref, expected_type', - [('events', Event), - ('keywords', ProblemKeyword)] + "backref, expected_type", [("events", Event), ("keywords", ProblemKeyword)] ) def test_problem_model_backref(session_scope_module, backref, expected_type): - problem = get_problem(session_scope_module, 'iris') + problem = get_problem(session_scope_module, "iris") backref_attr = getattr(problem, backref) assert isinstance(backref_attr, list) # only check if the list is not empty diff --git a/ramp-database/ramp_database/model/tests/test_score.py b/ramp-database/ramp_database/model/tests/test_score.py index 58967067d..4980bae7c 100644 --- a/ramp-database/ramp_database/model/tests/test_score.py +++ b/ramp-database/ramp_database/model/tests/test_score.py @@ -16,31 +16,27 @@ from ramp_database.testing import create_toy_db -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_score_type_model(session_scope_module): score_type = session_scope_module.query(ScoreType).first() - assert re.match(r'ScoreType\(name=.*\)', repr(score_type)) + assert re.match(r"ScoreType\(name=.*\)", repr(score_type)) -@pytest.mark.parametrize( - 'backref, expected_type', - [('events', EventScoreType)] -) -def test_score_type_model_backref(session_scope_module, backref, - expected_type): +@pytest.mark.parametrize("backref, expected_type", [("events", EventScoreType)]) +def test_score_type_model_backref(session_scope_module, backref, expected_type): score_type = session_scope_module.query(ScoreType).first() backref_attr = getattr(score_type, backref) assert isinstance(backref_attr, list) diff --git a/ramp-database/ramp_database/model/tests/test_submission.py b/ramp-database/ramp_database/model/tests/test_submission.py index c3e912caa..d058bb210 100644 --- a/ramp-database/ramp_database/model/tests/test_submission.py +++ b/ramp-database/ramp_database/model/tests/test_submission.py @@ -38,85 +38,86 @@ ID_SUBMISSION = 7 -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_submission_model_property(session_scope_module): # check that the property of Submission submission = get_submission_by_id(session_scope_module, ID_SUBMISSION) - assert re.match(r'Submission\(iris_test/test_user/.*\)', - str(submission)) - assert re.match(r'Submission\(event_name.*\)', repr(submission)) + assert re.match(r"Submission\(iris_test/test_user/.*\)", str(submission)) + assert re.match(r"Submission\(event_name.*\)", repr(submission)) assert isinstance(submission.team, Team) assert isinstance(submission.event, Event) - assert submission.official_score_name == 'acc' + assert submission.official_score_name == "acc" assert isinstance(submission.official_score, SubmissionScore) - assert all([isinstance(score, EventScoreType) - for score in submission.score_types]) + assert all([isinstance(score, EventScoreType) for score in submission.score_types]) assert issubclass(submission.Predictions, BasePrediction) assert submission.is_not_sandbox is True assert submission.is_error is False assert submission.is_public_leaderboard is False assert submission.is_private_leaderboard is False - submission_str = 'submission_00000000' + str(ID_SUBMISSION) - assert (os.path.join('submissions', submission_str) - in submission.path) + submission_str = "submission_00000000" + str(ID_SUBMISSION) + assert os.path.join("submissions", submission_str) in submission.path assert submission.basename == submission_str assert "submissions." + submission_str in submission.module assert len(submission.f_names) == 1 - assert submission.f_names[0] == 'estimator.py' - assert submission.link == '/' + os.path.join(submission.hash_, - 'estimator.py') - assert re.match('{}/{}/{}' - .format(submission.link, submission.event.name, - submission.team.name, submission.name), - submission.full_name_with_link) - assert re.match('{}' - .format(submission.link, submission.name), - submission.name_with_link) - assert re.match('{}' - .format(submission.hash_, submission.state), - submission.state_with_link) - - for score in submission.ordered_scores(score_names=['acc', 'error']): + assert submission.f_names[0] == "estimator.py" + assert submission.link == "/" + os.path.join(submission.hash_, "estimator.py") + assert re.match( + "{}/{}/{}".format( + submission.link, + submission.event.name, + submission.team.name, + submission.name, + ), + submission.full_name_with_link, + ) + assert re.match( + "{}".format(submission.link, submission.name), + submission.name_with_link, + ) + assert re.match( + "{}".format(submission.hash_, submission.state), + submission.state_with_link, + ) + + for score in submission.ordered_scores(score_names=["acc", "error"]): assert isinstance(score, SubmissionScore) def test_submission_model_set_state(session_scope_module): submission = get_submission_by_id(session_scope_module, ID_SUBMISSION) - submission.set_state('sent_to_training') - assert submission.state == 'sent_to_training' + submission.set_state("sent_to_training") + assert submission.state == "sent_to_training" assert ( submission.sent_to_training_timestamp - datetime.utcnow() ).total_seconds() < 10 - submission.set_state('training') - assert submission.state == 'training' - assert ( - submission.training_timestamp - datetime.utcnow() - ).total_seconds() < 10 + submission.set_state("training") + assert submission.state == "training" + assert (submission.training_timestamp - datetime.utcnow()).total_seconds() < 10 - submission.set_state('scored') - assert submission.state == 'scored' + submission.set_state("scored") + assert submission.state == "scored" for cv_fold in submission.on_cv_folds: - assert cv_fold.state == 'scored' + assert cv_fold.state == "scored" def test_submission_model_reset(session_scope_module): submission = get_submission_by_id(session_scope_module, ID_SUBMISSION) - for score in submission.ordered_scores(score_names=['acc', 'error']): + for score in submission.ordered_scores(score_names=["acc", "error"]): assert isinstance(score, SubmissionScore) # set the score to later test the reset function score.valid_score_cv_bag = 1.0 @@ -124,14 +125,13 @@ def test_submission_model_reset(session_scope_module): score.valid_score_cv_bags = np.ones(2) score.test_score_cv_bags = np.ones(2) # set to non-default the variable that should change with reset - submission.error_msg = 'simulate an error' - submission.contributivity = 100. + submission.error_msg = "simulate an error" + submission.contributivity = 100.0 submission.reset() assert submission.contributivity == pytest.approx(0) - assert submission.state == 'new' - assert submission.error_msg == '' - for score, worse_score in zip(submission.ordered_scores(['acc', 'error']), - [0, 1]): + assert submission.state == "new" + assert submission.error_msg == "" + for score, worse_score in zip(submission.ordered_scores(["acc", "error"]), [0, 1]): assert score.valid_score_cv_bag == pytest.approx(worse_score) assert score.test_score_cv_bag == pytest.approx(worse_score) assert score.valid_score_cv_bags is None @@ -140,8 +140,8 @@ def test_submission_model_reset(session_scope_module): def test_submission_model_set_error(session_scope_module): submission = get_submission_by_id(session_scope_module, ID_SUBMISSION) - error = 'training_error' - error_msg = 'simulate an error' + error = "training_error" + error_msg = "simulate an error" submission.set_error(error, error_msg) assert submission.state == error assert submission.error_msg == error_msg @@ -152,10 +152,11 @@ def test_submission_model_set_error(session_scope_module): @pytest.mark.parametrize( "state, expected_contributivity", - [('scored', 0.3), ('training_error', 0.0)] + [("scored", 0.3), ("training_error", 0.0)], ) -def test_submission_model_set_contributivity(session_scope_module, state, - expected_contributivity): +def test_submission_model_set_contributivity( + session_scope_module, state, expected_contributivity +): submission = get_submission_by_id(session_scope_module, ID_SUBMISSION) # set the state of the submission such that the contributivity submission.set_state(state) @@ -167,16 +168,17 @@ def test_submission_model_set_contributivity(session_scope_module, state, @pytest.mark.parametrize( - 'backref, expected_type', - [('historical_contributivitys', HistoricalContributivity), - ('scores', SubmissionScore), - ('files', SubmissionFile), - ('on_cv_folds', SubmissionOnCVFold), - ('sources', Submission), - ('targets', Submission)] + "backref, expected_type", + [ + ("historical_contributivitys", HistoricalContributivity), + ("scores", SubmissionScore), + ("files", SubmissionFile), + ("on_cv_folds", SubmissionOnCVFold), + ("sources", Submission), + ("targets", Submission), + ], ) -def test_submission_model_backref(session_scope_module, backref, - expected_type): +def test_submission_model_backref(session_scope_module, backref, expected_type): submission = get_submission_by_id(session_scope_module, ID_SUBMISSION) backref_attr = getattr(submission, backref) assert isinstance(backref_attr, list) @@ -187,19 +189,21 @@ def test_submission_model_backref(session_scope_module, backref, @pytest.mark.parametrize( "state_cv_folds, expected_state", - [(['tested', 'tested'], 'tested'), - (['tested', 'validated'], 'validated'), - (['validated', 'validated'], 'validated'), - (['trained', 'validated'], 'trained'), - (['trained', 'tested'], 'trained'), - (['trained', 'trained'], 'trained'), - (['training_error', 'tested'], 'training_error'), - (['validating_error', 'tested'], 'validating_error'), - (['testing_error', 'tested'], 'testing_error')] + [ + (["tested", "tested"], "tested"), + (["tested", "validated"], "validated"), + (["validated", "validated"], "validated"), + (["trained", "validated"], "trained"), + (["trained", "tested"], "trained"), + (["trained", "trained"], "trained"), + (["training_error", "tested"], "training_error"), + (["validating_error", "tested"], "validating_error"), + (["testing_error", "tested"], "testing_error"), + ], ) -def test_submission_model_set_state_after_training(session_scope_module, - state_cv_folds, - expected_state): +def test_submission_model_set_state_after_training( + session_scope_module, state_cv_folds, expected_state +): submission = get_submission_by_id(session_scope_module, ID_SUBMISSION) # set the state of the each fold for cv_fold, fold_state in zip(submission.on_cv_folds, state_cv_folds): @@ -212,29 +216,25 @@ def test_submission_score_model_property(session_scope_module): # get the submission associated with the 5th submission (iris) # we get only the information linked to the accuracy score which the first # score - submission_score = \ - (session_scope_module.query(SubmissionScore) - .filter( - SubmissionScore.submission_id == ID_SUBMISSION - ) - .first()) - assert submission_score.score_name == 'acc' + submission_score = ( + session_scope_module.query(SubmissionScore) + .filter(SubmissionScore.submission_id == ID_SUBMISSION) + .first() + ) + assert submission_score.score_name == "acc" assert callable(submission_score.score_function) assert submission_score.precision == 2 @pytest.mark.parametrize( - 'backref, expected_type', - [('on_cv_folds', SubmissionScoreOnCVFold)] + "backref, expected_type", [("on_cv_folds", SubmissionScoreOnCVFold)] ) -def test_submission_score_model_backref(session_scope_module, backref, - expected_type): - submission_score = \ - (session_scope_module.query(SubmissionScore) - .filter( - SubmissionScore.submission_id == ID_SUBMISSION - ) - .first()) +def test_submission_score_model_backref(session_scope_module, backref, expected_type): + submission_score = ( + session_scope_module.query(SubmissionScore) + .filter(SubmissionScore.submission_id == ID_SUBMISSION) + .first() + ) backref_attr = getattr(submission_score, backref) assert isinstance(backref_attr, list) # only check if the list is not empty @@ -244,46 +244,51 @@ def test_submission_score_model_backref(session_scope_module, backref, def test_submission_file_model_property(session_scope_module): # get the submission file of an iris submission with only a estimator file - submission_file = \ - (session_scope_module.query(SubmissionFile) - .filter( - SubmissionFile.submission_id == ID_SUBMISSION - ) - .first()) - assert re.match('SubmissionFile(.*)', - repr(submission_file)) + submission_file = ( + session_scope_module.query(SubmissionFile) + .filter(SubmissionFile.submission_id == ID_SUBMISSION) + .first() + ) + assert re.match("SubmissionFile(.*)", repr(submission_file)) assert submission_file.is_editable is True - assert submission_file.extension == 'py' - assert submission_file.type == 'estimator' - assert submission_file.name == 'estimator' - assert submission_file.f_name == 'estimator.py' - assert re.match('/.*estimator.py', submission_file.link) - submission_name = 'submission_00000000' + str(ID_SUBMISSION) - assert re.match(f'.*submissions.*{submission_name}.*estimator.py', - submission_file.path) - assert re.match('.*estimator', - submission_file.name_with_link) - assert re.match('from sklearn.ensemble import RandomForestClassifier.*', - submission_file.get_code()) - submission_file.set_code(code='# overwriting a code file') - assert submission_file.get_code() == '# overwriting a code file' + assert submission_file.extension == "py" + assert submission_file.type == "estimator" + assert submission_file.name == "estimator" + assert submission_file.f_name == "estimator.py" + assert re.match("/.*estimator.py", submission_file.link) + submission_name = "submission_00000000" + str(ID_SUBMISSION) + assert re.match( + f".*submissions.*{submission_name}.*estimator.py", submission_file.path + ) + assert re.match( + '.*estimator', + submission_file.name_with_link, + ) + assert re.match( + "from sklearn.ensemble import RandomForestClassifier.*", + submission_file.get_code(), + ) + submission_file.set_code(code="# overwriting a code file") + assert submission_file.get_code() == "# overwriting a code file" def test_submission_file_type_extension_model_property(session_scope_module): - submission_file_type_extension = \ - (session_scope_module.query(SubmissionFileTypeExtension).first()) - assert submission_file_type_extension.file_type == 'code' - assert submission_file_type_extension.extension_name == 'py' + submission_file_type_extension = session_scope_module.query( + SubmissionFileTypeExtension + ).first() + assert submission_file_type_extension.file_type == "code" + assert submission_file_type_extension.extension_name == "py" @pytest.mark.parametrize( - 'backref, expected_type', - [('submission_files', SubmissionFile)] + "backref, expected_type", [("submission_files", SubmissionFile)] ) -def test_submission_file_type_extension_model_backref(session_scope_module, - backref, expected_type): - submission_file_type_extension = \ - (session_scope_module.query(SubmissionFileTypeExtension).first()) +def test_submission_file_type_extension_model_backref( + session_scope_module, backref, expected_type +): + submission_file_type_extension = session_scope_module.query( + SubmissionFileTypeExtension + ).first() backref_attr = getattr(submission_file_type_extension, backref) assert isinstance(backref_attr, list) # only check if the list is not empty @@ -292,185 +297,197 @@ def test_submission_file_type_extension_model_backref(session_scope_module, def test_submission_score_on_cv_fold_model_property(session_scope_module): - cv_fold_score = (session_scope_module - .query(SubmissionScoreOnCVFold) # noqa - .filter(SubmissionScoreOnCVFold.submission_score_id == # noqa - SubmissionScore.id) # noqa - .filter(SubmissionScore.event_score_type_id == # noqa - EventScoreType.id) # noqa + cv_fold_score = ( + session_scope_module.query(SubmissionScoreOnCVFold) # noqa + .filter( + SubmissionScoreOnCVFold.submission_score_id == SubmissionScore.id # noqa + ) # noqa + .filter( + SubmissionScore.event_score_type_id == EventScoreType.id # noqa + ) # noqa .filter(SubmissionScore.submission_id == ID_SUBMISSION) # noqa - .filter(EventScoreType.name == 'acc') # noqa - .first()) # noqa - assert cv_fold_score.name == 'acc' + .filter(EventScoreType.name == "acc") # noqa + .first() + ) # noqa + assert cv_fold_score.name == "acc" assert isinstance(cv_fold_score.event_score_type, EventScoreType) assert callable(cv_fold_score.score_function) def test_submission_on_cv_fold_model_property(session_scope_module): - cv_fold = \ - (session_scope_module.query(SubmissionOnCVFold) - .filter( - SubmissionOnCVFold.submission_id == - ID_SUBMISSION - ).first()) - cv_fold.state = 'scored' + cv_fold = ( + session_scope_module.query(SubmissionOnCVFold) + .filter(SubmissionOnCVFold.submission_id == ID_SUBMISSION) + .first() + ) + cv_fold.state = "scored" cv_fold.contributivity = 0.2 - assert repr(cv_fold) == 'state = scored, c = 0.2, best = False' + assert repr(cv_fold) == "state = scored, c = 0.2, best = False" assert isinstance(cv_fold.official_score, SubmissionScoreOnCVFold) - assert cv_fold.official_score.name == 'acc' + assert cv_fold.official_score.name == "acc" @pytest.mark.parametrize( "state_set, expected_state", - [('new', False), - ('checked', False), - ('checking_error', False), - ('trained', False), - ('training_error', False), - ('validated', False), - ('validating_error', False), - ('tested', False), - ('testing_error', False), - ('training', False), - ('sent_to_training', False), - ('scored', True)] + [ + ("new", False), + ("checked", False), + ("checking_error", False), + ("trained", False), + ("training_error", False), + ("validated", False), + ("validating_error", False), + ("tested", False), + ("testing_error", False), + ("training", False), + ("sent_to_training", False), + ("scored", True), + ], ) def test_submission_on_cv_fold_model_is_public_leaderboard( - session_scope_module, state_set, expected_state): - cv_fold = \ - (session_scope_module.query(SubmissionOnCVFold) - .filter( - SubmissionOnCVFold.submission_id == - ID_SUBMISSION - ).first()) + session_scope_module, state_set, expected_state +): + cv_fold = ( + session_scope_module.query(SubmissionOnCVFold) + .filter(SubmissionOnCVFold.submission_id == ID_SUBMISSION) + .first() + ) cv_fold.state = state_set assert cv_fold.is_public_leaderboard is expected_state @pytest.mark.parametrize( "state_set, expected_state", - [('new', False), - ('checked', False), - ('checking_error', False), - ('trained', True), - ('training_error', False), - ('validated', True), - ('validating_error', True), - ('tested', True), - ('testing_error', True), - ('training', False), - ('sent_to_training', False), - ('scored', True)] + [ + ("new", False), + ("checked", False), + ("checking_error", False), + ("trained", True), + ("training_error", False), + ("validated", True), + ("validating_error", True), + ("tested", True), + ("testing_error", True), + ("training", False), + ("sent_to_training", False), + ("scored", True), + ], ) -def test_submission_on_cv_fold_model_is_trained(session_scope_module, - state_set, expected_state): - cv_fold = \ - (session_scope_module.query(SubmissionOnCVFold) - .filter( - SubmissionOnCVFold.submission_id == - ID_SUBMISSION - ).first()) +def test_submission_on_cv_fold_model_is_trained( + session_scope_module, state_set, expected_state +): + cv_fold = ( + session_scope_module.query(SubmissionOnCVFold) + .filter(SubmissionOnCVFold.submission_id == ID_SUBMISSION) + .first() + ) cv_fold.state = state_set assert cv_fold.is_trained is expected_state @pytest.mark.parametrize( "state_set, expected_state", - [('new', False), - ('checked', False), - ('checking_error', False), - ('trained', False), - ('training_error', False), - ('validated', True), - ('validating_error', False), - ('tested', True), - ('testing_error', True), - ('training', False), - ('sent_to_training', False), - ('scored', True)] + [ + ("new", False), + ("checked", False), + ("checking_error", False), + ("trained", False), + ("training_error", False), + ("validated", True), + ("validating_error", False), + ("tested", True), + ("testing_error", True), + ("training", False), + ("sent_to_training", False), + ("scored", True), + ], ) -def test_submission_on_cv_fold_model_is_validated(session_scope_module, - state_set, expected_state): - cv_fold = \ - (session_scope_module.query(SubmissionOnCVFold) - .filter( - SubmissionOnCVFold.submission_id == - ID_SUBMISSION - ).first()) +def test_submission_on_cv_fold_model_is_validated( + session_scope_module, state_set, expected_state +): + cv_fold = ( + session_scope_module.query(SubmissionOnCVFold) + .filter(SubmissionOnCVFold.submission_id == ID_SUBMISSION) + .first() + ) cv_fold.state = state_set assert cv_fold.is_validated is expected_state @pytest.mark.parametrize( "state_set, expected_state", - [('new', False), - ('checked', False), - ('checking_error', False), - ('trained', False), - ('training_error', False), - ('validated', False), - ('validating_error', False), - ('tested', True), - ('testing_error', False), - ('training', False), - ('sent_to_training', False), - ('scored', True)] + [ + ("new", False), + ("checked", False), + ("checking_error", False), + ("trained", False), + ("training_error", False), + ("validated", False), + ("validating_error", False), + ("tested", True), + ("testing_error", False), + ("training", False), + ("sent_to_training", False), + ("scored", True), + ], ) -def test_submission_on_cv_fold_model_is_tested(session_scope_module, - state_set, expected_state): - cv_fold = \ - (session_scope_module.query(SubmissionOnCVFold) - .filter( - SubmissionOnCVFold.submission_id == - ID_SUBMISSION - ).first()) +def test_submission_on_cv_fold_model_is_tested( + session_scope_module, state_set, expected_state +): + cv_fold = ( + session_scope_module.query(SubmissionOnCVFold) + .filter(SubmissionOnCVFold.submission_id == ID_SUBMISSION) + .first() + ) cv_fold.state = state_set assert cv_fold.is_tested is expected_state @pytest.mark.parametrize( "state_set, expected_state", - [('new', False), - ('checked', False), - ('checking_error', True), - ('trained', False), - ('training_error', True), - ('validated', False), - ('validating_error', True), - ('tested', False), - ('testing_error', True), - ('training', False), - ('sent_to_training', False), - ('scored', False)] + [ + ("new", False), + ("checked", False), + ("checking_error", True), + ("trained", False), + ("training_error", True), + ("validated", False), + ("validating_error", True), + ("tested", False), + ("testing_error", True), + ("training", False), + ("sent_to_training", False), + ("scored", False), + ], ) -def test_submission_on_cv_fold_model_is_error(session_scope_module, - state_set, expected_state): - cv_fold = \ - (session_scope_module.query(SubmissionOnCVFold) - .filter( - SubmissionOnCVFold.submission_id == - ID_SUBMISSION - ).first()) +def test_submission_on_cv_fold_model_is_error( + session_scope_module, state_set, expected_state +): + cv_fold = ( + session_scope_module.query(SubmissionOnCVFold) + .filter(SubmissionOnCVFold.submission_id == ID_SUBMISSION) + .first() + ) cv_fold.state = state_set assert cv_fold.is_error is expected_state def test_submission_on_cv_fold_model_reset(session_scope_module): - cv_fold = \ - (session_scope_module.query(SubmissionOnCVFold) - .filter(SubmissionOnCVFold.submission_id == - ID_SUBMISSION) - .first()) + cv_fold = ( + session_scope_module.query(SubmissionOnCVFold) + .filter(SubmissionOnCVFold.submission_id == ID_SUBMISSION) + .first() + ) # set to non-default values] cv_fold.contributivity = 0.3 cv_fold.best = True cv_fold.train_time = 1 cv_fold.valid_time = 1 cv_fold.test_time = 1 - cv_fold.state = 'scored' - cv_fold.error_msg = 'simulate a message' + cv_fold.state = "scored" + cv_fold.error_msg = "simulate a message" for score in cv_fold.scores: - if score.name == 'acc': + if score.name == "acc": score.train_score = 1.0 score.valid_score = 1.0 score.test_score = 1.0 @@ -481,39 +498,39 @@ def test_submission_on_cv_fold_model_reset(session_scope_module): assert cv_fold.train_time == pytest.approx(0) assert cv_fold.valid_time == pytest.approx(0) assert cv_fold.test_time == pytest.approx(0) - assert cv_fold.state == 'new' - assert cv_fold.error_msg == '' + assert cv_fold.state == "new" + assert cv_fold.error_msg == "" for score in cv_fold.scores: - if score.name == 'acc': + if score.name == "acc": assert score.train_score == pytest.approx(0) assert score.valid_score == pytest.approx(0) assert score.test_score == pytest.approx(0) def test_submission_on_cv_fold_model_error(session_scope_module): - cv_fold = \ - (session_scope_module.query(SubmissionOnCVFold) - .filter(SubmissionOnCVFold.submission_id == - ID_SUBMISSION) - .first()) - error = 'training_error' - error_msg = 'simulate an error' + cv_fold = ( + session_scope_module.query(SubmissionOnCVFold) + .filter(SubmissionOnCVFold.submission_id == ID_SUBMISSION) + .first() + ) + error = "training_error" + error_msg = "simulate an error" cv_fold.set_error(error, error_msg) assert cv_fold.state == error assert cv_fold.error_msg == error_msg @pytest.mark.parametrize( - 'backref, expected_type', - [('scores', SubmissionScoreOnCVFold)] + "backref, expected_type", [("scores", SubmissionScoreOnCVFold)] ) -def test_submission_on_cv_fold_model_backref(session_scope_module, backref, - expected_type): - cv_fold = \ - (session_scope_module.query(SubmissionOnCVFold) - .filter(SubmissionOnCVFold.submission_id == - ID_SUBMISSION) - .first()) +def test_submission_on_cv_fold_model_backref( + session_scope_module, backref, expected_type +): + cv_fold = ( + session_scope_module.query(SubmissionOnCVFold) + .filter(SubmissionOnCVFold.submission_id == ID_SUBMISSION) + .first() + ) backref_attr = getattr(cv_fold, backref) assert isinstance(backref_attr, list) # only check if the list is not empty @@ -522,8 +539,8 @@ def test_submission_on_cv_fold_model_backref(session_scope_module, backref, @pytest.mark.parametrize( - 'backref, expected_type', - [('submission_file_types', SubmissionFileTypeExtension)] + "backref, expected_type", + [("submission_file_types", SubmissionFileTypeExtension)], ) def test_extension_model_backref(session_scope_module, backref, expected_type): extension = session_scope_module.query(Extension).first() @@ -535,13 +552,12 @@ def test_extension_model_backref(session_scope_module, backref, expected_type): @pytest.mark.parametrize( - 'backref, expected_type', - [('workflow_element_types', WorkflowElementType)] + "backref, expected_type", [("workflow_element_types", WorkflowElementType)] ) -def test_submission_file_type_model_backref(session_scope_module, backref, - expected_type): - submission_file_type = (session_scope_module.query(SubmissionFileType) - .first()) +def test_submission_file_type_model_backref( + session_scope_module, backref, expected_type +): + submission_file_type = session_scope_module.query(SubmissionFileType).first() backref_attr = getattr(submission_file_type, backref) assert isinstance(backref_attr, list) # only check if the list is not empty diff --git a/ramp-database/ramp_database/model/tests/test_team.py b/ramp-database/ramp_database/model/tests/test_team.py index a8e7dada1..43ef6fef3 100644 --- a/ramp-database/ramp_database/model/tests/test_team.py +++ b/ramp-database/ramp_database/model/tests/test_team.py @@ -17,33 +17,29 @@ from ramp_database.tools.user import get_team_by_name -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_team_model(session_scope_module): - team = get_team_by_name(session_scope_module, 'test_user') - assert re.match(r'Team\(name=.*test_user.*, admin_name=.*test_user.*\)', - repr(team)) - assert re.match(r'Team\(.*test_user.*\)', str(team)) + team = get_team_by_name(session_scope_module, "test_user") + assert re.match(r"Team\(name=.*test_user.*, admin_name=.*test_user.*\)", repr(team)) + assert re.match(r"Team\(.*test_user.*\)", str(team)) -@pytest.mark.parametrize( - 'backref, expected_type', - [('team_events', EventTeam)] -) +@pytest.mark.parametrize("backref, expected_type", [("team_events", EventTeam)]) def test_event_model_backref(session_scope_module, backref, expected_type): - team = get_team_by_name(session_scope_module, 'test_user') + team = get_team_by_name(session_scope_module, "test_user") backref_attr = getattr(team, backref) assert isinstance(backref_attr, list) # only check if the list is not empty diff --git a/ramp-database/ramp_database/model/tests/test_user.py b/ramp-database/ramp_database/model/tests/test_user.py index bc2d603e3..32aa08cb5 100644 --- a/ramp-database/ramp_database/model/tests/test_user.py +++ b/ramp-database/ramp_database/model/tests/test_user.py @@ -19,38 +19,40 @@ from ramp_database.tools.user import get_user_by_name -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_user_model_properties(session_scope_module): - user = get_user_by_name(session_scope_module, 'test_user') + user = get_user_by_name(session_scope_module, "test_user") assert user.is_active is True assert user.is_anonymous is False - assert user.get_id() == '1' - assert re.match(r'User\(.*test_user.*\)', str(user)) - assert re.match(r'User\(name=.*test_user.*, lastname.*\)', repr(user)) + assert user.get_id() == "1" + assert re.match(r"User\(.*test_user.*\)", str(user)) + assert re.match(r"User\(name=.*test_user.*, lastname.*\)", repr(user)) @pytest.mark.parametrize( - 'backref, expected_type', - [('admined_events', EventAdmin), - ('submission_similaritys', SubmissionSimilarity), - ('admined_teams', Team)] + "backref, expected_type", + [ + ("admined_events", EventAdmin), + ("submission_similaritys", SubmissionSimilarity), + ("admined_teams", Team), + ], ) def test_user_model_backref(session_scope_module, backref, expected_type): - user = get_user_by_name(session_scope_module, 'test_user') + user = get_user_by_name(session_scope_module, "test_user") backref_attr = getattr(user, backref) assert isinstance(backref_attr, list) # only check if the list is not empty diff --git a/ramp-database/ramp_database/model/tests/test_workflow.py b/ramp-database/ramp_database/model/tests/test_workflow.py index 8c212db36..2a31c5949 100644 --- a/ramp-database/ramp_database/model/tests/test_workflow.py +++ b/ramp-database/ramp_database/model/tests/test_workflow.py @@ -22,47 +22,44 @@ from ramp_database.tools.event import get_workflow -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_workflow_element_type_model(session_scope_module): - workflow_element_type = \ - (session_scope_module.query(WorkflowElementType) - .filter(WorkflowElementType.type_id == - SubmissionFileType.id) - .filter(SubmissionFileType.name == 'code') - .first()) - - assert workflow_element_type.file_type == 'code' + workflow_element_type = ( + session_scope_module.query(WorkflowElementType) + .filter(WorkflowElementType.type_id == SubmissionFileType.id) + .filter(SubmissionFileType.name == "code") + .first() + ) + + assert workflow_element_type.file_type == "code" assert workflow_element_type.is_editable is True assert workflow_element_type.max_size == 100000 - assert re.match(r"WorkflowElementType\(.*\)", - repr(workflow_element_type)) - - -@pytest.mark.parametrize( - 'backref, expected_type', - [('workflows', WorkflowElement)] -) -def test_workflow_element_type_model_backref(session_scope_module, backref, - expected_type): - workflow_element_type = \ - (session_scope_module.query(WorkflowElementType) - .filter(WorkflowElementType.type_id == - SubmissionFileType.id) - .filter(SubmissionFileType.name == 'code') - .first()) + assert re.match(r"WorkflowElementType\(.*\)", repr(workflow_element_type)) + + +@pytest.mark.parametrize("backref, expected_type", [("workflows", WorkflowElement)]) +def test_workflow_element_type_model_backref( + session_scope_module, backref, expected_type +): + workflow_element_type = ( + session_scope_module.query(WorkflowElementType) + .filter(WorkflowElementType.type_id == SubmissionFileType.id) + .filter(SubmissionFileType.name == "code") + .first() + ) backref_attr = getattr(workflow_element_type, backref) assert isinstance(backref_attr, list) # only check if the list is not empty @@ -71,17 +68,16 @@ def test_workflow_element_type_model_backref(session_scope_module, backref, def test_workflow_model(session_scope_module): - workflow = get_workflow(session_scope_module, 'Estimator') - assert re.match(r'Workflow\(.*\)\n\t.*WorkflowElement.*', repr(workflow)) + workflow = get_workflow(session_scope_module, "Estimator") + assert re.match(r"Workflow\(.*\)\n\t.*WorkflowElement.*", repr(workflow)) @pytest.mark.parametrize( - 'backref, expected_type', - [('problems', Problem), - ('elements', WorkflowElement)] + "backref, expected_type", + [("problems", Problem), ("elements", WorkflowElement)], ) def test_workflow_model_backref(session_scope_module, backref, expected_type): - workflow = get_workflow(session_scope_module, 'Estimator') + workflow = get_workflow(session_scope_module, "Estimator") backref_attr = getattr(workflow, backref) assert isinstance(backref_attr, list) # only check if the list is not empty @@ -90,33 +86,30 @@ def test_workflow_model_backref(session_scope_module, backref, expected_type): def test_workflow_element_model(session_scope_module): - workflow_element = \ - (session_scope_module.query(WorkflowElement) - .filter(WorkflowElement.workflow_id == - Workflow.id) - .filter(Workflow.name == 'Estimator') - .one()) - - assert re.match(r'Workflow\(.*\): WorkflowElement\(.*\)', - repr(workflow_element)) - assert workflow_element.type == 'estimator' - assert workflow_element.file_type == 'code' + workflow_element = ( + session_scope_module.query(WorkflowElement) + .filter(WorkflowElement.workflow_id == Workflow.id) + .filter(Workflow.name == "Estimator") + .one() + ) + + assert re.match(r"Workflow\(.*\): WorkflowElement\(.*\)", repr(workflow_element)) + assert workflow_element.type == "estimator" + assert workflow_element.file_type == "code" assert workflow_element.is_editable is True assert workflow_element.max_size == 100000 @pytest.mark.parametrize( - 'backref, expected_type', - [('submission_files', SubmissionFile)] + "backref, expected_type", [("submission_files", SubmissionFile)] ) -def test_workflow_element_model_backref(session_scope_module, backref, - expected_type): - workflow_element = \ - (session_scope_module.query(WorkflowElement) - .filter(WorkflowElement.workflow_id == - Workflow.id) - .filter(Workflow.name == 'Estimator') - .one()) +def test_workflow_element_model_backref(session_scope_module, backref, expected_type): + workflow_element = ( + session_scope_module.query(WorkflowElement) + .filter(WorkflowElement.workflow_id == Workflow.id) + .filter(Workflow.name == "Estimator") + .one() + ) backref_attr = getattr(workflow_element, backref) assert isinstance(backref_attr, list) # only check if the list is not empty diff --git a/ramp-database/ramp_database/model/user.py b/ramp-database/ramp_database/model/user.py index aea79fc17..61e901e6d 100644 --- a/ramp-database/ramp_database/model/user.py +++ b/ramp-database/ramp_database/model/user.py @@ -16,8 +16,8 @@ from .event import EventTeam __all__ = [ - 'User', - 'UserInteraction', + "User", + "UserInteraction", ] @@ -111,7 +111,8 @@ class User(Model): admined_teams : list of :class:`ramp_database.model.Team` A back-reference to the teams administrated by the user. """ - __tablename__ = 'users' + + __tablename__ = "users" id = Column(Integer, primary_key=True) name = Column(String(20), nullable=False, unique=True) @@ -129,20 +130,35 @@ class User(Model): bio = Column(String(1024), default=None) is_want_news = Column(Boolean, default=True) access_level = Column( - Enum('admin', 'user', 'asked', 'not_confirmed', name='access_level'), - default='asked' + Enum("admin", "user", "asked", "not_confirmed", name="access_level"), + default="asked", ) signup_timestamp = Column(DateTime, nullable=False) - update_timestamp = Column(DateTime, onupdate=sql.func.now(), - server_default=sql.func.now()) + update_timestamp = Column( + DateTime, onupdate=sql.func.now(), server_default=sql.func.now() + ) # Flask-Login fields is_authenticated = Column(Boolean, default=False) - def __init__(self, name, hashed_password, lastname, firstname, email, - access_level='user', hidden_notes='', linkedin_url='', - twitter_url='', facebook_url='', google_url='', github_url='', - website_url='', bio='', is_want_news=True): + def __init__( + self, + name, + hashed_password, + lastname, + firstname, + email, + access_level="user", + hidden_notes="", + linkedin_url="", + twitter_url="", + facebook_url="", + google_url="", + github_url="", + website_url="", + bio="", + is_want_news=True, + ): self.name = name self.hashed_password = hashed_password self.lastname = lastname @@ -175,36 +191,42 @@ def get_id(self): return str(self.id) def __str__(self): - return 'User({})'.format(self.name) + return "User({})".format(self.name) def __repr__(self): - return ("User(name={}, lastname={}, firstname={}, email={}, " - "admined_teams={})" - .format(self.name, self.lastname, self.firstname, - self.email, self.admined_teams)) + return ( + "User(name={}, lastname={}, firstname={}, email={}, " + "admined_teams={})".format( + self.name, + self.lastname, + self.firstname, + self.email, + self.admined_teams, + ) + ) user_interaction_type = Enum( - 'copy', - 'download', - 'giving credit', - 'landing', - 'login', - 'logout', - 'looking at error', - 'looking at event', - 'looking at problem', - 'looking at problems', - 'looking at leaderboard', - 'looking at my_submissions', - 'looking at private leaderboard', - 'looking at submission', - 'looking at user', - 'save', - 'signing up at event', - 'submit', - 'upload', - name='user_interaction_type' + "copy", + "download", + "giving credit", + "landing", + "login", + "logout", + "looking at error", + "looking at event", + "looking at problem", + "looking at problems", + "looking at leaderboard", + "looking at my_submissions", + "looking at private leaderboard", + "looking at submission", + "looking at user", + "save", + "signing up at event", + "submit", + "upload", + name="user_interaction_type", ) @@ -279,7 +301,8 @@ class UserInteraction(Model): session : :class:`sqlalchemy.orm.Session` The session to directly perform the operation on the database. """ - __tablename__ = 'user_interactions' + + __tablename__ = "user_interactions" id = Column(Integer, primary_key=True, autoincrement=True) timestamp = Column(DateTime, nullable=False) @@ -289,35 +312,50 @@ class UserInteraction(Model): submission_file_similarity = Column(Float, default=None) ip = Column(String, default=None) - user_id = Column( - Integer, ForeignKey('users.id')) - user = relationship('User', - backref=backref('user_interactions', - cascade='all, delete-orphan')) - - problem_id = Column( - Integer, ForeignKey('problems.id')) - problem = relationship('Problem', backref=backref( - 'user_interactions', cascade='all, delete-orphan')) - - event_team_id = Column( - Integer, ForeignKey('event_teams.id')) - event_team = relationship('EventTeam', backref=backref( - 'user_interactions', cascade='all, delete-orphan')) - - submission_id = Column( - Integer, ForeignKey('submissions.id')) - submission = relationship('Submission', backref=backref( - 'user_interactions', cascade='all, delete-orphan')) - - submission_file_id = Column( - Integer, ForeignKey('submission_files.id')) - submission_file = relationship('SubmissionFile', backref=backref( - 'user_interactions', cascade='all, delete-orphan')) - - def __init__(self, interaction=None, user=None, problem=None, event=None, - ip=None, note=None, submission=None, submission_file=None, - diff=None, similarity=None, session=None): + user_id = Column(Integer, ForeignKey("users.id")) + user = relationship( + "User", + backref=backref("user_interactions", cascade="all, delete-orphan"), + ) + + problem_id = Column(Integer, ForeignKey("problems.id")) + problem = relationship( + "Problem", + backref=backref("user_interactions", cascade="all, delete-orphan"), + ) + + event_team_id = Column(Integer, ForeignKey("event_teams.id")) + event_team = relationship( + "EventTeam", + backref=backref("user_interactions", cascade="all, delete-orphan"), + ) + + submission_id = Column(Integer, ForeignKey("submissions.id")) + submission = relationship( + "Submission", + backref=backref("user_interactions", cascade="all, delete-orphan"), + ) + + submission_file_id = Column(Integer, ForeignKey("submission_files.id")) + submission_file = relationship( + "SubmissionFile", + backref=backref("user_interactions", cascade="all, delete-orphan"), + ) + + def __init__( + self, + interaction=None, + user=None, + problem=None, + event=None, + ip=None, + note=None, + submission=None, + submission_file=None, + diff=None, + similarity=None, + session=None, + ): self.timestamp = datetime.datetime.utcnow() self.interaction = interaction self.user = user @@ -328,13 +366,15 @@ def __init__(self, interaction=None, user=None, problem=None, event=None, # The current code works only if each user admins a single team. if session is None: self.event_team = EventTeam.query.filter_by( - event=event, team=user.admined_teams[0]).one_or_none() + event=event, team=user.admined_teams[0] + ).one_or_none() else: - self.event_team = \ - (session.query(EventTeam) - .filter(EventTeam.event == event) - .filter(EventTeam.team == user.admined_teams[0]) - .one_or_none()) + self.event_team = ( + session.query(EventTeam) + .filter(EventTeam.event == event) + .filter(EventTeam.team == user.admined_teams[0]) + .one_or_none() + ) self.ip = ip self.note = note self.submission = submission @@ -342,9 +382,9 @@ def __init__(self, interaction=None, user=None, problem=None, event=None, self.submission_file_diff = diff self.submission_file_similarity = similarity -# The following function was implemented to handle user interaction dump -# but it turned out that the db insertion was not the CPU sink. Keep it -# for a while if the site is still slow. + # The following function was implemented to handle user interaction dump + # but it turned out that the db insertion was not the CPU sink. Keep it + # for a while if the site is still slow. # def __init__(self, line=None, interaction=None, user=None, event=None, # ip=None, note=None, submission=None, submission_file=None, @@ -381,13 +421,22 @@ def __init__(self, interaction=None, user=None, problem=None, event=None, # self.submission_file_id = eval(tokens[9]) def __repr__(self): - return "; ".join(repr(member) - for member in (self.timestamp, self.interaction, - self.note, self.submission_file_diff, - self.submission_file_similarity, - self.ip, self.user, self.problem, - self.event_team, self.submission, - self.submission_file)) + return "; ".join( + repr(member) + for member in ( + self.timestamp, + self.interaction, + self.note, + self.submission_file_diff, + self.submission_file_similarity, + self.ip, + self.user, + self.problem, + self.event_team, + self.submission, + self.submission_file, + ) + ) @property def event(self): diff --git a/ramp-database/ramp_database/model/workflow.py b/ramp-database/ramp_database/model/workflow.py index f4272b9ba..5c2db9318 100644 --- a/ramp-database/ramp_database/model/workflow.py +++ b/ramp-database/ramp_database/model/workflow.py @@ -8,9 +8,9 @@ from .base import Model __all__ = [ - 'Workflow', - 'WorkflowElement', - 'WorkflowElementType', + "Workflow", + "WorkflowElement", + "WorkflowElementType", ] @@ -30,7 +30,8 @@ class WorkflowElementType(Model): workflows : list of :class:`ramp_database.model.WorkflowElement` A back-reference to the workflows linked with the workflow element. """ - __tablename__ = 'workflow_element_types' + + __tablename__ = "workflow_element_types" id = Column(Integer, primary_key=True) # file name without extension @@ -38,16 +39,17 @@ class WorkflowElementType(Model): name = Column(String, nullable=False, unique=True) # eg, code, text, data - type_id = Column(Integer, ForeignKey('submission_file_types.id'), - nullable=False) - type = relationship('SubmissionFileType', - backref=backref('workflow_element_types')) + type_id = Column(Integer, ForeignKey("submission_file_types.id"), nullable=False) + type = relationship("SubmissionFileType", backref=backref("workflow_element_types")) def __repr__(self): return ( - 'WorkflowElementType(name={}, type={} is_editable={}, max_size={})' - .format(self.name, self.type.name, self.type.is_editable, - self.type.max_size) + "WorkflowElementType(name={}, type={} is_editable={}, max_size={})".format( + self.name, + self.type.name, + self.type.is_editable, + self.type.max_size, + ) ) @property @@ -92,7 +94,8 @@ class Workflow(Model): elements : list of :class:`ramp_database.model.WorkflowElement` A back-reference to the elements of the workflow. """ - __tablename__ = 'workflows' + + __tablename__ = "workflows" id = Column(Integer, primary_key=True) name = Column(String, nullable=False, unique=True) @@ -101,9 +104,9 @@ def __init__(self, name): self.name = name def __repr__(self): - text = 'Workflow({})'.format(self.name) + text = "Workflow({})".format(self.name) for workflow_element in self.elements: - text += '\n\t' + str(workflow_element) + text += "\n\t" + str(workflow_element) return text @@ -141,7 +144,8 @@ class WorkflowElement(Model): A back-reference to the submission file associated with the workflow element. """ - __tablename__ = 'workflow_elements' + + __tablename__ = "workflow_elements" id = Column(Integer, primary_key=True) # Normally name will be the same as workflow_element_type.type.name, @@ -150,24 +154,27 @@ class WorkflowElement(Model): # refer to workflow_element_type.type.name name = Column(String, nullable=False) - workflow_id = Column(Integer, ForeignKey('workflows.id')) - workflow = relationship('Workflow', backref=backref('elements')) + workflow_id = Column(Integer, ForeignKey("workflows.id")) + workflow = relationship("Workflow", backref=backref("elements")) - workflow_element_type_id = Column(Integer, - ForeignKey('workflow_element_types.id'), - nullable=False) - workflow_element_type = relationship('WorkflowElementType', - backref=backref('workflows')) + workflow_element_type_id = Column( + Integer, ForeignKey("workflow_element_types.id"), nullable=False + ) + workflow_element_type = relationship( + "WorkflowElementType", backref=backref("workflows") + ) def __init__(self, workflow, workflow_element_type, name_in_workflow=None): self.workflow = workflow self.workflow_element_type = workflow_element_type - self.name = (self.workflow_element_type.name - if name_in_workflow is None else name_in_workflow) + self.name = ( + self.workflow_element_type.name + if name_in_workflow is None + else name_in_workflow + ) def __repr__(self): - return 'Workflow({}): WorkflowElement({})'.format(self.workflow.name, - self.name) + return "Workflow({}): WorkflowElement({})".format(self.workflow.name, self.name) @property def type(self): diff --git a/ramp-database/ramp_database/testing.py b/ramp-database/ramp_database/testing.py index da62e3c95..3c8c0091e 100644 --- a/ramp-database/ramp_database/testing.py +++ b/ramp-database/ramp_database/testing.py @@ -30,7 +30,7 @@ from .tools.team import sign_up_team from .tools.submission import submit_starting_kits -logger = logging.getLogger('RAMP-DATABASE') +logger = logging.getLogger("RAMP-DATABASE") HERE = os.path.dirname(__file__) @@ -53,7 +53,7 @@ def create_test_db(database_config, ramp_config): deployment_dir : str The deployment directory for the RAMP components (kits, data, etc.). """ - database_config = database_config['sqlalchemy'] + database_config = database_config["sqlalchemy"] # we can automatically setup the database from the config file used for the # tests. ramp_config = generate_ramp_config(read_config(ramp_config)) @@ -61,11 +61,11 @@ def create_test_db(database_config, ramp_config): # FIXME: we are recreating the deployment directory but it should be # replaced by an temporary creation of folder. deployment_dir = os.path.commonpath( - [ramp_config['ramp_kit_dir'], ramp_config['ramp_data_dir']] + [ramp_config["ramp_kit_dir"], ramp_config["ramp_data_dir"]] ) shutil.rmtree(deployment_dir, ignore_errors=True) - os.makedirs(ramp_config['ramp_submissions_dir']) + os.makedirs(ramp_config["ramp_submissions_dir"]) db, _ = setup_db(database_config) Model.metadata.drop_all(db) Model.metadata.create_all(db) @@ -90,7 +90,7 @@ def create_toy_db(database_config, ramp_config): The deployment directory for the RAMP components (kits, data, etc.). """ deployment_dir = create_test_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: setup_toy_db(session) return deployment_dir @@ -123,8 +123,13 @@ def _delete_line_from_file(f_name, line_to_delete): f.truncate() -def setup_ramp_kit_ramp_data(ramp_config, problem_name, force=False, - depth=None, mock_html_conversion=False): +def setup_ramp_kit_ramp_data( + ramp_config, + problem_name, + force=False, + depth=None, + mock_html_conversion=False, +): """Clone ramp-kit and ramp-data repository and setup it up. Parameters @@ -145,29 +150,29 @@ def setup_ramp_kit_ramp_data(ramp_config, problem_name, force=False, Whether we should call `nbconvert` to create the HTML notebook. If `True`, the file created will be an almost empty html file. """ - problem_kit_path = ramp_config['ramp_kit_dir'] + problem_kit_path = ramp_config["ramp_kit_dir"] if os.path.exists(problem_kit_path): if not force: raise ValueError( - 'The RAMP kit repository was previously cloned. To replace ' + "The RAMP kit repository was previously cloned. To replace " 'it, you need to set "force=True".' ) shutil.rmtree(problem_kit_path, ignore_errors=True) - ramp_kit_url = 'https://github.com/ramp-kits/{}.git'.format(problem_name) + ramp_kit_url = "https://github.com/ramp-kits/{}.git".format(problem_name) kwargs = {} if depth is not None: - kwargs['depth'] = depth + kwargs["depth"] = depth Repo.clone_from(ramp_kit_url, problem_kit_path, **kwargs) - problem_data_path = ramp_config['ramp_data_dir'] + problem_data_path = ramp_config["ramp_data_dir"] if os.path.exists(problem_data_path): if not force: raise ValueError( - 'The RAMP data repository was previously cloned. To replace ' + "The RAMP data repository was previously cloned. To replace " 'it, you need to set "force=True".' ) shutil.rmtree(problem_data_path, ignore_errors=True) - ramp_data_url = 'https://github.com/ramp-data/{}.git'.format(problem_name) + ramp_data_url = "https://github.com/ramp-data/{}.git".format(problem_name) Repo.clone_from(ramp_data_url, problem_data_path, **kwargs) current_directory = os.getcwd() @@ -177,17 +182,19 @@ def setup_ramp_kit_ramp_data(ramp_config, problem_name, force=False, filename_notebook_ipynb = "{}_starting_kit.ipynb".format(problem_name) filename_notebook_html = "{}_starting_kit.html".format(problem_name) if not mock_html_conversion: - subprocess.check_output([ - "jupyter", "nbconvert", "--to", "html", filename_notebook_ipynb - ]) + subprocess.check_output( + ["jupyter", "nbconvert", "--to", "html", filename_notebook_ipynb] + ) # delete this line since it trigger in the front-end # (try to open execute "custom.css".) - _delete_line_from_file(filename_notebook_html, - '\n') + _delete_line_from_file( + filename_notebook_html, + '\n', + ) else: # create an almost empty html file filename = os.path.join(problem_kit_path, filename_notebook_html) - with open(filename, mode='w+b') as f: + with open(filename, mode="w+b") as f: f.write(b"RAMP on iris") os.chdir(current_directory) @@ -204,23 +211,23 @@ def setup_files_extension_type(session): session : :class:`sqlalchemy.orm.Session` The session to directly perform the operation on the database. """ - extension_names = ['py', 'R', 'txt', 'csv'] + extension_names = ["py", "R", "txt", "csv"] for name in extension_names: add_extension(session, name) submission_file_types = [ - ('code', True, 10 ** 5), - ('text', True, 10 ** 5), - ('data', False, 10 ** 8) + ("code", True, 10 ** 5), + ("text", True, 10 ** 5), + ("data", False, 10 ** 8), ] for name, is_editable, max_size in submission_file_types: add_submission_file_type(session, name, is_editable, max_size) submission_file_type_extensions = [ - ('code', 'py'), - ('code', 'R'), - ('text', 'txt'), - ('data', 'csv') + ("code", "py"), + ("code", "R"), + ("text", "txt"), + ("data", "csv"), ] for type_name, extension_name in submission_file_type_extensions: add_submission_file_type_extension(session, type_name, extension_name) @@ -236,19 +243,34 @@ def add_users(session): The session to directly perform the operation on the database. """ add_user( - session, name='test_user', password='test', - lastname='Test', firstname='User', - email='test.user@gmail.com', access_level='asked') - approve_user(session, 'test_user') + session, + name="test_user", + password="test", + lastname="Test", + firstname="User", + email="test.user@gmail.com", + access_level="asked", + ) + approve_user(session, "test_user") add_user( - session, name='test_user_2', password='test', - lastname='Test_2', firstname='User_2', - email='test.user.2@gmail.com', access_level='user') - approve_user(session, 'test_user_2') + session, + name="test_user_2", + password="test", + lastname="Test_2", + firstname="User_2", + email="test.user.2@gmail.com", + access_level="user", + ) + approve_user(session, "test_user_2") add_user( - session, name='test_iris_admin', password='test', - lastname='Admin', firstname='Iris', - email='iris.admin@gmail.com', access_level='admin') + session, + name="test_iris_admin", + password="test", + lastname="Admin", + firstname="Iris", + email="iris.admin@gmail.com", + access_level="admin", + ) def add_problems(session): @@ -261,26 +283,38 @@ def add_problems(session): The session to directly perform the operation on the database. """ ramp_configs = { - 'iris': read_config(ramp_config_iris()), - 'boston_housing': read_config(ramp_config_boston_housing()) + "iris": read_config(ramp_config_iris()), + "boston_housing": read_config(ramp_config_boston_housing()), } for problem_name, ramp_config in ramp_configs.items(): internal_ramp_config = generate_ramp_config(ramp_config) setup_ramp_kit_ramp_data( - internal_ramp_config, problem_name, depth=1, - mock_html_conversion=True + internal_ramp_config, + problem_name, + depth=1, + mock_html_conversion=True, + ) + add_problem( + session, + problem_name, + internal_ramp_config["ramp_kit_dir"], + internal_ramp_config["ramp_data_dir"], + ) + add_keyword(session, problem_name, "data_domain", category="scientific data") + add_problem_keyword( + session, problem_name=problem_name, keyword_name=problem_name + ) + add_keyword( + session, + problem_name + "_theme", + "data_science_theme", + category="classification", + ) + add_problem_keyword( + session, + problem_name=problem_name, + keyword_name=problem_name + "_theme", ) - add_problem(session, problem_name, - internal_ramp_config['ramp_kit_dir'], - internal_ramp_config['ramp_data_dir']) - add_keyword(session, problem_name, 'data_domain', - category='scientific data') - add_problem_keyword(session, problem_name=problem_name, - keyword_name=problem_name) - add_keyword(session, problem_name + '_theme', 'data_science_theme', - category='classification') - add_problem_keyword(session, problem_name=problem_name, - keyword_name=problem_name + '_theme') def add_events(session): @@ -296,28 +330,31 @@ def add_events(session): Be aware that :func:`add_problems` needs to be called before. """ ramp_configs = { - 'iris': read_config(ramp_config_iris()), - 'iris_aws': read_config(ramp_config_aws_iris()), - 'boston_housing': read_config(ramp_config_boston_housing()) + "iris": read_config(ramp_config_iris()), + "iris_aws": read_config(ramp_config_aws_iris()), + "boston_housing": read_config(ramp_config_boston_housing()), } for problem_name, ramp_config in ramp_configs.items(): ramp_config_problem = generate_ramp_config(ramp_config) add_event( - session, problem_name=ramp_config_problem['problem_name'], - event_name=ramp_config_problem['event_name'], - event_title=ramp_config_problem['event_title'], - ramp_sandbox_name=ramp_config_problem['sandbox_name'], - ramp_submissions_path=ramp_config_problem['ramp_submissions_dir'], - is_public=True, force=False + session, + problem_name=ramp_config_problem["problem_name"], + event_name=ramp_config_problem["event_name"], + event_title=ramp_config_problem["event_title"], + ramp_sandbox_name=ramp_config_problem["sandbox_name"], + ramp_submissions_path=ramp_config_problem["ramp_submissions_dir"], + is_public=True, + force=False, ) # create an empty event archive archive_dir = os.path.join( - ramp_config_problem['ramp_kit_dir'], 'events_archived', + ramp_config_problem["ramp_kit_dir"], + "events_archived", ) if not os.path.isdir(archive_dir): os.makedirs(archive_dir) archive_file = os.path.join( - archive_dir, ramp_config_problem['event_name'] + '.zip' + archive_dir, ramp_config_problem["event_name"] + ".zip" ) Path(archive_file).touch() @@ -335,9 +372,9 @@ def sign_up_teams_to_events(session): Be aware that :func:`add_users`, :func:`add_problems`, and :func:`add_events` need to be called before. """ - for event_name in ['iris_test', 'iris_aws_test', 'boston_housing_test']: - sign_up_team(session, event_name, 'test_user') - sign_up_team(session, event_name, 'test_user_2') + for event_name in ["iris_test", "iris_aws_test", "boston_housing_test"]: + sign_up_team(session, event_name, "test_user") + sign_up_team(session, event_name, "test_user_2") def submit_all_starting_kits(session): @@ -349,22 +386,26 @@ def submit_all_starting_kits(session): The session to directly perform the operation on the database. """ ramp_configs = { - 'iris': read_config(ramp_config_iris()), - 'iris_aws': read_config(ramp_config_aws_iris()), - 'boston_housing': read_config(ramp_config_boston_housing()) + "iris": read_config(ramp_config_iris()), + "iris_aws": read_config(ramp_config_aws_iris()), + "boston_housing": read_config(ramp_config_boston_housing()), } for problem_name, ramp_config in ramp_configs.items(): ramp_config_problem = generate_ramp_config(ramp_config) path_submissions = os.path.join( - ramp_config_problem['ramp_kit_dir'], 'submissions' + ramp_config_problem["ramp_kit_dir"], "submissions" ) submit_starting_kits( - session, ramp_config_problem['event_name'], 'test_user', - path_submissions + session, + ramp_config_problem["event_name"], + "test_user", + path_submissions, ) submit_starting_kits( - session, ramp_config_problem['event_name'], 'test_user_2', - path_submissions + session, + ramp_config_problem["event_name"], + "test_user_2", + path_submissions, ) @@ -376,7 +417,7 @@ def ramp_config_aws_iris(): filename : str The RAMP configuration filename for the iris kit. """ - return os.path.join(HERE, 'tests', 'data', 'ramp_config_aws_iris.yml') + return os.path.join(HERE, "tests", "data", "ramp_config_aws_iris.yml") def ramp_config_iris(): @@ -387,7 +428,7 @@ def ramp_config_iris(): filename : str The RAMP configuration filename for the iris kit. """ - return os.path.join(HERE, 'tests', 'data', 'ramp_config_iris.yml') + return os.path.join(HERE, "tests", "data", "ramp_config_iris.yml") def ramp_config_boston_housing(): @@ -398,6 +439,4 @@ def ramp_config_boston_housing(): filename : str The RAMP configuration filename for the boston housing kit. """ - return os.path.join( - HERE, 'tests', 'data', 'ramp_config_boston_housing.yml' - ) + return os.path.join(HERE, "tests", "data", "ramp_config_boston_housing.yml") diff --git a/ramp-database/ramp_database/tests/test_cli.py b/ramp-database/ramp_database/tests/test_cli.py index c37e724ba..34688dd44 100644 --- a/ramp-database/ramp_database/tests/test_cli.py +++ b/ramp-database/ramp_database/tests/test_cli.py @@ -29,97 +29,172 @@ def make_toy_db(database_connection): yield finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_add_user(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['add-user', - '--config', database_config_template(), - '--login', 'glemaitre', - '--password', 'xxx', - '--lastname', 'xxx', - '--firstname', 'xxx', - '--email', 'xxx', - '--access-level', 'user'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "add-user", + "--config", + database_config_template(), + "--login", + "glemaitre", + "--password", + "xxx", + "--lastname", + "xxx", + "--firstname", + "xxx", + "--email", + "xxx", + "--access-level", + "user", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_delete_user(make_toy_db): runner = CliRunner() - runner.invoke(main, ['add-user', - '--config', database_config_template(), - '--login', 'yyy', - '--password', 'yyy', - '--lastname', 'yyy', - '--firstname', 'yyy', - '--email', 'yyy', - '--access-level', 'user'], - catch_exceptions=False) - result = runner.invoke(main, ['delete-user', - '--config', database_config_template(), - '--login', 'yyy'], - catch_exceptions=False) + runner.invoke( + main, + [ + "add-user", + "--config", + database_config_template(), + "--login", + "yyy", + "--password", + "yyy", + "--lastname", + "yyy", + "--firstname", + "yyy", + "--email", + "yyy", + "--access-level", + "user", + ], + catch_exceptions=False, + ) + result = runner.invoke( + main, + [ + "delete-user", + "--config", + database_config_template(), + "--login", + "yyy", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_approve_user(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['approve-user', - '--config', database_config_template(), - '--login', 'glemaitre'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "approve-user", + "--config", + database_config_template(), + "--login", + "glemaitre", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_make_user_admin(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['make-user-admin', - '--config', database_config_template(), - '--login', 'glemaitre'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "make-user-admin", + "--config", + database_config_template(), + "--login", + "glemaitre", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_set_user_access_level(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['set-user-access-level', - '--config', database_config_template(), - '--login', 'glemaitre', - '--access-level', 'admin'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "set-user-access-level", + "--config", + database_config_template(), + "--login", + "glemaitre", + "--access-level", + "admin", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_add_problem(make_toy_db): runner = CliRunner() ramp_config = generate_ramp_config(read_config(ramp_config_template())) - result = runner.invoke(main, ['add-problem', - '--config', database_config_template(), - '--problem', 'iris', - '--kit-dir', ramp_config['ramp_kit_dir'], - '--data-dir', ramp_config['ramp_data_dir'], - '--force', True], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "add-problem", + "--config", + database_config_template(), + "--problem", + "iris", + "--kit-dir", + ramp_config["ramp_kit_dir"], + "--data-dir", + ramp_config["ramp_data_dir"], + "--force", + True, + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_add_event(make_toy_db): runner = CliRunner() ramp_config = generate_ramp_config(read_config(ramp_config_template())) - result = runner.invoke(main, ['add-event', - '--config', database_config_template(), - '--problem', 'iris', - '--event', 'iris_test', - '--title', 'Iris classification', - '--sandbox', ramp_config['sandbox_name'], - '--submissions-dir', - ramp_config['ramp_submissions_dir'], - '--is-public', False, - '--force', True], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "add-event", + "--config", + database_config_template(), + "--problem", + "iris", + "--event", + "iris_test", + "--title", + "Iris classification", + "--sandbox", + ramp_config["sandbox_name"], + "--submissions-dir", + ramp_config["ramp_submissions_dir"], + "--is-public", + False, + "--force", + True, + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output @@ -129,12 +204,16 @@ def test_delete_event_error(make_toy_db, from_disk, force): runner = CliRunner() with pytest.raises(FileNotFoundError): - cmd = ('delete-event --config ' + database_config_template() + - ' --config-event ' + "random") + cmd = ( + "delete-event --config " + + database_config_template() + + " --config-event " + + "random" + ) if from_disk: - cmd += ' --from-disk' + cmd += " --from-disk" if force: - cmd += ' --force' + cmd += " --force" runner.invoke(main, cmd, catch_exceptions=False) @@ -144,32 +223,51 @@ def test_delete_event_only_files(make_toy_db): # create the event folder ramp_config = read_config(ramp_config_template()) - ramp_config['ramp']['event_name'] = 'iris_test2' - deployment_dir = os.path.commonpath([ramp_config['ramp']['kit_dir'], - ramp_config['ramp']['data_dir']]) - runner.invoke(main_utils, ['init-event', - '--name', 'iris_test2', - '--deployment-dir', deployment_dir]) + ramp_config["ramp"]["event_name"] = "iris_test2" + deployment_dir = os.path.commonpath( + [ramp_config["ramp"]["kit_dir"], ramp_config["ramp"]["data_dir"]] + ) + runner.invoke( + main_utils, + [ + "init-event", + "--name", + "iris_test2", + "--deployment-dir", + deployment_dir, + ], + ) event_config = os.path.join( - deployment_dir, 'events', ramp_config['ramp']['event_name'], - 'config.yml' + deployment_dir, + "events", + ramp_config["ramp"]["event_name"], + "config.yml", ) - with open(event_config, 'w+') as f: + with open(event_config, "w+") as f: yaml.dump(ramp_config, f) # check that --from-disk will raise an error - cmd = ['delete-event', - '--config', database_config_template(), - '--config-event', event_config, - '--from-disk'] + cmd = [ + "delete-event", + "--config", + database_config_template(), + "--config-event", + event_config, + "--from-disk", + ] result = runner.invoke(main, cmd) assert result.exit_code == 1 assert 'add the option "--force"' in result.output - cmd = ['delete-event', - '--config', database_config_template(), - '--config-event', event_config, - '--from-disk', '--force'] + cmd = [ + "delete-event", + "--config", + database_config_template(), + "--config-event", + event_config, + "--from-disk", + "--force", + ] result = runner.invoke(main, cmd) assert result.exit_code == 0, result.output assert not os.path.exists(os.path.dirname(event_config)) @@ -183,86 +281,124 @@ def test_delete_event(make_toy_db, from_disk): # deploy a new event named `iris_test2` ramp_config = read_config(ramp_config_template()) - ramp_config['ramp']['event_name'] = 'iris_test2' - deployment_dir = os.path.commonpath([ramp_config['ramp']['kit_dir'], - ramp_config['ramp']['data_dir']]) - runner.invoke(main_utils, ['init-event', - '--name', 'iris_test2', - '--deployment-dir', deployment_dir]) + ramp_config["ramp"]["event_name"] = "iris_test2" + deployment_dir = os.path.commonpath( + [ramp_config["ramp"]["kit_dir"], ramp_config["ramp"]["data_dir"]] + ) + runner.invoke( + main_utils, + [ + "init-event", + "--name", + "iris_test2", + "--deployment-dir", + deployment_dir, + ], + ) event_config = os.path.join( - deployment_dir, 'events', ramp_config['ramp']['event_name'], - 'config.yml' + deployment_dir, + "events", + ramp_config["ramp"]["event_name"], + "config.yml", ) - with open(event_config, 'w+') as f: + with open(event_config, "w+") as f: yaml.dump(ramp_config, f) - result = runner.invoke(main_utils, ['deploy-event', - '--config', - database_config_template(), - '--event-config', - event_config, - '--no-cloning']) - - cmd = ['delete-event', - '--config', database_config_template(), - '--config-event', event_config] + result = runner.invoke( + main_utils, + [ + "deploy-event", + "--config", + database_config_template(), + "--event-config", + event_config, + "--no-cloning", + ], + ) + + cmd = [ + "delete-event", + "--config", + database_config_template(), + "--config-event", + event_config, + ] if from_disk: - cmd.append('--from-disk') + cmd.append("--from-disk") result = runner.invoke(main, cmd) assert result.exit_code == 0, result.output event_path = os.path.dirname(event_config) - assert (os.path.exists(event_path) if not from_disk - else not os.path.exists(event_path)) + assert ( + os.path.exists(event_path) if not from_disk else not os.path.exists(event_path) + ) @pytest.mark.parametrize("force", [True, False]) @pytest.mark.parametrize("add_to_db", [True, False]) -def test_delete_predictions(make_toy_db, database_connection, force, - add_to_db): +def test_delete_predictions(make_toy_db, database_connection, force, add_to_db): # check that delete event is removed from the database and optionally from # the disk runner = CliRunner() ramp_config = read_config(ramp_config_template()) - ramp_config['ramp']['event_name'] = 'iris_test2' - deployment_dir = os.path.commonpath([ramp_config['ramp']['kit_dir'], - ramp_config['ramp']['data_dir']]) + ramp_config["ramp"]["event_name"] = "iris_test2" + deployment_dir = os.path.commonpath( + [ramp_config["ramp"]["kit_dir"], ramp_config["ramp"]["data_dir"]] + ) event_config = os.path.join( - deployment_dir, 'events', ramp_config['ramp']['event_name'], - 'config.yml' + deployment_dir, + "events", + ramp_config["ramp"]["event_name"], + "config.yml", ) if add_to_db: # deploy a new event named `iris_test2` - runner.invoke(main_utils, ['init-event', - '--name', 'iris_test2', - '--deployment-dir', deployment_dir]) - - with open(event_config, 'w+') as f: + runner.invoke( + main_utils, + [ + "init-event", + "--name", + "iris_test2", + "--deployment-dir", + deployment_dir, + ], + ) + + with open(event_config, "w+") as f: yaml.dump(ramp_config, f) - result = runner.invoke(main_utils, ['deploy-event', - '--config', - database_config_template(), - '--event-config', - event_config, - '--no-cloning']) + result = runner.invoke( + main_utils, + [ + "deploy-event", + "--config", + database_config_template(), + "--event-config", + event_config, + "--no-cloning", + ], + ) # add the directory for predictions - predictions_dir = ramp_config['ramp']['predictions_dir'] + predictions_dir = ramp_config["ramp"]["predictions_dir"] os.mkdir(predictions_dir) assert os.path.exists(predictions_dir) - cmd = ['delete-predictions', - '--config', database_config_template(), - '--config-event', event_config] + cmd = [ + "delete-predictions", + "--config", + database_config_template(), + "--config-event", + event_config, + ] if force: - cmd.append('--force') + cmd.append("--force") result = runner.invoke(main, cmd) if not add_to_db and not force: assert result.exit_code == 1 - assert 'use the option' in result.output + assert "use the option" in result.output assert os.path.exists(predictions_dir) else: assert result.exit_code == 0, result.output @@ -271,9 +407,13 @@ def test_delete_predictions(make_toy_db, database_connection, force, # clean up if add_to_db: # remove event from the db - cmd = ['delete-event', - '--config', database_config_template(), - '--config-event', event_config] + cmd = [ + "delete-event", + "--config", + database_config_template(), + "--config-event", + event_config, + ] result = runner.invoke(main, cmd) if os.path.exists(predictions_dir): @@ -283,137 +423,235 @@ def test_delete_predictions(make_toy_db, database_connection, force, def test_sign_up_team(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['sign-up-team', - '--config', database_config_template(), - '--event', 'iris_test', - '--team', 'glemaitre'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "sign-up-team", + "--config", + database_config_template(), + "--event", + "iris_test", + "--team", + "glemaitre", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_add_event_admin(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['add-event-admin', - '--config', database_config_template(), - '--event', 'iris_test', - '--user', 'glemaitre'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "add-event-admin", + "--config", + database_config_template(), + "--event", + "iris_test", + "--user", + "glemaitre", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_add_submission(make_toy_db): ramp_config = generate_ramp_config(read_config(ramp_config_template())) - submission_name = 'new_submission' - submission_path = os.path.join(ramp_config['ramp_kit_submissions_dir'], - submission_name) + submission_name = "new_submission" + submission_path = os.path.join( + ramp_config["ramp_kit_submissions_dir"], submission_name + ) shutil.copytree( - os.path.join(ramp_config['ramp_kit_submissions_dir'], - 'random_forest_10_10'), - submission_path + os.path.join(ramp_config["ramp_kit_submissions_dir"], "random_forest_10_10"), + submission_path, ) runner = CliRunner() - result = runner.invoke(main, ['add-submission', - '--config', database_config_template(), - '--event', 'iris_test', - '--team', 'glemaitre', - '--submission', submission_name, - '--path', submission_path], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "add-submission", + "--config", + database_config_template(), + "--event", + "iris_test", + "--team", + "glemaitre", + "--submission", + submission_name, + "--path", + submission_path, + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_get_submission_by_state(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['get-submissions-by-state', - '--config', database_config_template(), - '--event', 'boston_housing_test', - '--state', 'new'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "get-submissions-by-state", + "--config", + database_config_template(), + "--event", + "boston_housing_test", + "--state", + "new", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output assert "ID" in result.output - result = runner.invoke(main, ['get-submissions-by-state', - '--config', database_config_template(), - '--event', 'iris_test', - '--state', 'scored'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "get-submissions-by-state", + "--config", + database_config_template(), + "--event", + "iris_test", + "--state", + "scored", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output - assert 'No submission for this event and this state' in result.output + assert "No submission for this event and this state" in result.output def test_set_submission_state(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['set-submission-state', - '--config', database_config_template(), - '--submission-id', 5, - '--state', 'scored'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "set-submission-state", + "--config", + database_config_template(), + "--submission-id", + 5, + "--state", + "scored", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_update_leaderboards(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['update-leaderboards', - '--config', database_config_template(), - '--event', 'iris_test'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "update-leaderboards", + "--config", + database_config_template(), + "--event", + "iris_test", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_update_user_leaderboards(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['update-user-leaderboards', - '--config', database_config_template(), - '--event', 'iris_test', - '--user', 'glemaitre'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "update-user-leaderboards", + "--config", + database_config_template(), + "--event", + "iris_test", + "--user", + "glemaitre", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_update_all_user_leaderboards(make_toy_db): runner = CliRunner() - result = runner.invoke(main, ['update-all-users-leaderboards', - '--config', database_config_template(), - '--event', 'iris_test'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "update-all-users-leaderboards", + "--config", + database_config_template(), + "--event", + "iris_test", + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_compute_contributivity(make_toy_db): runner = CliRunner() ramp_config = read_config(ramp_config_template()) - deployment_dir = os.path.commonpath([ramp_config['ramp']['kit_dir'], - ramp_config['ramp']['data_dir']]) + deployment_dir = os.path.commonpath( + [ramp_config["ramp"]["kit_dir"], ramp_config["ramp"]["data_dir"]] + ) event_config = os.path.join( - deployment_dir, 'events', ramp_config['ramp']['event_name'], - 'config.yml' + deployment_dir, + "events", + ramp_config["ramp"]["event_name"], + "config.yml", ) # Create the event config on disk - runner.invoke(main_utils, ['init-event', - '--name', ramp_config['ramp']['event_name'], - '--deployment-dir', deployment_dir]) + runner.invoke( + main_utils, + [ + "init-event", + "--name", + ramp_config["ramp"]["event_name"], + "--deployment-dir", + deployment_dir, + ], + ) event_config = os.path.join( - deployment_dir, 'events', ramp_config['ramp']['event_name'], - 'config.yml' + deployment_dir, + "events", + ramp_config["ramp"]["event_name"], + "config.yml", ) - with open(event_config, 'w+') as f: + with open(event_config, "w+") as f: yaml.dump(ramp_config, f) result = runner.invoke( - main, ['compute-contributivity', - '--config', database_config_template(), - '--config-event', event_config], - catch_exceptions=False) + main, + [ + "compute-contributivity", + "--config", + database_config_template(), + "--config-event", + event_config, + ], + catch_exceptions=False, + ) assert result.exit_code == 0, result.output def test_export_submissions(make_toy_db): # when there are no submissions to save runner = CliRunner() - result = runner.invoke(main, ['export-leaderboards', - '--config', database_config_template(), - '--event', 'iris_test', - '--path', 'test.csv'], - catch_exceptions=False) + result = runner.invoke( + main, + [ + "export-leaderboards", + "--config", + database_config_template(), + "--event", + "iris_test", + "--path", + "test.csv", + ], + catch_exceptions=False, + ) assert result.exit_code == 0 - assert 'No score was found on the' in result.output + assert "No score was found on the" in result.output diff --git a/ramp-database/ramp_database/tests/test_exceptions.py b/ramp-database/ramp_database/tests/test_exceptions.py index ff12ee211..86906e103 100644 --- a/ramp-database/ramp_database/tests/test_exceptions.py +++ b/ramp-database/ramp_database/tests/test_exceptions.py @@ -11,13 +11,15 @@ @pytest.mark.parametrize( "ExceptionClass", - [DuplicateSubmissionError, - MergeTeamError, - MissingExtensionError, - MissingSubmissionFileError, - NameClashError, - TooEarlySubmissionError, - UnknownStateError] + [ + DuplicateSubmissionError, + MergeTeamError, + MissingExtensionError, + MissingSubmissionFileError, + NameClashError, + TooEarlySubmissionError, + UnknownStateError, + ], ) def test_exceptions(ExceptionClass): with pytest.raises(ExceptionClass): @@ -26,28 +28,32 @@ def test_exceptions(ExceptionClass): @pytest.mark.parametrize( "ExceptionClass", - [DuplicateSubmissionError, - MergeTeamError, - MissingExtensionError, - MissingSubmissionFileError, - NameClashError, - TooEarlySubmissionError, - UnknownStateError] + [ + DuplicateSubmissionError, + MergeTeamError, + MissingExtensionError, + MissingSubmissionFileError, + NameClashError, + TooEarlySubmissionError, + UnknownStateError, + ], ) def test_exceptions_msg(ExceptionClass): - with pytest.raises(ExceptionClass, match='Some error message'): - raise ExceptionClass('Some error message') + with pytest.raises(ExceptionClass, match="Some error message"): + raise ExceptionClass("Some error message") @pytest.mark.parametrize( "ExceptionClass", - [DuplicateSubmissionError, - MergeTeamError, - MissingExtensionError, - MissingSubmissionFileError, - NameClashError, - TooEarlySubmissionError, - UnknownStateError] + [ + DuplicateSubmissionError, + MergeTeamError, + MissingExtensionError, + MissingSubmissionFileError, + NameClashError, + TooEarlySubmissionError, + UnknownStateError, + ], ) def test_exceptions_obj(ExceptionClass): class DummyObject: diff --git a/ramp-database/ramp_database/tests/test_testing.py b/ramp-database/ramp_database/tests/test_testing.py index 177e25918..bd79fb647 100644 --- a/ramp-database/ramp_database/tests/test_testing.py +++ b/ramp-database/ramp_database/tests/test_testing.py @@ -30,12 +30,12 @@ from ramp_database.testing import submit_all_starting_kits -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def database_config(): return read_config(database_config_template()) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def ramp_config(): return ramp_config_template() @@ -44,34 +44,34 @@ def ramp_config(): def session_scope_function(database_config, ramp_config, database_connection): try: deployment_dir = create_test_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_ramp_kit_ramp_data(session_scope_function, ramp_config): internal_ramp_config = generate_ramp_config(read_config(ramp_config)) setup_ramp_kit_ramp_data( - internal_ramp_config, 'iris', depth=1, mock_html_conversion=True + internal_ramp_config, "iris", depth=1, mock_html_conversion=True ) - msg_err = 'The RAMP kit repository was previously cloned.' + msg_err = "The RAMP kit repository was previously cloned." with pytest.raises(ValueError, match=msg_err): setup_ramp_kit_ramp_data( - internal_ramp_config, 'iris', depth=1, mock_html_conversion=True + internal_ramp_config, "iris", depth=1, mock_html_conversion=True ) # retrieve the path to the ramp kit to remove it - shutil.rmtree(internal_ramp_config['ramp_kit_dir']) - msg_err = 'The RAMP data repository was previously cloned.' + shutil.rmtree(internal_ramp_config["ramp_kit_dir"]) + msg_err = "The RAMP data repository was previously cloned." with pytest.raises(ValueError, match=msg_err): setup_ramp_kit_ramp_data( - internal_ramp_config, 'iris', depth=1, mock_html_conversion=True + internal_ramp_config, "iris", depth=1, mock_html_conversion=True ) setup_ramp_kit_ramp_data( - internal_ramp_config, 'iris', force=True, mock_html_conversion=True + internal_ramp_config, "iris", force=True, mock_html_conversion=True ) @@ -79,8 +79,8 @@ def test_add_users(session_scope_function): add_users(session_scope_function) users = get_user_by_name(session_scope_function, None) for user in users: - assert user.name in ('test_user', 'test_user_2', 'test_iris_admin') - err_msg = 'username is already in use' + assert user.name in ("test_user", "test_user_2", "test_iris_admin") + err_msg = "username is already in use" with pytest.raises(NameClashError, match=err_msg): add_users(session_scope_function) @@ -89,10 +89,10 @@ def test_add_problems(session_scope_function): add_problems(session_scope_function) problems = get_problem(session_scope_function, None) for problem in problems: - assert problem.name in ('iris', 'boston_housing') + assert problem.name in ("iris", "boston_housing") # trying to add twice the same problem will raise a git error since the # repositories already exist. - msg_err = 'The RAMP kit repository was previously cloned.' + msg_err = "The RAMP kit repository was previously cloned." with pytest.raises(ValueError, match=msg_err): add_problems(session_scope_function) @@ -121,11 +121,10 @@ def test_submit_all_starting_kits(session_scope_function): def test_ramp_config_iris(): filename = ramp_config_iris() - assert os.path.join('tests', 'data', 'ramp_config_iris.yml') in filename + assert os.path.join("tests", "data", "ramp_config_iris.yml") in filename def test_ramp_config_boston_housing(): filename = ramp_config_boston_housing() - expected_path = os.path.join('tests', 'data', - 'ramp_config_boston_housing.yml') + expected_path = os.path.join("tests", "data", "ramp_config_boston_housing.yml") assert expected_path in filename diff --git a/ramp-database/ramp_database/tests/test_utils.py b/ramp-database/ramp_database/tests/test_utils.py index cd129a8be..6807b00f3 100644 --- a/ramp-database/ramp_database/tests/test_utils.py +++ b/ramp-database/ramp_database/tests/test_utils.py @@ -26,13 +26,13 @@ def database(database_connection): yield finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_setup_db(database): database_config = read_config( - database_config_template(), filter_section='sqlalchemy' + database_config_template(), filter_section="sqlalchemy" ) db, Session = setup_db(database_config) with db.connect() as conn: @@ -43,7 +43,7 @@ def test_setup_db(database): def test_session_scope(database): database_config = read_config( - database_config_template(), filter_section='sqlalchemy' + database_config_template(), filter_section="sqlalchemy" ) with session_scope(database_config) as session: file_type = session.query(SubmissionFileType).all() diff --git a/ramp-database/ramp_database/tools/_query.py b/ramp-database/ramp_database/tools/_query.py index 80697b098..e60609a12 100644 --- a/ramp-database/ramp_database/tools/_query.py +++ b/ramp-database/ramp_database/tools/_query.py @@ -36,11 +36,13 @@ def select_submissions_by_state(session, event_name, state): submissions : list of :class:`ramp_database.model.Submission` The queried list of submissions. """ - q = (session.query(Submission) - .filter(Event.name == event_name) - .filter(Event.id == EventTeam.event_id) - .filter(EventTeam.id == Submission.event_team_id) - .order_by(Submission.submission_timestamp)) + q = ( + session.query(Submission) + .filter(Event.name == event_name) + .filter(Event.id == EventTeam.event_id) + .filter(EventTeam.id == Submission.event_team_id) + .order_by(Submission.submission_timestamp) + ) if state is None: return q.all() return q.filter(Submission.state == state).all() @@ -61,9 +63,7 @@ def select_submission_by_id(session, submission_id): submission : :class:`ramp_database.model.Submission` The queried submission. """ - return (session.query(Submission) - .filter(Submission.id == submission_id) - .first()) + return session.query(Submission).filter(Submission.id == submission_id).first() def select_submission_by_name(session, event_name, team_name, name): @@ -85,15 +85,17 @@ def select_submission_by_name(session, event_name, team_name, name): submission : :class:`ramp_database.model.Submission` The queried submission. """ - return (session.query(Submission) - .filter(Event.name == event_name) - .filter(Event.id == EventTeam.event_id) - .filter(Team.name == team_name) - .filter(Team.id == EventTeam.team_id) - .filter(EventTeam.id == Submission.event_team_id) - .filter(Submission.name == name) - .order_by(Submission.submission_timestamp) - .one_or_none()) + return ( + session.query(Submission) + .filter(Event.name == event_name) + .filter(Event.id == EventTeam.event_id) + .filter(Team.name == team_name) + .filter(Team.id == EventTeam.team_id) + .filter(EventTeam.id == Submission.event_team_id) + .filter(Submission.name == name) + .order_by(Submission.submission_timestamp) + .one_or_none() + ) def select_event_by_name(session, event_name): @@ -135,10 +137,12 @@ def select_event_team_by_name(session, event_name, team_name): """ event = select_event_by_name(session, event_name) team = select_team_by_name(session, team_name) - return (session.query(EventTeam) - .filter(EventTeam.event == event) - .filter(EventTeam.team == team) - .one_or_none()) + return ( + session.query(EventTeam) + .filter(EventTeam.event == event) + .filter(EventTeam.team == team) + .one_or_none() + ) def select_user_by_name(session, user_name): @@ -216,9 +220,7 @@ def select_problem_by_name(session, problem_name): """ if problem_name is None: return session.query(Problem).all() - return (session.query(Problem) - .filter(Problem.name == problem_name) - .one_or_none()) + return session.query(Problem).filter(Problem.name == problem_name).one_or_none() def select_similarities_by_target(session, target_submission): @@ -237,10 +239,11 @@ def select_similarities_by_target(session, target_submission): :class:`ramp_database.model.SubmissionSimilarity` The queried submission similarity. """ - return (session.query(SubmissionSimilarity) - .filter(SubmissionSimilarity.target_submission == - target_submission) - .all()) + return ( + session.query(SubmissionSimilarity) + .filter(SubmissionSimilarity.target_submission == target_submission) + .all() + ) def select_similarities_by_source(session, source_submission): @@ -259,10 +262,11 @@ def select_similarities_by_source(session, source_submission): :class:`ramp_database.model.SubmissionSimilarity` The queried submission similarity. """ - return (session.query(SubmissionSimilarity) - .filter(SubmissionSimilarity.source_submission == - source_submission) - .all()) + return ( + session.query(SubmissionSimilarity) + .filter(SubmissionSimilarity.source_submission == source_submission) + .all() + ) def select_workflow_by_name(session, workflow_name): @@ -282,9 +286,7 @@ def select_workflow_by_name(session, workflow_name): """ if workflow_name is None: return session.query(Workflow).all() - return (session.query(Workflow) - .filter(Workflow.name == workflow_name) - .one_or_none()) + return session.query(Workflow).filter(Workflow.name == workflow_name).one_or_none() def select_extension_by_name(session, extension_name): @@ -304,9 +306,9 @@ def select_extension_by_name(session, extension_name): """ if extension_name is None: return session.query(Extension).all() - return (session.query(Extension) - .filter(Extension.name == extension_name) - .one_or_none()) + return ( + session.query(Extension).filter(Extension.name == extension_name).one_or_none() + ) def select_submission_file_type_by_name(session, type_name): @@ -327,13 +329,14 @@ def select_submission_file_type_by_name(session, type_name): """ if type_name is None: return session.query(SubmissionFileType).all() - return (session.query(SubmissionFileType) - .filter(SubmissionFileType.name == type_name) - .one_or_none()) + return ( + session.query(SubmissionFileType) + .filter(SubmissionFileType.name == type_name) + .one_or_none() + ) -def select_submission_type_extension_by_name(session, type_name, - extension_name): +def select_submission_type_extension_by_name(session, type_name, extension_name): """Query the submission file type extension given its extension. Parameters @@ -358,8 +361,7 @@ def select_submission_type_extension_by_name(session, type_name, return session.query(SubmissionFileTypeExtension).all() q = session.query(SubmissionFileTypeExtension) if type_name is not None: - submission_file_type = select_submission_file_type_by_name(session, - type_name) + submission_file_type = select_submission_file_type_by_name(session, type_name) q = q.filter(SubmissionFileTypeExtension.type == submission_file_type) if extension_name is not None: extension = select_extension_by_name(session, extension_name) @@ -387,9 +389,11 @@ def select_submission_type_extension_by_extension(session, extension): """ if extension is None: return session.query(SubmissionFileTypeExtension).all() - return (session.query(SubmissionFileTypeExtension) - .filter(SubmissionFileTypeExtension.extension == extension) - .one_or_none()) + return ( + session.query(SubmissionFileTypeExtension) + .filter(SubmissionFileTypeExtension.extension == extension) + .one_or_none() + ) def select_workflow_element_type_by_name(session, workflow_element_type_name): @@ -407,14 +411,16 @@ def select_workflow_element_type_by_name(session, workflow_element_type_name): workflow_element_type : :class:`ramp_database.model.WorkflowElementType` The queried workflow element type. """ - return (session.query(WorkflowElementType) - .filter(WorkflowElementType.name == - workflow_element_type_name) - .one_or_none()) + return ( + session.query(WorkflowElementType) + .filter(WorkflowElementType.name == workflow_element_type_name) + .one_or_none() + ) -def select_workflow_element_by_workflow_and_type(session, workflow, - workflow_element_type): +def select_workflow_element_by_workflow_and_type( + session, workflow, workflow_element_type +): """Query the workflow element given the workflow and the workflow element type. @@ -432,11 +438,12 @@ def select_workflow_element_by_workflow_and_type(session, workflow, workflow_element : :class:`ramp_database.model.WorkflowElement` The queried workflow element. """ - return (session.query(WorkflowElement) - .filter(WorkflowElement.workflow == workflow) - .filter(WorkflowElement.workflow_element_type == - workflow_element_type) - .one_or_none()) + return ( + session.query(WorkflowElement) + .filter(WorkflowElement.workflow == workflow) + .filter(WorkflowElement.workflow_element_type == workflow_element_type) + .one_or_none() + ) def select_event_admin_by_instance(session, event, user): @@ -456,7 +463,9 @@ def select_event_admin_by_instance(session, event, user): event_admin : :class:`ramp_database.model.EventAdmin` or None The queried event/admin instance. """ - return (session.query(EventAdmin) - .filter(EventAdmin.event == event) - .filter(EventAdmin.admin == user) - .one_or_none()) + return ( + session.query(EventAdmin) + .filter(EventAdmin.event == event) + .filter(EventAdmin.admin == user) + .one_or_none() + ) diff --git a/ramp-database/ramp_database/tools/contributivity.py b/ramp-database/ramp_database/tools/contributivity.py index 82cf0b649..273cc5ac0 100644 --- a/ramp-database/ramp_database/tools/contributivity.py +++ b/ramp-database/ramp_database/tools/contributivity.py @@ -11,7 +11,7 @@ from ._query import select_submissions_by_state from ._query import select_submission_by_id -logger = logging.getLogger('RAMP-DATABASE') +logger = logging.getLogger("RAMP-DATABASE") def compute_historical_contributivity(session, event_name): @@ -25,15 +25,17 @@ def compute_historical_contributivity(session, event_name): event_name : str The event associated to the submission. """ - submissions = select_submissions_by_state( - session, event_name, state='scored') + submissions = select_submissions_by_state(session, event_name, state="scored") submissions.sort(key=lambda x: x.submission_timestamp, reverse=True) for s in submissions: s.historical_contributivity = 0.0 for s in submissions: s.historical_contributivity += s.contributivity - similarities = session.query(SubmissionSimilarity).filter_by( - type='target_credit', target_submission=s).all() + similarities = ( + session.query(SubmissionSimilarity) + .filter_by(type="target_credit", target_submission=s) + .all() + ) if similarities: # if a target team enters several credits to a source submission # we only take the latest @@ -44,16 +46,20 @@ def compute_historical_contributivity(session, event_name): source_submission = ss.source_submission if source_submission not in processed_submissions: partial_credit = historical_contributivity * ss.similarity - source_submission.historical_contributivity +=\ - partial_credit + source_submission.historical_contributivity += partial_credit s.historical_contributivity -= partial_credit processed_submissions.append(source_submission) session.commit() -def compute_contributivity(session, event_name, ramp_kit_dir, - ramp_data_dir, ramp_predictions_dir=None, - min_improvement=0.0): +def compute_contributivity( + session, + event_name, + ramp_kit_dir, + ramp_data_dir, + ramp_predictions_dir=None, + min_improvement=0.0, +): """Blend submissions of an event, compute combined score and contributivities. @@ -72,17 +78,16 @@ def compute_contributivity(session, event_name, ramp_kit_dir, min_improvement : float, default is 0.0 The minimum improvement under which greedy blender is stopped. """ - logging.basicConfig(level=logging.INFO, format='%(message)s') - logger.info('Combining models') + logging.basicConfig(level=logging.INFO, format="%(message)s") + logger.info("Combining models") event = select_event_by_name(session, event_name) ramp_submission_dir = event.path_ramp_submissions score_type = event.get_official_score_type(session) - submissions = select_submissions_by_state( - session, event_name, state='scored') + submissions = select_submissions_by_state(session, event_name, state="scored") if len(submissions) == 0: - logger.info('No submissions to blend.') + logger.info("No submissions to blend.") return # ramp-board submission folder layout is different to that of # ramp-worklow. Here we symlink @@ -90,63 +95,61 @@ def compute_contributivity(session, event_name, ramp_kit_dir, # to predictions/sumbmission_/ if it exists, in order to avoid # rescoring the model. for sub in submissions: - if (ramp_predictions_dir is None or - not Path(ramp_predictions_dir).exists()): + if ramp_predictions_dir is None or not Path(ramp_predictions_dir).exists(): continue - training_output_dir_board = ( - Path(ramp_predictions_dir) / sub.basename - ) + training_output_dir_board = Path(ramp_predictions_dir) / sub.basename training_output_dir_ramwf = ( - Path(ramp_submission_dir) / sub.basename / 'training_output' + Path(ramp_submission_dir) / sub.basename / "training_output" ) - if (not training_output_dir_ramwf.exists() - and training_output_dir_board.exists()): + if ( + not training_output_dir_ramwf.exists() + and training_output_dir_board.exists() + ): # Note: on Windows 10+ this requires to enable the Developer Mode - os.symlink(training_output_dir_board.resolve(), - training_output_dir_ramwf) + os.symlink(training_output_dir_board.resolve(), training_output_dir_ramwf) blend_submissions( - submissions=[sub.basename for sub in submissions], - ramp_kit_dir=ramp_kit_dir, - ramp_data_dir=ramp_data_dir, - ramp_submission_dir=ramp_submission_dir, - save_output=True, - min_improvement=min_improvement, + submissions=[sub.basename for sub in submissions], + ramp_kit_dir=ramp_kit_dir, + ramp_data_dir=ramp_data_dir, + ramp_submission_dir=ramp_submission_dir, + save_output=True, + min_improvement=min_improvement, ) - bsc_f_name = 'bagged_scores_combined.csv' + bsc_f_name = "bagged_scores_combined.csv" bsc_df = pd.read_csv( - os.path.join(ramp_submission_dir, 'training_output', bsc_f_name)) + os.path.join(ramp_submission_dir, "training_output", bsc_f_name) + ) n_folds = len(bsc_df) // 2 - row = (bsc_df['step'] == 'valid') & (bsc_df['n_bag'] == n_folds - 1) - event.combined_combined_valid_score =\ - bsc_df[row][score_type.name].values[0] - row = (bsc_df['step'] == 'test') & (bsc_df['n_bag'] == n_folds - 1) + row = (bsc_df["step"] == "valid") & (bsc_df["n_bag"] == n_folds - 1) + event.combined_combined_valid_score = bsc_df[row][score_type.name].values[0] + row = (bsc_df["step"] == "test") & (bsc_df["n_bag"] == n_folds - 1) event.combined_combined_test_score = bsc_df[row][score_type.name].values[0] - bsfb_f_name = 'bagged_scores_foldwise_best.csv' + bsfb_f_name = "bagged_scores_foldwise_best.csv" bsfb_df = pd.read_csv( - os.path.join(ramp_submission_dir, 'training_output', bsfb_f_name)) - row = (bsfb_df['step'] == 'valid') & (bsfb_df['n_bag'] == n_folds - 1) - event.combined_foldwise_valid_score =\ - bsfb_df[row][score_type.name].values[0] - row = (bsfb_df['step'] == 'test') & (bsfb_df['n_bag'] == n_folds - 1) - event.combined_foldwise_test_score =\ - bsfb_df[row][score_type.name].values[0] - - c_f_name = 'contributivities.csv' + os.path.join(ramp_submission_dir, "training_output", bsfb_f_name) + ) + row = (bsfb_df["step"] == "valid") & (bsfb_df["n_bag"] == n_folds - 1) + event.combined_foldwise_valid_score = bsfb_df[row][score_type.name].values[0] + row = (bsfb_df["step"] == "test") & (bsfb_df["n_bag"] == n_folds - 1) + event.combined_foldwise_test_score = bsfb_df[row][score_type.name].values[0] + + c_f_name = "contributivities.csv" contributivities_df = pd.read_csv( - os.path.join(ramp_submission_dir, 'training_output', c_f_name)) + os.path.join(ramp_submission_dir, "training_output", c_f_name) + ) logger.info(contributivities_df) for index, row in contributivities_df.iterrows(): - sub_id = int(row['submission'][-9:]) + sub_id = int(row["submission"][-9:]) submission = select_submission_by_id(session, sub_id) submission.contributivity = 0.0 for fold_i in range(n_folds): - c_i = row['fold_{}'.format(fold_i)] + c_i = row["fold_{}".format(fold_i)] submission.contributivity += c_i session.commit() diff --git a/ramp-database/ramp_database/tools/database.py b/ramp-database/ramp_database/tools/database.py index e76c8e047..4cc2972ae 100644 --- a/ramp-database/ramp_database/tools/database.py +++ b/ramp-database/ramp_database/tools/database.py @@ -8,7 +8,7 @@ from ._query import select_submission_file_type_by_name from ._query import select_submission_type_extension_by_name -logger = logging.getLogger('RAMP-DATABASE') +logger = logging.getLogger("RAMP-DATABASE") # Add functions: add entries in the database @@ -25,7 +25,7 @@ def add_extension(session, name): extension = select_extension_by_name(session, name) if extension is None: extension = Extension(name=name) - logger.info('Adding {}'.format(extension)) + logger.info("Adding {}".format(extension)) session.add(extension) session.commit() @@ -51,8 +51,9 @@ def add_submission_file_type(session, name, is_editable, max_size): submission_file_type = select_submission_file_type_by_name(session, name) if submission_file_type is None: submission_file_type = SubmissionFileType( - name=name, is_editable=is_editable, max_size=max_size) - logger.info('Adding {}'.format(submission_file_type)) + name=name, is_editable=is_editable, max_size=max_size + ) + logger.info("Adding {}".format(submission_file_type)) session.add(submission_file_type) session.commit() @@ -77,14 +78,12 @@ def add_submission_file_type_extension(session, type_name, extension_name): session, type_name, extension_name ) if type_extension is None: - submission_file_type = select_submission_file_type_by_name(session, - type_name) + submission_file_type = select_submission_file_type_by_name(session, type_name) extension = select_extension_by_name(session, extension_name) type_extension = SubmissionFileTypeExtension( - type=submission_file_type, - extension=extension + type=submission_file_type, extension=extension ) - logger.info('Adding {}'.format(type_extension)) + logger.info("Adding {}".format(type_extension)) session.add(type_extension) session.commit() @@ -150,6 +149,4 @@ def get_submission_file_type_extension(session, type_name, extension_name): list of :class:`ramp_database.model.SubmissionFileTypeExtension` The queried submission file type. """ - return select_submission_type_extension_by_name( - session, type_name, extension_name - ) + return select_submission_type_extension_by_name(session, type_name, extension_name) diff --git a/ramp-database/ramp_database/tools/event.py b/ramp-database/ramp_database/tools/event.py index dd53eea83..a2174cd17 100644 --- a/ramp-database/ramp_database/tools/event.py +++ b/ramp-database/ramp_database/tools/event.py @@ -30,7 +30,7 @@ from ..model import WorkflowElement from ..model import WorkflowElementType -logger = logging.getLogger('RAMP-DATABASE') +logger = logging.getLogger("RAMP-DATABASE") # Delete functions: remove from the database some information @@ -46,8 +46,9 @@ def delete_problem(session, problem_name): """ problem = select_problem_by_name(session, problem_name) if problem is None: - raise NoResultFound('No result found for "{}" in Problem table' - .format(problem_name)) + raise NoResultFound( + 'No result found for "{}" in Problem table'.format(problem_name) + ) for event in problem.events: delete_event(session, event.name) session.delete(problem) @@ -118,22 +119,22 @@ def add_workflow(session, workflow_object): session.add(Workflow(name=workflow_name)) workflow = select_workflow_by_name(session, workflow_name) for element_name in workflow_object.element_names: - tokens = element_name.split('.') + tokens = element_name.split(".") element_filename = tokens[0] # inferring that file is code if there is no extension - element_file_extension_name = ('.'.join(tokens[1:]) - if len(tokens) > 1 else 'py') - extension = select_extension_by_name(session, - element_file_extension_name) + element_file_extension_name = ".".join(tokens[1:]) if len(tokens) > 1 else "py" + extension = select_extension_by_name(session, element_file_extension_name) if extension is None: - raise ValueError('Unknown extension {}.' - .format(element_file_extension_name)) + raise ValueError( + "Unknown extension {}.".format(element_file_extension_name) + ) type_extension = select_submission_type_extension_by_extension( session, extension ) if type_extension is None: - raise ValueError('Unknown file type {}.' - .format(element_file_extension_name)) + raise ValueError( + "Unknown file type {}.".format(element_file_extension_name) + ) workflow_element_type = select_workflow_element_type_by_name( session, element_filename @@ -142,18 +143,18 @@ def add_workflow(session, workflow_object): workflow_element_type = WorkflowElementType( name=element_filename, type=type_extension.type ) - logger.info('Adding {}'.format(workflow_element_type)) + logger.info("Adding {}".format(workflow_element_type)) session.add(workflow_element_type) workflow_element = select_workflow_element_by_workflow_and_type( - session, workflow=workflow, - workflow_element_type=workflow_element_type + session, + workflow=workflow, + workflow_element_type=workflow_element_type, ) if workflow_element is None: workflow_element = WorkflowElement( - workflow=workflow, - workflow_element_type=workflow_element_type + workflow=workflow, workflow_element_type=workflow_element_type ) - logger.info('Adding {}'.format(workflow_element)) + logger.info("Adding {}".format(workflow_element)) session.add(workflow_element) session.commit() @@ -183,26 +184,40 @@ def add_problem(session, problem_name, kit_dir, data_dir, force=False): problem_kit_path = kit_dir if problem is not None: if not force: - raise ValueError('Attempting to overwrite a problem and ' - 'delete all linked events. Use"force=True" ' - 'if you want to overwrite the problem and ' - 'delete the events.') + raise ValueError( + "Attempting to overwrite a problem and " + 'delete all linked events. Use"force=True" ' + "if you want to overwrite the problem and " + "delete the events." + ) delete_problem(session, problem_name) # load the module to get the type of workflow used for the problem problem_module = import_module_from_source( - os.path.join(problem_kit_path, 'problem.py'), 'problem') + os.path.join(problem_kit_path, "problem.py"), "problem" + ) add_workflow(session, problem_module.workflow) - problem = Problem(name=problem_name, path_ramp_kit=kit_dir, - path_ramp_data=data_dir, session=session) - logger.info('Adding {}'.format(problem)) + problem = Problem( + name=problem_name, + path_ramp_kit=kit_dir, + path_ramp_data=data_dir, + session=session, + ) + logger.info("Adding {}".format(problem)) session.add(problem) session.commit() -def add_event(session, problem_name, event_name, event_title, - ramp_sandbox_name, ramp_submissions_path, is_public=False, - force=False): +def add_event( + session, + problem_name, + event_name, + event_title, + ramp_sandbox_name, + ramp_submissions_path, + is_public=False, + force=False, +): """Add a RAMP event in the database. Event file should be set up in ``databoard/specific/events/``. @@ -242,41 +257,44 @@ def add_event(session, problem_name, event_name, event_title, event = select_event_by_name(session, event_name) if event is not None: if not force: - raise ValueError("Attempting to overwrite existing event. " - "Use force=True to overwrite.") + raise ValueError( + "Attempting to overwrite existing event. " + "Use force=True to overwrite." + ) delete_event(session, event_name) - if event_name[:len(problem_name)+1] != (problem_name + '_'): + if event_name[: len(problem_name) + 1] != (problem_name + "_"): raise ValueError( "The event name should start with the problem name: '{}_'. Please " - "edit the entry of the event configuration file " - .format(problem_name) + "edit the entry of the event configuration file ".format( + problem_name + ) ) - event = Event(name=event_name, problem_name=problem_name, - event_title=event_title, - ramp_sandbox_name=ramp_sandbox_name, - path_ramp_submissions=ramp_submissions_path, - session=session) + event = Event( + name=event_name, + problem_name=problem_name, + event_title=event_title, + ramp_sandbox_name=ramp_sandbox_name, + path_ramp_submissions=ramp_submissions_path, + session=session, + ) event.is_public = is_public event.is_send_submitted_mails = False event.is_send_trained_mails = False - logger.info('Adding {}'.format(event)) + logger.info("Adding {}".format(event)) session.add(event) session.commit() X_train, y_train = event.problem.get_train_data() cv = event.problem.module.get_cv(X_train, y_train) for train_indices, test_indices in cv: - cv_fold = CVFold(event=event, - train_is=train_indices, - test_is=test_indices) + cv_fold = CVFold(event=event, train_is=train_indices, test_is=test_indices) session.add(cv_fold) score_types = event.problem.module.score_types for score_type in score_types: - event_score_type = EventScoreType(event=event, - score_type_object=score_type) + event_score_type = EventScoreType(event=event, score_type_object=score_type) session.add(event_score_type) event.official_score_name = score_types[0].name session.commit() @@ -303,8 +321,9 @@ def add_event_admin(session, event_name, user_name): session.commit() -def add_keyword(session, name, keyword_type, category=None, description=None, - force=False): +def add_keyword( + session, name, keyword_type, category=None, description=None, force=False +): """Add a keyword to the database. Parameters @@ -327,20 +346,25 @@ def add_keyword(session, name, keyword_type, category=None, description=None, if not force: raise ValueError( 'Attempting to update an existing keyword. Use "force=True"' - 'to overwrite the keyword.' + "to overwrite the keyword." ) keyword.type = keyword_type keyword.category = category keyword.description = description else: - keyword = Keyword(name=name, type=keyword_type, category=category, - description=description) + keyword = Keyword( + name=name, + type=keyword_type, + category=category, + description=description, + ) session.add(keyword) session.commit() -def add_problem_keyword(session, problem_name, keyword_name, description=None, - force=False): +def add_problem_keyword( + session, problem_name, keyword_name, description=None, force=False +): """Add relationship between a keyword and a problem. Parameters @@ -358,15 +382,17 @@ def add_problem_keyword(session, problem_name, keyword_name, description=None, """ problem = select_problem_by_name(session, problem_name) keyword = get_keyword_by_name(session, keyword_name) - problem_keyword = (session.query(ProblemKeyword) - .filter_by(problem=problem, keyword=keyword) - .one_or_none()) + problem_keyword = ( + session.query(ProblemKeyword) + .filter_by(problem=problem, keyword=keyword) + .one_or_none() + ) if problem_keyword is not None: if not force: raise ValueError( - 'Attempting to update an existing problem-keyword ' + "Attempting to update an existing problem-keyword " 'relationship. Use "force=True" if you want to overwrite the ' - 'relationship.' + "relationship." ) problem_keyword.description = description else: @@ -452,10 +478,7 @@ def get_cv_fold_by_event(session, event): ------- cv fold : : list of all cv folds of this event """ - return (session.query(CVFold) - .filter(EventScoreType.event_id == - event.id) - .all()) + return session.query(CVFold).filter(EventScoreType.event_id == event.id).all() def get_score_type_by_event(session, event): @@ -474,10 +497,9 @@ def get_score_type_by_event(session, event): list of list of :class:`ramp_database.model.ScoreType` The queried problem. """ - return (session.query(EventScoreType) - .filter(EventScoreType.event_id == - event.id) - .all()) + return ( + session.query(EventScoreType).filter(EventScoreType.event_id == event.id).all() + ) def get_event_admin(session, event_name, user_name): @@ -543,6 +565,8 @@ def get_problem_keyword_by_name(session, problem_name, keyword_name): """ problem = select_problem_by_name(session, problem_name) keyword = session.query(Keyword).filter_by(name=keyword_name).one_or_none() - return (session.query(ProblemKeyword) - .filter_by(problem=problem, keyword=keyword) - .one_or_none()) + return ( + session.query(ProblemKeyword) + .filter_by(problem=problem, keyword=keyword) + .one_or_none() + ) diff --git a/ramp-database/ramp_database/tools/frontend.py b/ramp-database/ramp_database/tools/frontend.py index 7f9783f79..f04a2499f 100644 --- a/ramp-database/ramp_database/tools/frontend.py +++ b/ramp-database/ramp_database/tools/frontend.py @@ -21,7 +21,7 @@ def is_admin(session, event_name, user_name): """ event = select_event_by_name(session, event_name) user = select_user_by_name(session, user_name) - if user.access_level == 'admin': + if user.access_level == "admin": return True event_admin = select_event_admin_by_instance(session, event, user) if event_admin is None: @@ -46,7 +46,7 @@ def is_accessible_event(session, event_name, user_name): user = select_user_by_name(session, user_name) if event is None: return False - if user.access_level == 'asked': + if user.access_level == "asked": return False if event.is_public or is_admin(session, event_name, user_name): return True @@ -84,8 +84,7 @@ def is_accessible_leaderboard(session, event_name, user_name): return False -def is_accessible_code(session, event_name, user_name, - submission_id=None): +def is_accessible_code(session, event_name, user_name, submission_id=None): """Whether or not the user can look at the code submission. Parameters @@ -121,9 +120,7 @@ def is_accessible_code(session, event_name, user_name, session, event_name, user_name, event.ramp_sandbox_name ) else: - submission = (session.query(Submission) - .filter_by(id=submission_id) - .one_or_none()) + submission = session.query(Submission).filter_by(id=submission_id).one_or_none() if submission is not None and user == submission.event_team.team.admin: return True return False @@ -147,8 +144,7 @@ def is_user_signed_up(session, event_name, user_name): Whether or not the user is signed up for the event. """ event_team = select_event_team_by_name(session, event_name, user_name) - if (event_team is not None and - (event_team.is_active and event_team.approved)): + if event_team is not None and (event_team.is_active and event_team.approved): return True return False @@ -171,7 +167,6 @@ def is_user_sign_up_requested(session, event_name, user_name): Whether or not the user had asked to join event or not. """ event_team = select_event_team_by_name(session, event_name, user_name) - if (event_team is not None and - (event_team.is_active and not event_team.approved)): + if event_team is not None and (event_team.is_active and not event_team.approved): return True return False diff --git a/ramp-database/ramp_database/tools/leaderboard.py b/ramp-database/ramp_database/tools/leaderboard.py index 069dbe356..39f37bc61 100644 --- a/ramp-database/ramp_database/tools/leaderboard.py +++ b/ramp-database/ramp_database/tools/leaderboard.py @@ -18,11 +18,12 @@ width = -1 if LooseVersion(pd.__version__) < LooseVersion("1.0.0") else None -pd.set_option('display.max_colwidth', width) +pd.set_option("display.max_colwidth", width) -def _compute_leaderboard(session, submissions, leaderboard_type, event_name, - with_links=True): +def _compute_leaderboard( + session, submissions, leaderboard_type, event_name, with_links=True +): """Format the leaderboard. Parameters @@ -45,14 +46,15 @@ def _compute_leaderboard(session, submissions, leaderboard_type, event_name, """ record_score = [] event = session.query(Event).filter_by(name=event_name).one() - map_score_precision = {score_type.name: score_type.precision - for score_type in event.score_types} + map_score_precision = { + score_type.name: score_type.precision for score_type in event.score_types + } for sub in submissions: # take only max n bag df_scores_bag = get_bagged_scores(session, sub.id) - highest_level = df_scores_bag.index.get_level_values('n_bag').max() + highest_level = df_scores_bag.index.get_level_values("n_bag").max() df_scores_bag = df_scores_bag.loc[(slice(None), highest_level), :] - df_scores_bag.index = df_scores_bag.index.droplevel('n_bag') + df_scores_bag.index = df_scores_bag.index.droplevel("n_bag") df_scores_bag = df_scores_bag.round(map_score_precision) df_scores = get_scores(session, sub.id) @@ -60,29 +62,39 @@ def _compute_leaderboard(session, submissions, leaderboard_type, event_name, df_time = get_time(session, sub.id) df_time = df_time.stack().to_frame() - df_time.index = df_time.index.set_names(['fold', 'step']) - df_time = df_time.rename(columns={0: 'time'}) + df_time.index = df_time.index.set_names(["fold", "step"]) + df_time = df_time.rename(columns={0: "time"}) df_time = df_time.sum(axis=0, level="step").T - df_scores_mean = df_scores.groupby('step').mean() - df_scores_std = df_scores.groupby('step').std() + df_scores_mean = df_scores.groupby("step").mean() + df_scores_std = df_scores.groupby("step").std() # select only the validation and testing steps and rename them to # public and private - map_renaming = {'valid': 'public', 'test': 'private'} - df_scores_mean = (df_scores_mean.loc[list(map_renaming.keys())] - .rename(index=map_renaming) - .stack().to_frame().T) - df_scores_std = (df_scores_std.loc[list(map_renaming.keys())] - .rename(index=map_renaming) - .stack().to_frame().T) - df_scores_bag = (df_scores_bag.rename(index=map_renaming) - .stack().to_frame().T) + map_renaming = {"valid": "public", "test": "private"} + df_scores_mean = ( + df_scores_mean.loc[list(map_renaming.keys())] + .rename(index=map_renaming) + .stack() + .to_frame() + .T + ) + df_scores_std = ( + df_scores_std.loc[list(map_renaming.keys())] + .rename(index=map_renaming) + .stack() + .to_frame() + .T + ) + df_scores_bag = df_scores_bag.rename(index=map_renaming).stack().to_frame().T - df = pd.concat([df_scores_bag, df_scores_mean, df_scores_std], axis=1, - keys=['bag', 'mean', 'std']) + df = pd.concat( + [df_scores_bag, df_scores_mean, df_scores_std], + axis=1, + keys=["bag", "mean", "std"], + ) - df.columns = df.columns.set_names(['stat', 'set', 'score']) + df.columns = df.columns.set_names(["stat", "set", "score"]) # change the multi-index into a stacked index df.columns = df.columns.map(lambda x: " ".join(x)) @@ -90,78 +102,84 @@ def _compute_leaderboard(session, submissions, leaderboard_type, event_name, # add the aggregated time information df_time.index = df.index df_time = df_time.rename( - columns={'train': 'train time [s]', - 'valid': 'validation time [s]', - 'test': 'test time [s]'} + columns={ + "train": "train time [s]", + "valid": "validation time [s]", + "test": "test time [s]", + } ) df = pd.concat([df, df_time], axis=1) - if leaderboard_type == 'private': - df['submission ID'] = sub.basename.replace('submission_', '') - df['team'] = sub.team.name - df['submission'] = sub.name_with_link if with_links else sub.name - df['contributivity'] = int(round(100 * sub.contributivity)) - df['historical contributivity'] = int(round( - 100 * sub.historical_contributivity)) - df['max RAM [MB]'] = get_submission_max_ram(session, sub.id) - df['submitted at (UTC)'] = pd.Timestamp(sub.submission_timestamp) + if leaderboard_type == "private": + df["submission ID"] = sub.basename.replace("submission_", "") + df["team"] = sub.team.name + df["submission"] = sub.name_with_link if with_links else sub.name + df["contributivity"] = int(round(100 * sub.contributivity)) + df["historical contributivity"] = int( + round(100 * sub.historical_contributivity) + ) + df["max RAM [MB]"] = get_submission_max_ram(session, sub.id) + df["submitted at (UTC)"] = pd.Timestamp(sub.submission_timestamp) record_score.append(df) # stack all the records df = pd.concat(record_score, axis=0, ignore_index=True, sort=False) # keep only second precision for the time stamp - df['submitted at (UTC)'] = df['submitted at (UTC)'].astype('datetime64[s]') + df["submitted at (UTC)"] = df["submitted at (UTC)"].astype("datetime64[s]") # reordered the column - stats_order = (['bag', 'mean', 'std'] if leaderboard_type == 'private' - else ['bag']) - dataset_order = (['public', 'private'] if leaderboard_type == 'private' - else ['public']) - score_order = ([event.official_score_name] + - [score_type.name for score_type in event.score_types - if score_type.name != event.official_score_name]) + stats_order = ["bag", "mean", "std"] if leaderboard_type == "private" else ["bag"] + dataset_order = ( + ["public", "private"] if leaderboard_type == "private" else ["public"] + ) + score_order = [event.official_score_name] + [ + score_type.name + for score_type in event.score_types + if score_type.name != event.official_score_name + ] score_list = [ - '{} {} {}'.format(stat, dataset, score) - for dataset, score, stat in product(dataset_order, - score_order, - stats_order) + "{} {} {}".format(stat, dataset, score) + for dataset, score, stat in product(dataset_order, score_order, stats_order) ] # Only display train and validation time for the public leaderboard - time_list = (['train time [s]', 'validation time [s]', 'test time [s]'] - if leaderboard_type == 'private' - else ['train time [s]', 'validation time [s]']) + time_list = ( + ["train time [s]", "validation time [s]", "test time [s]"] + if leaderboard_type == "private" + else ["train time [s]", "validation time [s]"] + ) col_ordered = ( - ['team', 'submission'] + - score_list + - ['contributivity', 'historical contributivity'] + - time_list + - ['max RAM [MB]', 'submitted at (UTC)'] + ["team", "submission"] + + score_list + + ["contributivity", "historical contributivity"] + + time_list + + ["max RAM [MB]", "submitted at (UTC)"] ) if leaderboard_type == "private": col_ordered = ["submission ID"] + col_ordered df = df[col_ordered] # check if the contributivity columns are null - contrib_columns = ['contributivity', 'historical contributivity'] + contrib_columns = ["contributivity", "historical contributivity"] if (df[contrib_columns] == 0).all(axis=0).all(): df = df.drop(columns=contrib_columns) df = df.sort_values( "bag {} {}".format(leaderboard_type, event.official_score_name), - ascending=event.get_official_score_type(session).is_lower_the_better + ascending=event.get_official_score_type(session).is_lower_the_better, ) # rename the column name for the public leaderboard - if leaderboard_type == 'public': - df = df.rename(columns={ - key: value for key, value in zip(score_list, score_order) - }) + if leaderboard_type == "public": + df = df.rename( + columns={key: value for key, value in zip(score_list, score_order)} + ) return df -def _compute_competition_leaderboard(session, submissions, leaderboard_type, - event_name): +def _compute_competition_leaderboard( + session, submissions, leaderboard_type, event_name +): """Format the competition leaderboard. Parameters @@ -184,85 +202,108 @@ def _compute_competition_leaderboard(session, submissions, leaderboard_type, score_type = event.get_official_score_type(session) score_name = event.official_score_name - private_leaderboard = _compute_leaderboard(session, submissions, 'private', - event_name, with_links=False) + private_leaderboard = _compute_leaderboard( + session, submissions, "private", event_name, with_links=False + ) - time_list = (['train time [s]', 'validation time [s]', 'test time [s]'] - if leaderboard_type == 'private' - else ['train time [s]', 'validation time [s]']) + time_list = ( + ["train time [s]", "validation time [s]", "test time [s]"] + if leaderboard_type == "private" + else ["train time [s]", "validation time [s]"] + ) - col_selected_private = (['team', 'submission'] + - ['bag private ' + score_name, - 'bag public ' + score_name] + - time_list + - ['submitted at (UTC)']) + col_selected_private = ( + ["team", "submission"] + + ["bag private " + score_name, "bag public " + score_name] + + time_list + + ["submitted at (UTC)"] + ) leaderboard_df = private_leaderboard[col_selected_private] leaderboard_df = leaderboard_df.rename( - columns={'bag private ' + score_name: 'private ' + score_name, - 'bag public ' + score_name: 'public ' + score_name} + columns={ + "bag private " + score_name: "private " + score_name, + "bag public " + score_name: "public " + score_name, + } ) # select best submission for each team - best_df = (leaderboard_df.groupby('team').min() - if score_type.is_lower_the_better - else leaderboard_df.groupby('team').max()) - best_df = best_df[['public ' + score_name]].reset_index() - best_df['best'] = True + best_df = ( + leaderboard_df.groupby("team").min() + if score_type.is_lower_the_better + else leaderboard_df.groupby("team").max() + ) + best_df = best_df[["public " + score_name]].reset_index() + best_df["best"] = True # merge to get a best indicator column then select best leaderboard_df = pd.merge( - leaderboard_df, best_df, how='left', - left_on=['team', 'public ' + score_name], - right_on=['team', 'public ' + score_name] + leaderboard_df, + best_df, + how="left", + left_on=["team", "public " + score_name], + right_on=["team", "public " + score_name], ) leaderboard_df = leaderboard_df.fillna(False) - leaderboard_df = leaderboard_df[leaderboard_df['best']] - leaderboard_df = leaderboard_df.drop(columns='best') + leaderboard_df = leaderboard_df[leaderboard_df["best"]] + leaderboard_df = leaderboard_df.drop(columns="best") # dealing with ties: we need the lowest timestamp - best_df = leaderboard_df.groupby('team').min() - best_df = best_df[['submitted at (UTC)']].reset_index() - best_df['best'] = True + best_df = leaderboard_df.groupby("team").min() + best_df = best_df[["submitted at (UTC)"]].reset_index() + best_df["best"] = True leaderboard_df = pd.merge( - leaderboard_df, best_df, how='left', - left_on=['team', 'submitted at (UTC)'], - right_on=['team', 'submitted at (UTC)']) + leaderboard_df, + best_df, + how="left", + left_on=["team", "submitted at (UTC)"], + right_on=["team", "submitted at (UTC)"], + ) leaderboard_df = leaderboard_df.fillna(False) - leaderboard_df = leaderboard_df[leaderboard_df['best']] - leaderboard_df = leaderboard_df.drop(columns='best') + leaderboard_df = leaderboard_df[leaderboard_df["best"]] + leaderboard_df = leaderboard_df.drop(columns="best") # sort by public score then by submission timestamp, compute rank leaderboard_df = leaderboard_df.sort_values( - by=['public ' + score_name, 'submitted at (UTC)'], - ascending=[score_type.is_lower_the_better, True]) - leaderboard_df['public rank'] = np.arange(len(leaderboard_df)) + 1 + by=["public " + score_name, "submitted at (UTC)"], + ascending=[score_type.is_lower_the_better, True], + ) + leaderboard_df["public rank"] = np.arange(len(leaderboard_df)) + 1 # sort by private score then by submission timestamp, compute rank leaderboard_df = leaderboard_df.sort_values( - by=['private ' + score_name, 'submitted at (UTC)'], - ascending=[score_type.is_lower_the_better, True]) - leaderboard_df['private rank'] = np.arange(len(leaderboard_df)) + 1 + by=["private " + score_name, "submitted at (UTC)"], + ascending=[score_type.is_lower_the_better, True], + ) + leaderboard_df["private rank"] = np.arange(len(leaderboard_df)) + 1 - leaderboard_df['move'] = \ - leaderboard_df['public rank'] - leaderboard_df['private rank'] - leaderboard_df['move'] = [ - '{:+d}'.format(m) if m != 0 else '-' for m in leaderboard_df['move']] + leaderboard_df["move"] = ( + leaderboard_df["public rank"] - leaderboard_df["private rank"] + ) + leaderboard_df["move"] = [ + "{:+d}".format(m) if m != 0 else "-" for m in leaderboard_df["move"] + ] col_selected = ( - [leaderboard_type + ' rank', 'team', 'submission', - leaderboard_type + ' ' + score_name] + - time_list + - ['submitted at (UTC)'] + [ + leaderboard_type + " rank", + "team", + "submission", + leaderboard_type + " " + score_name, + ] + + time_list + + ["submitted at (UTC)"] ) - if leaderboard_type == 'private': - col_selected.insert(1, 'move') + if leaderboard_type == "private": + col_selected.insert(1, "move") df = leaderboard_df[col_selected] - df = df.rename(columns={ - leaderboard_type + ' ' + score_name: score_name, - leaderboard_type + ' rank': 'rank' - }) - df = df.sort_values(by='rank') + df = df.rename( + columns={ + leaderboard_type + " " + score_name: score_name, + leaderboard_type + " rank": "rank", + } + ) + df = df.sort_values(by="rank") return df @@ -289,33 +330,38 @@ def get_leaderboard_all_info(session, event_name): """ update_all_user_leaderboards(session, event_name, new_only=False) - submissions = (session.query(Submission) - .filter(Event.name == event_name) - .filter(Event.id == EventTeam.event_id) - .filter(EventTeam.id == Submission.event_team_id) - .filter(Submission.state == 'scored')).all() + submissions = ( + session.query(Submission) + .filter(Event.name == event_name) + .filter(Event.id == EventTeam.event_id) + .filter(EventTeam.id == Submission.event_team_id) + .filter(Submission.state == "scored") + ).all() if not submissions: return pd.DataFrame() - private_leaderboard = _compute_leaderboard(session, submissions, - 'private', event_name, - with_links=False) - private_leaderboard = private_leaderboard.set_index(['team', 'submission']) - public_leaderboard = _compute_leaderboard(session, submissions, - 'public', event_name, - with_links=False) - public_leaderboard = public_leaderboard.set_index(['team', 'submission']) + private_leaderboard = _compute_leaderboard( + session, submissions, "private", event_name, with_links=False + ) + private_leaderboard = private_leaderboard.set_index(["team", "submission"]) + public_leaderboard = _compute_leaderboard( + session, submissions, "public", event_name, with_links=False + ) + public_leaderboard = public_leaderboard.set_index(["team", "submission"]) # join private and public data - joined_leaderboard = private_leaderboard.join(public_leaderboard, - on=['team', 'submission'], - lsuffix='-private', - rsuffix='-public') + joined_leaderboard = private_leaderboard.join( + public_leaderboard, + on=["team", "submission"], + lsuffix="-private", + rsuffix="-public", + ) return joined_leaderboard -def get_leaderboard(session, leaderboard_type, event_name, user_name=None, - with_links=True): +def get_leaderboard( + session, leaderboard_type, event_name, user_name=None, with_links=True +): r"""Get a leaderboard. Parameters @@ -338,67 +384,86 @@ def get_leaderboard(session, leaderboard_type, event_name, user_name=None, leaderboard : str The leaderboard in HTML format. """ - q = (session.query(Submission) - .filter(Event.id == EventTeam.event_id) - .filter(Team.id == EventTeam.team_id) - .filter(EventTeam.id == Submission.event_team_id) - .filter(Event.name == event_name)) + q = ( + session.query(Submission) + .filter(Event.id == EventTeam.event_id) + .filter(Team.id == EventTeam.team_id) + .filter(EventTeam.id == Submission.event_team_id) + .filter(Event.name == event_name) + ) if user_name is not None: q = q.filter(Team.name == user_name) submissions = q.all() - submission_filter = {'public': 'is_public_leaderboard', - 'private': 'is_private_leaderboard', - 'failed': 'is_error', - 'new': 'is_new', - 'public competition': 'is_in_competition', - 'private competition': 'is_in_competition'} - - submissions = [sub for sub in submissions - if (getattr(sub, submission_filter[leaderboard_type]) and - sub.is_not_sandbox)] + submission_filter = { + "public": "is_public_leaderboard", + "private": "is_private_leaderboard", + "failed": "is_error", + "new": "is_new", + "public competition": "is_in_competition", + "private competition": "is_in_competition", + } + + submissions = [ + sub + for sub in submissions + if (getattr(sub, submission_filter[leaderboard_type]) and sub.is_not_sandbox) + ] if not submissions: return None - if leaderboard_type in ['public', 'private']: + if leaderboard_type in ["public", "private"]: df = _compute_leaderboard( - session, submissions, leaderboard_type, event_name, - with_links=with_links + session, + submissions, + leaderboard_type, + event_name, + with_links=with_links, ) - elif leaderboard_type in ['new', 'failed']: - if leaderboard_type == 'new': - columns = ['team', 'submission', 'submitted at (UTC)', 'state'] + elif leaderboard_type in ["new", "failed"]: + if leaderboard_type == "new": + columns = ["team", "submission", "submitted at (UTC)", "state"] else: - columns = ['team', 'submission', 'submitted at (UTC)', 'error'] + columns = ["team", "submission", "submitted at (UTC)", "error"] # we rely on the zip function ignore the submission state if the error # column was not appended - data = [{ - column: value for column, value in zip( - columns, - [sub.event_team.team.name, - sub.name_with_link, - pd.Timestamp(sub.submission_timestamp), - (sub.state_with_link if leaderboard_type == 'failed' - else sub.state)]) - } for sub in submissions] + data = [ + { + column: value + for column, value in zip( + columns, + [ + sub.event_team.team.name, + sub.name_with_link, + pd.Timestamp(sub.submission_timestamp), + ( + sub.state_with_link + if leaderboard_type == "failed" + else sub.state + ), + ], + ) + } + for sub in submissions + ] df = pd.DataFrame(data, columns=columns) else: # make some extra filtering submissions = [sub for sub in submissions if sub.is_public_leaderboard] if not submissions: return None - competition_type = ('public' if 'public' in leaderboard_type - else 'private') + competition_type = "public" if "public" in leaderboard_type else "private" df = _compute_competition_leaderboard( session, submissions, competition_type, event_name ) - df_html = df.to_html(escape=False, index=False, max_cols=None, - max_rows=None, justify='left') - df_html = ' {} '.format( - df_html.split('')[1].split('')[0] + df_html = df.to_html( + escape=False, index=False, max_cols=None, max_rows=None, justify="left" + ) + df_html = " {} ".format( + df_html.split("")[1].split("")[0] ) return df_html @@ -419,32 +484,25 @@ def update_leaderboards(session, event_name, new_only=False): """ event = session.query(Event).filter_by(name=event_name).one() if not new_only: - event.private_leaderboard_html = get_leaderboard( - session, 'private', event_name - ) + event.private_leaderboard_html = get_leaderboard(session, "private", event_name) event.public_leaderboard_html_with_links = get_leaderboard( - session, 'public', event_name + session, "public", event_name ) event.public_leaderboard_html_no_links = get_leaderboard( - session, 'public', event_name, with_links=False - ) - event.failed_leaderboard_html = get_leaderboard( - session, 'failed', event_name + session, "public", event_name, with_links=False ) + event.failed_leaderboard_html = get_leaderboard(session, "failed", event_name) event.public_competition_leaderboard_html = get_leaderboard( - session, 'public competition', event_name + session, "public competition", event_name ) event.private_competition_leaderboard_html = get_leaderboard( - session, 'private competition', event_name + session, "private competition", event_name ) - event.new_leaderboard_html = get_leaderboard( - session, 'new', event_name - ) + event.new_leaderboard_html = get_leaderboard(session, "new", event_name) session.commit() -def update_user_leaderboards(session, event_name, user_name, - new_only=False): +def update_user_leaderboards(session, event_name, user_name, new_only=False): """Update the of a user leaderboards for a given event. Parameters @@ -463,13 +521,13 @@ def update_user_leaderboards(session, event_name, user_name, event_team = get_event_team_by_name(session, event_name, user_name) if not new_only: event_team.leaderboard_html = get_leaderboard( - session, 'public', event_name, user_name + session, "public", event_name, user_name ) event_team.failed_leaderboard_html = get_leaderboard( - session, 'failed', event_name, user_name + session, "failed", event_name, user_name ) event_team.new_leaderboard_html = get_leaderboard( - session, 'new', event_name, user_name + session, "new", event_name, user_name ) session.commit() @@ -494,12 +552,12 @@ def update_all_user_leaderboards(session, event_name, new_only=False): user_name = event_team.team.name if not new_only: event_team.leaderboard_html = get_leaderboard( - session, 'public', event_name, user_name + session, "public", event_name, user_name ) event_team.failed_leaderboard_html = get_leaderboard( - session, 'failed', event_name, user_name + session, "failed", event_name, user_name ) event_team.new_leaderboard_html = get_leaderboard( - session, 'new', event_name, user_name + session, "new", event_name, user_name ) session.commit() diff --git a/ramp-database/ramp_database/tools/submission.py b/ramp-database/ramp_database/tools/submission.py index 736b4235d..b428a399c 100644 --- a/ramp-database/ramp_database/tools/submission.py +++ b/ramp-database/ramp_database/tools/submission.py @@ -31,13 +31,12 @@ from ._query import select_team_by_name STATES = submission_states.enums -logger = logging.getLogger('RAMP-DATABASE') +logger = logging.getLogger("RAMP-DATABASE") # Add functions: add information to the database # TODO: move the queries in "_query" -def add_submission(session, event_name, team_name, submission_name, - submission_path): +def add_submission(session, event_name, team_name, submission_name, submission_path): """Create a submission in the database and returns an handle. Parameters @@ -63,80 +62,102 @@ def add_submission(session, event_name, team_name, submission_name, event = select_event_by_name(session, event_name) team = select_team_by_name(session, team_name) event_team = select_event_team_by_name(session, event_name, team_name) - submission = (session.query(Submission) - .filter(Submission.name == submission_name) - .filter(Submission.event_team == event_team) - .first()) + submission = ( + session.query(Submission) + .filter(Submission.name == submission_name) + .filter(Submission.event_team == event_team) + .first() + ) # create a new submission if submission is None: - all_submissions = (session.query(Submission) - .filter(Submission.event_team == event_team) - .order_by(Submission.submission_timestamp) - .all()) + all_submissions = ( + session.query(Submission) + .filter(Submission.event_team == event_team) + .order_by(Submission.submission_timestamp) + .all() + ) last_submission = None if not all_submissions else all_submissions[-1] # check for non-admin user if they wait enough to make a new submission - if (team.admin.access_level != 'admin' and last_submission is not None - and last_submission.is_not_sandbox): - time_to_last_submission = (datetime.datetime.utcnow() - - last_submission.submission_timestamp) + if ( + team.admin.access_level != "admin" + and last_submission is not None + and last_submission.is_not_sandbox + ): + time_to_last_submission = ( + datetime.datetime.utcnow() - last_submission.submission_timestamp + ) min_resubmit_time = datetime.timedelta( - seconds=event.min_duration_between_submissions) - awaiting_time = int((min_resubmit_time - time_to_last_submission) - .total_seconds()) + seconds=event.min_duration_between_submissions + ) + awaiting_time = int( + (min_resubmit_time - time_to_last_submission).total_seconds() + ) if awaiting_time > 0: raise TooEarlySubmissionError( - 'You need to wait {} more seconds until next submission' - .format(awaiting_time)) + "You need to wait {} more seconds until next submission".format( + awaiting_time + ) + ) submission = Submission( name=submission_name, event_team=event_team, session=session ) for cv_fold in event.cv_folds: - submission_on_cv_fold = SubmissionOnCVFold(submission=submission, - cv_fold=cv_fold) + submission_on_cv_fold = SubmissionOnCVFold( + submission=submission, cv_fold=cv_fold + ) session.add(submission_on_cv_fold) session.add(submission) # the submission already exist else: # We allow resubmit for new or failing submissions - if (submission.is_not_sandbox and - (submission.state == 'new' or submission.is_error)): - submission.set_state('new', session) + if submission.is_not_sandbox and ( + submission.state == "new" or submission.is_error + ): + submission.set_state("new", session) submission.submission_timestamp = datetime.datetime.utcnow() - all_cv_folds = (session.query(SubmissionOnCVFold) - .filter_by(submission_id=submission.id) - .all()) + all_cv_folds = ( + session.query(SubmissionOnCVFold) + .filter_by(submission_id=submission.id) + .all() + ) all_cv_folds = sorted(all_cv_folds, key=lambda x: x.id) for submission_on_cv_fold in all_cv_folds: submission_on_cv_fold.reset() else: - error_msg = ('Submission "{}" of team "{}" at event "{}" exists ' - 'already' - .format(submission_name, team_name, event_name)) + error_msg = ( + 'Submission "{}" of team "{}" at event "{}" exists ' + "already".format(submission_name, team_name, event_name) + ) raise DuplicateSubmissionError(error_msg) # filter the files which contain an extension - files_type_extension = [filename.split('.', maxsplit=1) - for filename in os.listdir(submission_path) - if len(filename.split('.')) > 1] + files_type_extension = [ + filename.split(".", maxsplit=1) + for filename in os.listdir(submission_path) + if len(filename.split(".")) > 1 + ] for workflow_element in event.problem.workflow.elements: try: desposited_types, deposited_extensions = zip( - *[(filename, extension) - for filename, extension in files_type_extension - if filename == workflow_element.name] + *[ + (filename, extension) + for filename, extension in files_type_extension + if filename == workflow_element.name + ] ) except ValueError as e: session.rollback() - if 'values to unpack' in str(e): + if "values to unpack" in str(e): # no file matching the workflow element raise MissingSubmissionFileError( - 'No file corresponding to the workflow element "{}"' - .format(workflow_element) + 'No file corresponding to the workflow element "{}"'.format( + workflow_element ) + ) raise # check that files have the correct extension ... @@ -148,32 +169,33 @@ def add_submission(session, event_name, team_name, submission_name, else: session.rollback() raise MissingExtensionError( - 'All extensions "{}" are unknown for the submission "{}".' - .format(", ".join(deposited_extensions), submission_name) + 'All extensions "{}" are unknown for the submission "{}".'.format( + ", ".join(deposited_extensions), submission_name + ) ) # check if it is a resubmission - submission_file = (session.query(SubmissionFile) - .filter(SubmissionFile.workflow_element == - workflow_element) - .filter(SubmissionFile.submission == - submission) - .one_or_none()) + submission_file = ( + session.query(SubmissionFile) + .filter(SubmissionFile.workflow_element == workflow_element) + .filter(SubmissionFile.submission == submission) + .one_or_none() + ) # TODO: handle if resubmitted file changed extension if submission_file is None: submission_file_type = select_submission_file_type_by_name( session, workflow_element.file_type ) - type_extension = \ - (session.query(SubmissionFileTypeExtension) - .filter(SubmissionFileTypeExtension.type == - submission_file_type) - .filter(SubmissionFileTypeExtension.extension == - extension) - .one()) + type_extension = ( + session.query(SubmissionFileTypeExtension) + .filter(SubmissionFileTypeExtension.type == submission_file_type) + .filter(SubmissionFileTypeExtension.extension == extension) + .one() + ) submission_file = SubmissionFile( - submission=submission, workflow_element=workflow_element, - submission_file_type_extension=type_extension + submission=submission, + workflow_element=workflow_element, + submission_file_type_extension=type_extension, ) session.add(submission_file) event.set_n_submissions() @@ -200,13 +222,21 @@ def is_editable(filename, workflow): from .leaderboard import update_leaderboards from .leaderboard import update_user_leaderboards + update_leaderboards(session, event_name, new_only=True) update_user_leaderboards(session, event_name, team.name, new_only=True) return submission -def add_submission_similarity(session, credit_type, user, source_submission, - target_submission, similarity, timestamp): +def add_submission_similarity( + session, + credit_type, + user, + source_submission, + target_submission, + similarity, + timestamp, +): """Add submission similarity entry. Parameters @@ -227,16 +257,19 @@ def add_submission_similarity(session, credit_type, user, source_submission, The date and time of the creation of the similarity. """ submission_similarity = SubmissionSimilarity( - type=credit_type, user=user, source_submission=source_submission, - target_submission=target_submission, similarity=similarity, - timestamp=timestamp + type=credit_type, + user=user, + source_submission=source_submission, + target_submission=target_submission, + similarity=similarity, + timestamp=timestamp, ) session.add(submission_similarity) session.commit() # Getter functions: get information from the database -def get_submissions(session, event_name, state='new'): +def get_submissions(session, event_name, state="new"): """Get information about submissions from an event with a specific state optionally. @@ -334,8 +367,7 @@ def get_submission_by_name(session, event_name, team_name, name): ramp_database.tools.get_submissions : Get submissions information. ramp_database.tools.get_submission_by_id : Get a submission using an id. """ - submission = select_submission_by_name(session, event_name, team_name, - name) + submission = select_submission_by_name(session, event_name, team_name, name) submission.event.name submission.team.name return submission @@ -392,15 +424,15 @@ def get_time(session, submission_id): A pandas dataframe containing the computation time of each fold. """ results = defaultdict(list) - all_cv_folds = (session.query(SubmissionOnCVFold) - .filter_by(submission_id=submission_id) - .all()) + all_cv_folds = ( + session.query(SubmissionOnCVFold).filter_by(submission_id=submission_id).all() + ) all_cv_folds = sorted(all_cv_folds, key=lambda x: x.id) for fold_id, cv_fold in enumerate(all_cv_folds): - results['fold'].append(fold_id) - for step in ('train', 'valid', 'test'): - results[step].append(getattr(cv_fold, '{}_time'.format(step))) - return pd.DataFrame(results).set_index('fold') + results["fold"].append(fold_id) + for step in ("train", "valid", "test"): + results[step].append(getattr(cv_fold, "{}_time".format(step))) + return pd.DataFrame(results).set_index("fold") def get_scores(session, submission_id): @@ -420,16 +452,16 @@ def get_scores(session, submission_id): """ results = defaultdict(list) index = [] - all_cv_folds = (session.query(SubmissionOnCVFold) - .filter_by(submission_id=submission_id) - .all()) + all_cv_folds = ( + session.query(SubmissionOnCVFold).filter_by(submission_id=submission_id).all() + ) all_cv_folds = sorted(all_cv_folds, key=lambda x: x.id) for fold_id, cv_fold in enumerate(all_cv_folds): - for step in ('train', 'valid', 'test'): + for step in ("train", "valid", "test"): index.append((fold_id, step)) for score in cv_fold.scores: - results[score.name].append(getattr(score, step + '_score')) - multi_index = pd.MultiIndex.from_tuples(index, names=['fold', 'step']) + results[score.name].append(getattr(score, step + "_score")) + multi_index = pd.MultiIndex.from_tuples(index, names=["fold", "step"]) scores = pd.DataFrame(results, index=multi_index) return scores @@ -451,19 +483,21 @@ def get_bagged_scores(session, submission_id): """ submission = select_submission_by_id(session, submission_id) bagged_scores = {} - for step in ('test', 'valid'): + for step in ("test", "valid"): score_dict = {} for score in submission.scores: - score_all_bags = getattr(score, '{}_score_cv_bags'.format(step)) + score_all_bags = getattr(score, "{}_score_cv_bags".format(step)) if score_all_bags is None: continue - score_dict[score.score_name] = \ - {n: sc for n, sc in enumerate(score_all_bags)} + score_dict[score.score_name] = { + n: sc for n, sc in enumerate(score_all_bags) + } bagged_scores[step] = score_dict - bagged_scores = pd.concat({step: pd.DataFrame(scores) - for step, scores in bagged_scores.items()}) - bagged_scores.columns = bagged_scores.columns.rename('scores') - bagged_scores.index = bagged_scores.index.rename(['step', 'n_bag']) + bagged_scores = pd.concat( + {step: pd.DataFrame(scores) for step, scores in bagged_scores.items()} + ) + bagged_scores.columns = bagged_scores.columns.rename("scores") + bagged_scores.index = bagged_scores.index.rename(["step", "n_bag"]) return bagged_scores @@ -541,22 +575,28 @@ def get_source_submissions(session, submission_id): List of the submissions connected with the submission to be trained. """ submission = select_submission_by_id(session, submission_id) - submissions = (session.query(Submission) - .filter_by(event_team=submission.event_team) - .all()) + submissions = ( + session.query(Submission).filter_by(event_team=submission.event_team).all() + ) # there is for the moment a single admin users = [submission.team.admin] for user in users: - user_interactions = \ - (session.query(UserInteraction) - .filter_by(user=user, interaction='looking at submission') - .all()) - submissions += [user_interaction.submission - for user_interaction in user_interactions - if user_interaction.event == submission.event] + user_interactions = ( + session.query(UserInteraction) + .filter_by(user=user, interaction="looking at submission") + .all() + ) + submissions += [ + user_interaction.submission + for user_interaction in user_interactions + if user_interaction.event == submission.event + ] submissions = list(set(submissions)) - submissions = [s for s in submissions - if s.submission_timestamp < submission.submission_timestamp] + submissions = [ + s + for s in submissions + if s.submission_timestamp < submission.submission_timestamp + ] submissions.sort(key=lambda x: x.submission_timestamp, reverse=True) return submissions @@ -606,17 +646,16 @@ def set_time(session, submission_id, path_predictions): path_predictions : str The path where the results files are located. """ - all_cv_folds = (session.query(SubmissionOnCVFold) - .filter_by(submission_id=submission_id) - .all()) + all_cv_folds = ( + session.query(SubmissionOnCVFold).filter_by(submission_id=submission_id).all() + ) all_cv_folds = sorted(all_cv_folds, key=lambda x: x.id) for fold_id, cv_fold in enumerate(all_cv_folds): - path_results = os.path.join(path_predictions, - 'fold_{}'.format(fold_id)) + path_results = os.path.join(path_predictions, "fold_{}".format(fold_id)) results = {} - for step in ('train', 'valid', 'test'): - results[step + '_time'] = np.loadtxt( - os.path.join(path_results, step + '_time') + for step in ("train", "valid", "test"): + results[step + "_time"] = np.loadtxt( + os.path.join(path_results, step + "_time") ).item() for key, value in results.items(): setattr(cv_fold, key, value) @@ -635,20 +674,19 @@ def set_scores(session, submission_id, path_predictions): path_predictions : str The path where the results files are located. """ - all_cv_folds = (session.query(SubmissionOnCVFold) - .filter_by(submission_id=submission_id) - .all()) + all_cv_folds = ( + session.query(SubmissionOnCVFold).filter_by(submission_id=submission_id).all() + ) all_cv_folds = sorted(all_cv_folds, key=lambda x: x.id) for fold_id, cv_fold in enumerate(all_cv_folds): - path_results = os.path.join(path_predictions, - 'fold_{}'.format(fold_id)) + path_results = os.path.join(path_predictions, "fold_{}".format(fold_id)) scores_update = pd.read_csv( - os.path.join(path_results, 'scores.csv'), index_col=0 + os.path.join(path_results, "scores.csv"), index_col=0 ) for score in cv_fold.scores: for step in scores_update.index: value = scores_update.loc[step, score.name] - setattr(score, step + '_score', value) + setattr(score, step + "_score", value) session.commit() @@ -665,22 +703,21 @@ def set_bagged_scores(session, submission_id, path_predictions): The path where the results files are located. """ submission = select_submission_by_id(session, submission_id) - df = pd.read_csv(os.path.join(path_predictions, 'bagged_scores.csv'), - index_col=[0, 1]) - df_steps = df.index.get_level_values('step').unique().tolist() + df = pd.read_csv( + os.path.join(path_predictions, "bagged_scores.csv"), index_col=[0, 1] + ) + df_steps = df.index.get_level_values("step").unique().tolist() for score in submission.scores: - for step in ('valid', 'test'): - highest_n_bag = df.index.get_level_values('n_bag').max() + for step in ("valid", "test"): + highest_n_bag = df.index.get_level_values("n_bag").max() if step in df_steps: - score_last_bag = df.loc[(step, highest_n_bag), - score.score_name] - score_all_bags = (df.loc[(step, slice(None)), score.score_name] - .tolist()) + score_last_bag = df.loc[(step, highest_n_bag), score.score_name] + score_all_bags = df.loc[(step, slice(None)), score.score_name].tolist() else: score_last_bag = float(score.event_score_type.worst) score_all_bags = None - setattr(score, '{}_score_cv_bag'.format(step), score_last_bag) - setattr(score, '{}_score_cv_bags'.format(step), score_all_bags) + setattr(score, "{}_score_cv_bag".format(step), score_last_bag) + setattr(score, "{}_score_cv_bags".format(step), score_all_bags) session.commit() @@ -747,13 +784,20 @@ def submit_starting_kits(session, event_name, team_name, path_submission): from_submission_path = os.path.join(path_submission, submission_name) # one of the starting kit is usually used a sandbox and we need to # change the name to not have any duplicate - submission_name = (submission_name - if submission_name != event.ramp_sandbox_name - else submission_name + '_test') - submission = add_submission(session, event_name, team_name, - submission_name, from_submission_path) - logger.info('Copying the submission files into the deployment folder') - logger.info('Adding {}'.format(submission)) + submission_name = ( + submission_name + if submission_name != event.ramp_sandbox_name + else submission_name + "_test" + ) + submission = add_submission( + session, + event_name, + team_name, + submission_name, + from_submission_path, + ) + logger.info("Copying the submission files into the deployment folder") + logger.info("Adding {}".format(submission)) # revert the minimum duration between two submissions event.min_duration_between_submissions = min_duration_between_submissions session.commit() diff --git a/ramp-database/ramp_database/tools/team.py b/ramp-database/ramp_database/tools/team.py index 8bd9010b7..e8c402ae7 100644 --- a/ramp-database/ramp_database/tools/team.py +++ b/ramp-database/ramp_database/tools/team.py @@ -9,7 +9,7 @@ from ._query import select_event_team_by_name from ._query import select_team_by_name -logger = logging.getLogger('RAMP-DATABASE') +logger = logging.getLogger("RAMP-DATABASE") def ask_sign_up_team(session, event_name, team_name): @@ -61,14 +61,19 @@ def sign_up_team(session, event_name, team_name): """ event, team, event_team = ask_sign_up_team(session, event_name, team_name) # setup the sandbox - path_sandbox_submission = os.path.join(event.problem.path_ramp_kit, - 'submissions', - event.ramp_sandbox_name) + path_sandbox_submission = os.path.join( + event.problem.path_ramp_kit, "submissions", event.ramp_sandbox_name + ) submission_name = event.ramp_sandbox_name - submission = add_submission(session, event_name, team_name, - submission_name, path_sandbox_submission) - logger.info('Copying the submission files into the deployment folder') - logger.info('Adding {}'.format(submission)) + submission = add_submission( + session, + event_name, + team_name, + submission_name, + path_sandbox_submission, + ) + logger.info("Copying the submission files into the deployment folder") + logger.info("Adding {}".format(submission)) event_team.approved = True session.commit() diff --git a/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/problem.py b/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/problem.py index b15324346..55bca6c0c 100644 --- a/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/problem.py +++ b/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/problem.py @@ -3,8 +3,8 @@ import rampwf as rw from sklearn.model_selection import ShuffleSplit -problem_title = 'Boston housing price regression' -_target_column_name = 'medv' +problem_title = "Boston housing price regression" +_target_column_name = "medv" # A type (class) which will be used to create wrapper objects for y_pred Predictions = rw.prediction_types.make_regression() # An object implementing the workflow @@ -13,7 +13,7 @@ score_types = [ rw.score_types.RMSE(), - rw.score_types.RelativeRMSE(name='rel_rmse'), + rw.score_types.RelativeRMSE(name="rel_rmse"), ] @@ -23,17 +23,17 @@ def get_cv(X, y): def _read_data(path, f_name): - data = pd.read_csv(os.path.join(path, 'data', f_name)) + data = pd.read_csv(os.path.join(path, "data", f_name)) y_array = data[_target_column_name].values X_array = data.drop([_target_column_name], axis=1).values return X_array, y_array -def get_train_data(path='.'): - f_name = 'train.csv' +def get_train_data(path="."): + f_name = "train.csv" return _read_data(path, f_name) -def get_test_data(path='.'): - f_name = 'test.csv' +def get_test_data(path="."): + f_name = "test.csv" return _read_data(path, f_name) diff --git a/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/submissions/random_forest_100/regressor.py b/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/submissions/random_forest_100/regressor.py index 061cf0374..493993e6f 100755 --- a/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/submissions/random_forest_100/regressor.py +++ b/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/submissions/random_forest_100/regressor.py @@ -8,7 +8,8 @@ def __init__(self): def fit(self, X, y): self.reg = RandomForestRegressor( - n_estimators=100, max_leaf_nodes=3, random_state=61) + n_estimators=100, max_leaf_nodes=3, random_state=61 + ) self.reg.fit(X, y) def predict(self, X): diff --git a/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/submissions/starting_kit/regressor.py b/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/submissions/starting_kit/regressor.py index 8de7fa9b3..c8882e665 100755 --- a/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/submissions/starting_kit/regressor.py +++ b/ramp-database/ramp_database/tools/tests/data/boston_housing_kit/submissions/starting_kit/regressor.py @@ -8,7 +8,8 @@ def __init__(self): def fit(self, X, y): self.reg = RandomForestRegressor( - n_estimators=2, max_leaf_nodes=2, random_state=61) + n_estimators=2, max_leaf_nodes=2, random_state=61 + ) self.reg.fit(X, y) def predict(self, X): diff --git a/ramp-database/ramp_database/tools/tests/data/iris_kit/problem.py b/ramp-database/ramp_database/tools/tests/data/iris_kit/problem.py index 0dadae60c..aff46c180 100644 --- a/ramp-database/ramp_database/tools/tests/data/iris_kit/problem.py +++ b/ramp-database/ramp_database/tools/tests/data/iris_kit/problem.py @@ -3,20 +3,19 @@ import rampwf as rw from sklearn.model_selection import StratifiedShuffleSplit -problem_title = 'Iris classification' -_target_column_name = 'species' -_prediction_label_names = ['setosa', 'versicolor', 'virginica'] +problem_title = "Iris classification" +_target_column_name = "species" +_prediction_label_names = ["setosa", "versicolor", "virginica"] # A type (class) which will be used to create wrapper objects for y_pred -Predictions = rw.prediction_types.make_multiclass( - label_names=_prediction_label_names) +Predictions = rw.prediction_types.make_multiclass(label_names=_prediction_label_names) # An object implementing the workflow workflow = rw.workflows.Estimator() score_types = [ - rw.score_types.Accuracy(name='acc'), - rw.score_types.ClassificationError(name='error'), - rw.score_types.NegativeLogLikelihood(name='nll'), - rw.score_types.F1Above(name='f1_70', threshold=0.7), + rw.score_types.Accuracy(name="acc"), + rw.score_types.ClassificationError(name="error"), + rw.score_types.NegativeLogLikelihood(name="nll"), + rw.score_types.F1Above(name="f1_70", threshold=0.7), ] @@ -26,17 +25,17 @@ def get_cv(X, y): def _read_data(path, f_name): - data = pd.read_csv(os.path.join(path, 'data', f_name)) + data = pd.read_csv(os.path.join(path, "data", f_name)) y_array = data[_target_column_name].values X_array = data.drop([_target_column_name], axis=1) return X_array, y_array -def get_train_data(path='.'): - f_name = 'train.csv' +def get_train_data(path="."): + f_name = "train.csv" return _read_data(path, f_name) -def get_test_data(path='.'): - f_name = 'test.csv' +def get_test_data(path="."): + f_name = "test.csv" return _read_data(path, f_name) diff --git a/ramp-database/ramp_database/tools/tests/data/iris_kit/submissions/random_forest_10_10/estimator.py b/ramp-database/ramp_database/tools/tests/data/iris_kit/submissions/random_forest_10_10/estimator.py index 494d240b8..3860343c3 100755 --- a/ramp-database/ramp_database/tools/tests/data/iris_kit/submissions/random_forest_10_10/estimator.py +++ b/ramp-database/ramp_database/tools/tests/data/iris_kit/submissions/random_forest_10_10/estimator.py @@ -2,6 +2,5 @@ def get_estimator(): - clf = RandomForestClassifier(n_estimators=10, max_leaf_nodes=10, - random_state=61) + clf = RandomForestClassifier(n_estimators=10, max_leaf_nodes=10, random_state=61) return clf diff --git a/ramp-database/ramp_database/tools/tests/data/iris_kit/submissions/starting_kit/estimator.py b/ramp-database/ramp_database/tools/tests/data/iris_kit/submissions/starting_kit/estimator.py index 661d88c2b..64220a376 100755 --- a/ramp-database/ramp_database/tools/tests/data/iris_kit/submissions/starting_kit/estimator.py +++ b/ramp-database/ramp_database/tools/tests/data/iris_kit/submissions/starting_kit/estimator.py @@ -2,6 +2,5 @@ def get_estimator(): - clf = RandomForestClassifier(n_estimators=1, max_leaf_nodes=2, - random_state=61) + clf = RandomForestClassifier(n_estimators=1, max_leaf_nodes=2, random_state=61) return clf diff --git a/ramp-database/ramp_database/tools/tests/test_database.py b/ramp-database/ramp_database/tools/tests/test_database.py index b811a73c9..0fb65beac 100644 --- a/ramp-database/ramp_database/tools/tests/test_database.py +++ b/ramp-database/ramp_database/tools/tests/test_database.py @@ -30,16 +30,16 @@ def session_scope_function(database_connection): ramp_config = ramp_config_template() try: deployment_dir = create_test_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_check_extension(session_scope_function): - extension_name = 'cpp' + extension_name = "cpp" add_extension(session_scope_function, extension_name) extension = get_extension(session_scope_function, extension_name) assert extension.name == extension_name @@ -50,11 +50,10 @@ def test_check_extension(session_scope_function): def test_check_submission_file_type(session_scope_function): - name = 'my own type' + name = "my own type" is_editable = False max_size = 10 ** 5 - add_submission_file_type(session_scope_function, name, is_editable, - max_size) + add_submission_file_type(session_scope_function, name, is_editable, max_size) sub_file_type = get_submission_file_type(session_scope_function, name) assert sub_file_type.name == name assert sub_file_type.is_editable is is_editable @@ -67,24 +66,25 @@ def test_check_submission_file_type(session_scope_function): def test_check_submission_file_type_extension(session_scope_function): # create a new type and extension - extension_name = 'cpp' + extension_name = "cpp" add_extension(session_scope_function, extension_name) - type_name = 'my own type' + type_name = "my own type" is_editable = False max_size = 10 ** 5 - add_submission_file_type(session_scope_function, type_name, is_editable, - max_size) - - add_submission_file_type_extension(session_scope_function, type_name, - extension_name) - sub_ext_type = get_submission_file_type_extension(session_scope_function, - type_name, - extension_name) + add_submission_file_type(session_scope_function, type_name, is_editable, max_size) + + add_submission_file_type_extension( + session_scope_function, type_name, extension_name + ) + sub_ext_type = get_submission_file_type_extension( + session_scope_function, type_name, extension_name + ) assert sub_ext_type.file_type == type_name assert sub_ext_type.extension_name == extension_name assert isinstance(sub_ext_type, SubmissionFileTypeExtension) - sub_ext_type = get_submission_file_type_extension(session_scope_function, - None, None) + sub_ext_type = get_submission_file_type_extension( + session_scope_function, None, None + ) assert len(sub_ext_type) == 5 assert isinstance(sub_ext_type, list) diff --git a/ramp-database/ramp_database/tools/tests/test_event.py b/ramp-database/ramp_database/tools/tests/test_event.py index f03e38874..5bbbc75d5 100644 --- a/ramp-database/ramp_database/tools/tests/test_event.py +++ b/ramp-database/ramp_database/tools/tests/test_event.py @@ -62,44 +62,47 @@ def session_scope_function(database_connection): ramp_config = ramp_config_template() try: deployment_dir = create_test_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: add_users(session) yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_toy_db(): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_check_problem(session_scope_function): ramp_configs = { - 'iris': read_config(ramp_config_iris()), - 'boston_housing': read_config(ramp_config_boston_housing()) + "iris": read_config(ramp_config_iris()), + "boston_housing": read_config(ramp_config_boston_housing()), } for problem_name, ramp_config in ramp_configs.items(): internal_ramp_config = generate_ramp_config(ramp_config) setup_ramp_kit_ramp_data( internal_ramp_config, problem_name, mock_html_conversion=True ) - add_problem(session_scope_function, problem_name, - internal_ramp_config['ramp_kit_dir'], - internal_ramp_config['ramp_data_dir']) + add_problem( + session_scope_function, + problem_name, + internal_ramp_config["ramp_kit_dir"], + internal_ramp_config["ramp_data_dir"], + ) - problem_name = 'iris' + problem_name = "iris" problem = get_problem(session_scope_function, problem_name) assert problem.name == problem_name assert isinstance(problem, Problem) @@ -109,21 +112,23 @@ def test_check_problem(session_scope_function): # Without forcing, we cannot write the same problem twice internal_ramp_config = generate_ramp_config(ramp_configs[problem_name]) - err_msg = 'Attempting to overwrite a problem and delete all linked events' + err_msg = "Attempting to overwrite a problem and delete all linked events" with pytest.raises(ValueError, match=err_msg): add_problem( - session_scope_function, problem_name, - internal_ramp_config['ramp_kit_dir'], - internal_ramp_config['ramp_data_dir'], - force=False + session_scope_function, + problem_name, + internal_ramp_config["ramp_kit_dir"], + internal_ramp_config["ramp_data_dir"], + force=False, ) # Force add the problem add_problem( - session_scope_function, problem_name, - internal_ramp_config['ramp_kit_dir'], - internal_ramp_config['ramp_data_dir'], - force=True + session_scope_function, + problem_name, + internal_ramp_config["ramp_kit_dir"], + internal_ramp_config["ramp_data_dir"], + force=True, ) problem = get_problem(session_scope_function, problem_name) assert problem.name == problem_name @@ -139,19 +144,17 @@ def test_check_problem(session_scope_function): class WorkflowFaultyElements: """Workflow with faulty elements.""" + def __init__(self, case=None): self.case = case @property def element_names(self): - if self.case == 'unknown-extension': - return ['function.cpp'] + if self.case == "unknown-extension": + return ["function.cpp"] -@pytest.mark.parametrize( - "case, err_msg", - [('unknown-extension', 'Unknown extension')] -) +@pytest.mark.parametrize("case, err_msg", [("unknown-extension", "Unknown extension")]) def test_add_workflow_error(case, err_msg, session_scope_function): workflow = WorkflowFaultyElements(case=case) with pytest.raises(ValueError, match=err_msg): @@ -161,39 +164,43 @@ def test_add_workflow_error(case, err_msg, session_scope_function): def test_check_workflow(session_scope_function): # load the workflow from the iris kit which is in the test data - for kit in ['iris', 'boston_housing']: - kit_path = os.path.join(HERE, 'data', '{}_kit'.format(kit)) + for kit in ["iris", "boston_housing"]: + kit_path = os.path.join(HERE, "data", "{}_kit".format(kit)) problem_module = import_module_from_source( - os.path.join(kit_path, 'problem.py'), 'problem') + os.path.join(kit_path, "problem.py"), "problem" + ) add_workflow(session_scope_function, problem_module.workflow) workflow = get_workflow(session_scope_function, None) assert len(workflow) == 2 assert isinstance(workflow, list) - workflow = get_workflow(session_scope_function, 'Estimator') - assert workflow.name == 'Estimator' + workflow = get_workflow(session_scope_function, "Estimator") + assert workflow.name == "Estimator" assert isinstance(workflow, Workflow) -def _check_event(session, event, event_name, event_title, event_is_public, - scores_name): +def _check_event(session, event, event_name, event_title, event_is_public, scores_name): assert isinstance(event, Event) assert event.name == event_name assert event.title == event_title assert event.is_public == event_is_public - score_type = (session.query(EventScoreType) - .filter(EventScoreType.event_id == Event.id) - .filter(Event.name == event_name) - .all()) + score_type = ( + session.query(EventScoreType) + .filter(EventScoreType.event_id == Event.id) + .filter(Event.name == event_name) + .all() + ) for score in score_type: assert score.name in scores_name # rebuild the fold indices to check if we stored the right one in the # database - cv_folds = (session.query(CVFold) - .filter(CVFold.event_id == Event.id) - .filter(Event.name == event_name) - .all()) + cv_folds = ( + session.query(CVFold) + .filter(CVFold.event_id == Event.id) + .filter(Event.name == event_name) + .all() + ) X_train, y_train = event.problem.get_train_data() cv = event.problem.module.get_cv(X_train, y_train) for ((train_indices, test_indices), stored_fold) in zip(cv, cv_folds): @@ -203,74 +210,90 @@ def _check_event(session, event, event_name, event_title, event_is_public, def test_check_event(session_scope_function): ramp_configs = { - 'iris': read_config(ramp_config_iris()), - 'boston_housing': read_config(ramp_config_boston_housing()) + "iris": read_config(ramp_config_iris()), + "boston_housing": read_config(ramp_config_boston_housing()), } for problem_name, ramp_config in ramp_configs.items(): internal_ramp_config = generate_ramp_config(ramp_config) setup_ramp_kit_ramp_data( - internal_ramp_config, problem_name, depth=1, - mock_html_conversion=True + internal_ramp_config, + problem_name, + depth=1, + mock_html_conversion=True, + ) + add_problem( + session_scope_function, + problem_name, + internal_ramp_config["ramp_kit_dir"], + internal_ramp_config["ramp_data_dir"], ) - add_problem(session_scope_function, problem_name, - internal_ramp_config['ramp_kit_dir'], - internal_ramp_config['ramp_data_dir']) for problem_name, ramp_config in ramp_configs.items(): internal_ramp_config = generate_ramp_config(ramp_config) - add_event(session_scope_function, problem_name, - internal_ramp_config['event_name'], - internal_ramp_config['event_title'], - internal_ramp_config['sandbox_name'], - internal_ramp_config['ramp_submissions_dir'], - is_public=True, force=False) + add_event( + session_scope_function, + problem_name, + internal_ramp_config["event_name"], + internal_ramp_config["event_title"], + internal_ramp_config["sandbox_name"], + internal_ramp_config["ramp_submissions_dir"], + is_public=True, + force=False, + ) event = get_event(session_scope_function, None) assert len(event) == 2 assert isinstance(event, list) - problem_name = 'iris' + problem_name = "iris" internal_ramp_config = generate_ramp_config(ramp_configs[problem_name]) - event = get_event( - session_scope_function, internal_ramp_config['event_name'] - ) - scores_iris = ('acc', 'error', 'nll', 'f1_70') + event = get_event(session_scope_function, internal_ramp_config["event_name"]) + scores_iris = ("acc", "error", "nll", "f1_70") _check_event( - session_scope_function, event, internal_ramp_config['event_name'], - internal_ramp_config['event_title'], True, scores_iris + session_scope_function, + event, + internal_ramp_config["event_name"], + internal_ramp_config["event_title"], + True, + scores_iris, ) # add event for second time without forcing should raise an error - err_msg = 'Attempting to overwrite existing event.' + err_msg = "Attempting to overwrite existing event." with pytest.raises(ValueError, match=err_msg): add_event( - session_scope_function, problem_name, - internal_ramp_config['event_name'], - internal_ramp_config['event_title'], - internal_ramp_config['sandbox_name'], - internal_ramp_config['ramp_submissions_dir'], + session_scope_function, + problem_name, + internal_ramp_config["event_name"], + internal_ramp_config["event_title"], + internal_ramp_config["sandbox_name"], + internal_ramp_config["ramp_submissions_dir"], is_public=True, - force=False + force=False, ) # add event by force add_event( - session_scope_function, problem_name, - internal_ramp_config['event_name'], - internal_ramp_config['event_title'], - internal_ramp_config['sandbox_name'], - internal_ramp_config['ramp_submissions_dir'], - is_public=True, force=True - ) - event = get_event( - session_scope_function, internal_ramp_config['event_name'] + session_scope_function, + problem_name, + internal_ramp_config["event_name"], + internal_ramp_config["event_title"], + internal_ramp_config["sandbox_name"], + internal_ramp_config["ramp_submissions_dir"], + is_public=True, + force=True, ) + event = get_event(session_scope_function, internal_ramp_config["event_name"]) _check_event( - session_scope_function, event, internal_ramp_config['event_name'], - internal_ramp_config['event_title'], True, scores_iris + session_scope_function, + event, + internal_ramp_config["event_name"], + internal_ramp_config["event_title"], + True, + scores_iris, ) - delete_event(session_scope_function, internal_ramp_config['event_name']) + delete_event(session_scope_function, internal_ramp_config["event_name"]) event = get_event(session_scope_function, None) assert len(event) == 1 @@ -278,13 +301,14 @@ def test_check_event(session_scope_function): err_msg = "The event name should start with the problem name: 'iris_'" with pytest.raises(ValueError, match=err_msg): add_event( - session_scope_function, problem_name, + session_scope_function, + problem_name, "xxxx", - internal_ramp_config['event_title'], - internal_ramp_config['sandbox_name'], - internal_ramp_config['ramp_submissions_dir'], + internal_ramp_config["event_title"], + internal_ramp_config["sandbox_name"], + internal_ramp_config["ramp_submissions_dir"], is_public=True, - force=False + force=False, ) @@ -293,33 +317,38 @@ def test_delete_event(session_scope_function): # Setup the problem/event # add sample problem - problem_name = 'iris' + problem_name = "iris" ramp_config = read_config(ramp_config_iris()) internal_ramp_config = generate_ramp_config(ramp_config) setup_ramp_kit_ramp_data(internal_ramp_config, problem_name, depth=1) - add_problem(session_scope_function, problem_name, - internal_ramp_config['ramp_kit_dir'], - internal_ramp_config['ramp_data_dir']) + add_problem( + session_scope_function, + problem_name, + internal_ramp_config["ramp_kit_dir"], + internal_ramp_config["ramp_data_dir"], + ) - event_name = 'iris_test_delete' + event_name = "iris_test_delete" # add sample event - add_event(session_scope_function, problem_name, - event_name, - internal_ramp_config['event_title'], - internal_ramp_config['sandbox_name'], - internal_ramp_config['ramp_submissions_dir'], - is_public=True, force=False) - - event = get_event(session_scope_function, - event_name) + add_event( + session_scope_function, + problem_name, + event_name, + internal_ramp_config["event_title"], + internal_ramp_config["sandbox_name"], + internal_ramp_config["ramp_submissions_dir"], + is_public=True, + force=False, + ) + + event = get_event(session_scope_function, event_name) assert event # add event-team - username = 'test_user' + username = "test_user" sign_up_team(session_scope_function, event_name, username) - event_team = get_event_team_by_name(session_scope_function, - event_name, username) + event_team = get_event_team_by_name(session_scope_function, event_name, username) assert event_team # add event admin @@ -336,6 +365,7 @@ def test_delete_event(session_scope_function): # add the submission to the event from ramp_database.tools.submission import get_submission_by_id + submission = get_submission_by_id(session_scope_function, event.id) assert submission @@ -349,8 +379,7 @@ def test_delete_event(session_scope_function): # make sure event and all the connections were deleted event_test = get_event(session_scope_function, None) assert len(event_test) == 0 - assert not get_event_team_by_name(session_scope_function, - event_name, username) + assert not get_event_team_by_name(session_scope_function, event_name, username) assert not get_event_admin(session_scope_function, event_name, username) event_score_types = get_score_type_by_event(session_scope_function, event) assert len(event_score_types) == 0 @@ -360,47 +389,54 @@ def test_delete_event(session_scope_function): def test_check_keyword(session_scope_function): - add_keyword(session_scope_function, 'keyword', 'data_domain') + add_keyword(session_scope_function, "keyword", "data_domain") keyword = get_keyword_by_name(session_scope_function, None) assert isinstance(keyword, list) assert len(keyword) == 1 - keyword = get_keyword_by_name(session_scope_function, 'keyword') + keyword = get_keyword_by_name(session_scope_function, "keyword") assert isinstance(keyword, Keyword) - assert keyword.name == 'keyword' - assert keyword.type == 'data_domain' + assert keyword.name == "keyword" + assert keyword.type == "data_domain" assert keyword.category is None assert keyword.description is None - err_msg = 'Attempting to update an existing keyword' + err_msg = "Attempting to update an existing keyword" with pytest.raises(ValueError, match=err_msg): - add_keyword(session_scope_function, 'keyword', 'data_domain') - - add_keyword(session_scope_function, 'keyword', 'data_science_theme', - category='some cat', description='new description', force=True) - keyword = get_keyword_by_name(session_scope_function, 'keyword') - assert keyword.type == 'data_science_theme' - assert keyword.category == 'some cat' - assert keyword.description == 'new description' + add_keyword(session_scope_function, "keyword", "data_domain") + + add_keyword( + session_scope_function, + "keyword", + "data_science_theme", + category="some cat", + description="new description", + force=True, + ) + keyword = get_keyword_by_name(session_scope_function, "keyword") + assert keyword.type == "data_science_theme" + assert keyword.category == "some cat" + assert keyword.description == "new description" def test_check_problem_keyword(session_toy_db): - add_keyword(session_toy_db, 'keyword', 'data_domain') - add_problem_keyword(session_toy_db, 'iris', 'keyword') - problem_keyword = get_problem_keyword_by_name( - session_toy_db, 'iris', 'keyword' - ) + add_keyword(session_toy_db, "keyword", "data_domain") + add_problem_keyword(session_toy_db, "iris", "keyword") + problem_keyword = get_problem_keyword_by_name(session_toy_db, "iris", "keyword") assert isinstance(problem_keyword, ProblemKeyword) - assert problem_keyword.problem.name == 'iris' - assert problem_keyword.keyword.name == 'keyword' + assert problem_keyword.problem.name == "iris" + assert problem_keyword.keyword.name == "keyword" assert problem_keyword.description is None - err_msg = 'Attempting to update an existing problem-keyword relationship' + err_msg = "Attempting to update an existing problem-keyword relationship" with pytest.raises(ValueError, match=err_msg): - add_problem_keyword(session_toy_db, 'iris', 'keyword') - - add_problem_keyword(session_toy_db, 'iris', 'keyword', - description='new description', force=True) - problem_keyword = get_problem_keyword_by_name( - session_toy_db, 'iris', 'keyword' + add_problem_keyword(session_toy_db, "iris", "keyword") + + add_problem_keyword( + session_toy_db, + "iris", + "keyword", + description="new description", + force=True, ) - assert problem_keyword.description == 'new description' + problem_keyword = get_problem_keyword_by_name(session_toy_db, "iris", "keyword") + assert problem_keyword.description == "new description" diff --git a/ramp-database/ramp_database/tools/tests/test_frontend.py b/ramp-database/ramp_database/tools/tests/test_frontend.py index 4d6ce5663..074cdf2dd 100644 --- a/ramp-database/ramp_database/tools/tests/test_frontend.py +++ b/ramp-database/ramp_database/tools/tests/test_frontend.py @@ -32,93 +32,99 @@ from ramp_database.tools.frontend import is_user_signed_up -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_toy_db(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_check_admin(session_toy_db): - event_name = 'iris_test' - user_name = 'test_iris_admin' + event_name = "iris_test" + user_name = "test_iris_admin" assert is_admin(session_toy_db, event_name, user_name) - user_name = 'test_user' + user_name = "test_user" assert not is_admin(session_toy_db, event_name, user_name) add_event_admin(session_toy_db, event_name, user_name) assert is_admin(session_toy_db, event_name, user_name) event_admin = get_event_admin(session_toy_db, event_name, user_name) assert event_admin.event.name == event_name assert event_admin.admin.name == user_name - user_name = 'test_user_2' + user_name = "test_user_2" assert get_event_admin(session_toy_db, event_name, user_name) is None @pytest.mark.parametrize( "event_name, user_name, is_accessible", - [('xxx', 'test_iris_admin', False), - ('iris_test', 'test_user', False), - ('iris_test', 'test_iris_admin', True), - ('iris_test', 'test_user_2', True), - ('boston_housing_test', 'test_user_2', False)] + [ + ("xxx", "test_iris_admin", False), + ("iris_test", "test_user", False), + ("iris_test", "test_iris_admin", True), + ("iris_test", "test_user_2", True), + ("boston_housing_test", "test_user_2", False), + ], ) -def test_is_accessible_event(session_toy_db, event_name, user_name, - is_accessible): +def test_is_accessible_event(session_toy_db, event_name, user_name, is_accessible): # force one of the user to not be approved - if user_name == 'test_user': + if user_name == "test_user": user = get_user_by_name(session_toy_db, user_name) - user.access_level = 'asked' + user.access_level = "asked" session_toy_db.commit() # force an event to be private - if event_name == 'boston_housing_test': + if event_name == "boston_housing_test": event = get_event(session_toy_db, event_name) event.is_public = False session_toy_db.commit() - assert is_accessible_event(session_toy_db, event_name, - user_name) is is_accessible + assert is_accessible_event(session_toy_db, event_name, user_name) is is_accessible @pytest.mark.parametrize( "event_name, user_name, is_accessible", - [('boston_housing', 'test_iris_admin', False), - ('boston_housing_test', 'test_iris_admin', False), - ('iris_test', 'test_user', True)] + [ + ("boston_housing", "test_iris_admin", False), + ("boston_housing_test", "test_iris_admin", False), + ("iris_test", "test_user", True), + ], ) def test_user_signed_up(session_toy_db, event_name, user_name, is_accessible): - assert is_user_signed_up(session_toy_db, event_name, - user_name) is is_accessible + assert is_user_signed_up(session_toy_db, event_name, user_name) is is_accessible @pytest.mark.parametrize( "event_name, user_name, asked", - [('boston_housing', 'test_iris_admin', False), - ('boston_housing_test', 'test_iris_admin', False), - ('iris_test', 'test_user', False)] + [ + ("boston_housing", "test_iris_admin", False), + ("boston_housing_test", "test_iris_admin", False), + ("iris_test", "test_user", False), + ], ) -def test_is_user_sign_up_requested(session_toy_db, event_name, user_name, - asked): - assert is_user_sign_up_requested(session_toy_db, event_name, - user_name) is asked +def test_is_user_sign_up_requested(session_toy_db, event_name, user_name, asked): + assert is_user_sign_up_requested(session_toy_db, event_name, user_name) is asked def test_is_accessible_code(session_toy_db): # create a third user add_user( - session_toy_db, name='test_user_3', password='test', - lastname='Test_3', firstname='User_3', - email='test.user.3@gmail.com', access_level='user') - approve_user(session_toy_db, 'test_user_3') - event_name = 'iris_test' - sign_up_team(session_toy_db, event_name, 'test_user_3') + session_toy_db, + name="test_user_3", + password="test", + lastname="Test_3", + firstname="User_3", + email="test.user.3@gmail.com", + access_level="user", + ) + approve_user(session_toy_db, "test_user_3") + event_name = "iris_test" + sign_up_team(session_toy_db, event_name, "test_user_3") # simulate a user which is not authenticated - user = get_user_by_name(session_toy_db, 'test_user_2') + user = get_user_by_name(session_toy_db, "test_user_2") user.is_authenticated = False assert not is_accessible_code(session_toy_db, event_name, user.name) # simulate a user which authenticated and author of the submission to a @@ -126,11 +132,11 @@ def test_is_accessible_code(session_toy_db): user.is_authenticated = True assert is_accessible_code(session_toy_db, event_name, user.name) # simulate an admin user - user = get_user_by_name(session_toy_db, 'test_iris_admin') + user = get_user_by_name(session_toy_db, "test_iris_admin") user.is_authenticated = True - assert is_accessible_code(session_toy_db, event_name, 'test_iris_admin') + assert is_accessible_code(session_toy_db, event_name, "test_iris_admin") # simulate a user which is not signed up to the event - user = add_user(session_toy_db, 'xx', 'xx', 'xx', 'xx', 'xx', 'user') + user = add_user(session_toy_db, "xx", "xx", "xx", "xx", "xx", "user") user.is_authenticated = True assert not is_accessible_code(session_toy_db, event_name, user.name) # simulate that the event is not publicly opened @@ -139,48 +145,42 @@ def test_is_accessible_code(session_toy_db): tomorrow = datetime.datetime.utcnow() + datetime.timedelta(days=1) event.public_opening_timestamp = tomorrow session_toy_db.commit() - assert is_accessible_code(session_toy_db, event_name, 'test_user_3') + assert is_accessible_code(session_toy_db, event_name, "test_user_3") # Make a submission - submission_name = 'random_forest_10_10' + submission_name = "random_forest_10_10" ramp_config = generate_ramp_config(read_config(ramp_config_template())) path_submission = os.path.join( - os.path.dirname(ramp_config['ramp_sandbox_dir']), submission_name + os.path.dirname(ramp_config["ramp_sandbox_dir"]), submission_name ) sub = add_submission( - session_toy_db, event_name, 'test_user_3', submission_name, - path_submission + session_toy_db, + event_name, + "test_user_3", + submission_name, + path_submission, ) # check that the user submitting the submission could access it - assert is_accessible_code( - session_toy_db, event_name, 'test_user_3', sub.id - ) + assert is_accessible_code(session_toy_db, event_name, "test_user_3", sub.id) # change the admin of the team from ramp_database.model import Team, User - team = (session_toy_db.query(Team) - .filter(Team.name == 'test_user_3') - .first()) - user = (session_toy_db.query(User) - .filter(User.name == 'test_user_2') - .first()) + + team = session_toy_db.query(Team).filter(Team.name == "test_user_3").first() + user = session_toy_db.query(User).filter(User.name == "test_user_2").first() team.admin_id = user.id team.admin = user session_toy_db.commit() # check that the admin can access the submission - assert is_accessible_code( - session_toy_db, event_name, 'test_user_2', sub.id - ) + assert is_accessible_code(session_toy_db, event_name, "test_user_2", sub.id) # but others cannot - assert not is_accessible_code( - session_toy_db, event_name, 'test_user_3', sub.id - ) + assert not is_accessible_code(session_toy_db, event_name, "test_user_3", sub.id) event.public_opening_timestamp = past_public_opening session_toy_db.commit() def test_is_accessible_leaderboard(session_toy_db): - event_name = 'iris_test' + event_name = "iris_test" # simulate a user which is not authenticated - user = get_user_by_name(session_toy_db, 'test_user_2') + user = get_user_by_name(session_toy_db, "test_user_2") user.is_authenticated = False assert not is_accessible_leaderboard(session_toy_db, event_name, user.name) # simulate a user which authenticated and author of the submission to a @@ -188,12 +188,10 @@ def test_is_accessible_leaderboard(session_toy_db): user.is_authenticated = True assert not is_accessible_leaderboard(session_toy_db, event_name, user.name) # simulate an admin user - user = get_user_by_name(session_toy_db, 'test_iris_admin') + user = get_user_by_name(session_toy_db, "test_iris_admin") user.is_authenticated = True - assert is_accessible_leaderboard(session_toy_db, event_name, - 'test_iris_admin') + assert is_accessible_leaderboard(session_toy_db, event_name, "test_iris_admin") # simulate a close event event = get_event(session_toy_db, event_name) event.closing_timestamp = datetime.datetime.utcnow() - assert not is_accessible_leaderboard(session_toy_db, event_name, - 'test_user_2') + assert not is_accessible_leaderboard(session_toy_db, event_name, "test_user_2") diff --git a/ramp-database/ramp_database/tools/tests/test_leaderboard.py b/ramp-database/ramp_database/tools/tests/test_leaderboard.py index 90e00cab7..cb2e11178 100644 --- a/ramp-database/ramp_database/tools/tests/test_leaderboard.py +++ b/ramp-database/ramp_database/tools/tests/test_leaderboard.py @@ -27,17 +27,17 @@ from ramp_database.tools.leaderboard import update_user_leaderboards -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_toy_db(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) @@ -47,23 +47,29 @@ def session_toy_function(database_connection): ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_update_leaderboard_functions(session_toy_function): - event_name = 'iris_test' - user_name = 'test_user' - for leaderboard_type in ['public', 'private', 'failed', - 'public competition', 'private competition']: - leaderboard = get_leaderboard(session_toy_function, leaderboard_type, - event_name) + event_name = "iris_test" + user_name = "test_user" + for leaderboard_type in [ + "public", + "private", + "failed", + "public competition", + "private competition", + ]: + leaderboard = get_leaderboard( + session_toy_function, leaderboard_type, event_name + ) assert leaderboard is None - leaderboard = get_leaderboard(session_toy_function, 'new', event_name) + leaderboard = get_leaderboard(session_toy_function, "new", event_name) assert leaderboard event = get_event(session_toy_function, event_name) @@ -75,15 +81,12 @@ def test_update_leaderboard_functions(session_toy_function): assert event.private_competition_leaderboard_html is None assert event.new_leaderboard_html - event_team = get_event_team_by_name(session_toy_function, event_name, - user_name) + event_team = get_event_team_by_name(session_toy_function, event_name, user_name) assert event_team.leaderboard_html is None assert event_team.failed_leaderboard_html is None assert event_team.new_leaderboard_html - event_teams = (session_toy_function.query(EventTeam) - .filter_by(event=event) - .all()) + event_teams = session_toy_function.query(EventTeam).filter_by(event=event).all() for et in event_teams: assert et.leaderboard_html is None assert et.failed_leaderboard_html is None @@ -92,9 +95,7 @@ def test_update_leaderboard_functions(session_toy_function): # run the dispatcher to process the different submissions config = read_config(database_config_template()) event_config = read_config(ramp_config_template()) - dispatcher = Dispatcher( - config, event_config, n_workers=-1, hunger_policy='exit' - ) + dispatcher = Dispatcher(config, event_config, n_workers=-1, hunger_policy="exit") dispatcher.launch() session_toy_function.commit() @@ -109,16 +110,13 @@ def test_update_leaderboard_functions(session_toy_function): assert event.new_leaderboard_html is None update_user_leaderboards(session_toy_function, event_name, user_name) - event_team = get_event_team_by_name(session_toy_function, event_name, - user_name) + event_team = get_event_team_by_name(session_toy_function, event_name, user_name) assert event_team.leaderboard_html assert event_team.failed_leaderboard_html assert event_team.new_leaderboard_html is None update_all_user_leaderboards(session_toy_function, event_name) - event_teams = (session_toy_function.query(EventTeam) - .filter_by(event=event) - .all()) + event_teams = session_toy_function.query(EventTeam).filter_by(event=event).all() for et in event_teams: assert et.leaderboard_html assert et.failed_leaderboard_html @@ -126,80 +124,87 @@ def test_update_leaderboard_functions(session_toy_function): @pytest.mark.parametrize( - 'leaderboard_type, expected_html', - [('new', not None), - ('public', None), - ('private', None), - ('failed', None), - ('public competition', None), - ('private competition', None)] + "leaderboard_type, expected_html", + [ + ("new", not None), + ("public", None), + ("private", None), + ("failed", None), + ("public competition", None), + ("private competition", None), + ], ) -def test_get_leaderboard_only_new_submissions(session_toy_db, leaderboard_type, - expected_html): +def test_get_leaderboard_only_new_submissions( + session_toy_db, leaderboard_type, expected_html +): # only check that the submission should be shown as new when the # dispatcher was not started. if expected_html is not None: - assert get_leaderboard(session_toy_db, leaderboard_type, 'iris_test') + assert get_leaderboard(session_toy_db, leaderboard_type, "iris_test") else: - assert (get_leaderboard( - session_toy_db, leaderboard_type, 'iris_test') is expected_html) + assert ( + get_leaderboard(session_toy_db, leaderboard_type, "iris_test") + is expected_html + ) def test_get_leaderboard(session_toy_db): - """ this test assumes that all the submissions in the database are 'new'""" - leaderboard_new = get_leaderboard(session_toy_db, 'new', 'iris_test') - assert leaderboard_new.count('') == 6 - leaderboard_new = get_leaderboard(session_toy_db, 'new', 'iris_test', - 'test_user') - assert leaderboard_new.count('') == 3 + """this test assumes that all the submissions in the database are 'new'""" + leaderboard_new = get_leaderboard(session_toy_db, "new", "iris_test") + assert leaderboard_new.count("") == 6 + leaderboard_new = get_leaderboard(session_toy_db, "new", "iris_test", "test_user") + assert leaderboard_new.count("") == 3 # run the dispatcher to process the different submissions config = read_config(database_config_template()) event_config = read_config(ramp_config_template()) - dispatcher = Dispatcher( - config, event_config, n_workers=-1, hunger_policy='exit' - ) + dispatcher = Dispatcher(config, event_config, n_workers=-1, hunger_policy="exit") dispatcher.launch() session_toy_db.commit() - assert get_leaderboard(session_toy_db, 'new', 'iris_test') is None + assert get_leaderboard(session_toy_db, "new", "iris_test") is None # the iris dataset has a single submission which is failing - leaderboard_failed = get_leaderboard(session_toy_db, 'failed', 'iris_test') - assert leaderboard_failed.count('') == 2 - leaderboard_failed = get_leaderboard(session_toy_db, 'failed', 'iris_test', - 'test_user') - assert leaderboard_failed.count('') == 1 + leaderboard_failed = get_leaderboard(session_toy_db, "failed", "iris_test") + assert leaderboard_failed.count("") == 2 + leaderboard_failed = get_leaderboard( + session_toy_db, "failed", "iris_test", "test_user" + ) + assert leaderboard_failed.count("") == 1 # the remaining submission should be successful - leaderboard_public = get_leaderboard(session_toy_db, 'public', 'iris_test') - assert leaderboard_public.count('') == 4 - leaderboard_public = get_leaderboard(session_toy_db, 'public', 'iris_test', - 'test_user') - assert leaderboard_public.count('') == 2 - - leaderboard_private = get_leaderboard(session_toy_db, 'private', - 'iris_test') - assert leaderboard_private.count('') == 4 - leaderboard_private = get_leaderboard(session_toy_db, 'private', - 'iris_test', 'test_user') - assert leaderboard_private.count('') == 2 + leaderboard_public = get_leaderboard(session_toy_db, "public", "iris_test") + assert leaderboard_public.count("") == 4 + leaderboard_public = get_leaderboard( + session_toy_db, "public", "iris_test", "test_user" + ) + assert leaderboard_public.count("") == 2 + + leaderboard_private = get_leaderboard(session_toy_db, "private", "iris_test") + assert leaderboard_private.count("") == 4 + leaderboard_private = get_leaderboard( + session_toy_db, "private", "iris_test", "test_user" + ) + assert leaderboard_private.count("") == 2 # the competition leaderboard will have the best solution for each user - competition_public = get_leaderboard(session_toy_db, 'public competition', - 'iris_test') - assert competition_public.count('') == 2 - competition_private = get_leaderboard(session_toy_db, - 'private competition', 'iris_test') - assert competition_private.count('') == 2 + competition_public = get_leaderboard( + session_toy_db, "public competition", "iris_test" + ) + assert competition_public.count("") == 2 + competition_private = get_leaderboard( + session_toy_db, "private competition", "iris_test" + ) + assert competition_private.count("") == 2 # check the difference between the public and private leaderboard - assert leaderboard_private.count('') > leaderboard_public.count('') - for private_term in ['bag', 'mean', 'std', 'private']: + assert leaderboard_private.count("") > leaderboard_public.count("") + for private_term in ["bag", "mean", "std", "private"]: assert private_term not in leaderboard_public assert private_term in leaderboard_private # check the column name in each leaderboard - assert """submission ID + assert ( + """submission ID team submission bag public acc @@ -230,8 +235,11 @@ def test_get_leaderboard(session_toy_db): validation time [s] test time [s] max RAM [MB] - submitted at (UTC)""" in leaderboard_private - assert """team + submitted at (UTC)""" + in leaderboard_private + ) + assert ( + """team submission acc error @@ -240,21 +248,30 @@ def test_get_leaderboard(session_toy_db): train time [s] validation time [s] max RAM [MB] - submitted at (UTC)""" in leaderboard_public - assert """team + submitted at (UTC)""" + in leaderboard_public + ) + assert ( + """team submission submitted at (UTC) - error""" in leaderboard_failed + error""" + in leaderboard_failed + ) # check the same for the competition leaderboard - assert """rank + assert ( + """rank team submission acc train time [s] validation time [s] - submitted at (UTC)""" in competition_public - assert """rank + submitted at (UTC)""" + in competition_public + ) + assert ( + """rank move team submission @@ -262,24 +279,21 @@ def test_get_leaderboard(session_toy_db): train time [s] validation time [s] test time [s] - submitted at (UTC)""" in competition_private + submitted at (UTC)""" + in competition_private + ) @pytest.mark.parametrize( - 'event_name, expected_size', - [('iris_test', 4), - ('iris_aws_test', 0), - ('boston_housing_test', 0)] + "event_name, expected_size", + [("iris_test", 4), ("iris_aws_test", 0), ("boston_housing_test", 0)], ) -def test_export_leaderboard_to_dataframe(session_toy_db, - event_name, expected_size): - """ it will run iris_test if it was not run previously, ie - test test_get_leaderboard already run """ +def test_export_leaderboard_to_dataframe(session_toy_db, event_name, expected_size): + """it will run iris_test if it was not run previously, ie + test test_get_leaderboard already run""" config = read_config(database_config_template()) event_config = read_config(ramp_config_template()) - dispatcher = Dispatcher( - config, event_config, n_workers=-1, hunger_policy='exit' - ) + dispatcher = Dispatcher(config, event_config, n_workers=-1, hunger_policy="exit") dispatcher.launch() session_toy_db.commit() @@ -287,9 +301,11 @@ def test_export_leaderboard_to_dataframe(session_toy_db, # assert only submissions with the event_name assert leaderboard.shape[0] == expected_size - submissions = (session_toy_db.query(Submission) - .filter(Event.name == event_name) - .filter(Event.id == EventTeam.event_id) - .filter(EventTeam.id == Submission.event_team_id) - .filter(Submission.state == 'scored')).all() + submissions = ( + session_toy_db.query(Submission) + .filter(Event.name == event_name) + .filter(Event.id == EventTeam.event_id) + .filter(EventTeam.id == Submission.event_team_id) + .filter(Submission.state == "scored") + ).all() assert len(submissions) == leaderboard.shape[0] diff --git a/ramp-database/ramp_database/tools/tests/test_query.py b/ramp-database/ramp_database/tools/tests/test_query.py index 1e230b454..981195266 100644 --- a/ramp-database/ramp_database/tools/tests/test_query.py +++ b/ramp-database/ramp_database/tools/tests/test_query.py @@ -15,32 +15,29 @@ select_submission_by_name, select_submission_by_id, select_user_by_name, - ) def _change_state_db(session): # change the state of one of the submission in the iris event submission_id = 1 - sub = (session.query(Submission) - .filter(Submission.id == submission_id) - .first()) - sub.set_state('trained') + sub = session.query(Submission).filter(Submission.id == submission_id).first() + sub.set_state("trained") session.commit() -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: _change_state_db(session) yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) @@ -60,8 +57,7 @@ def test_select_submissions_by_state(session_scope_module): def test_select_submissions_by_name(session_scope_module): session = session_scope_module - res = select_submission_by_name(session, "iris_test", "test_user_2", - "starting_kit") + res = select_submission_by_name(session, "iris_test", "test_user_2", "starting_kit") assert isinstance(res, Submission) res = select_submission_by_name(session, "unknown", "unknown", "unknown") diff --git a/ramp-database/ramp_database/tools/tests/test_submission.py b/ramp-database/ramp_database/tools/tests/test_submission.py index 5e472a991..d881d1dc1 100644 --- a/ramp-database/ramp_database/tools/tests/test_submission.py +++ b/ramp-database/ramp_database/tools/tests/test_submission.py @@ -76,36 +76,34 @@ def base_db(database_connection): ramp_config = ramp_config_template() try: deployment_dir = create_test_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def _change_state_db(session): # change the state of one of the submission in the iris event submission_id = 1 - sub = (session.query(Submission) - .filter(Submission.id == submission_id) - .first()) - sub.set_state('trained') + sub = session.query(Submission).filter(Submission.id == submission_id).first() + sub.set_state("trained") session.commit() -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def session_scope_module(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: _change_state_db(session) yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) @@ -115,7 +113,7 @@ def _setup_sign_up(session): add_problems(session) add_events(session) sign_up_teams_to_events(session) - return 'iris_test', 'test_user' + return "iris_test", "test_user" def test_add_submission_create_new_submission(base_db): @@ -126,18 +124,17 @@ def test_add_submission_create_new_submission(base_db): event_name, username = _setup_sign_up(session) ramp_config = generate_ramp_config(read_config(config)) - submission_name = 'random_forest_10_10' + submission_name = "random_forest_10_10" path_submission = os.path.join( - os.path.dirname(ramp_config['ramp_sandbox_dir']), submission_name + os.path.dirname(ramp_config["ramp_sandbox_dir"]), submission_name ) - add_submission(session, event_name, username, submission_name, - path_submission) + add_submission(session, event_name, username, submission_name, path_submission) all_submissions = get_submissions(session, event_name, None) # check that the submissions have been copied for sub_id, _, _ in all_submissions: sub = get_submission_by_id(session, sub_id) assert os.path.exists(sub.path) - assert os.path.exists(os.path.join(sub.path, 'estimator.py')) + assert os.path.exists(os.path.join(sub.path, "estimator.py")) # `sign_up_team` make a submission (sandbox) by user. This submission will # be the third submission. @@ -145,14 +142,15 @@ def test_add_submission_create_new_submission(base_db): # check that the number of submissions for an event was updated event = session.query(Event).filter(Event.name == event_name).one_or_none() assert event.n_submissions == 1 - submission = get_submission_by_name(session, event_name, username, - submission_name) + submission = get_submission_by_name(session, event_name, username, submission_name) assert submission.name == submission_name submission_file = submission.files[0] - assert submission_file.name == 'estimator' - assert submission_file.extension == 'py' - assert (os.path.join('submission_00000000' + str(ID_SUBMISSION), - 'estimator.py') in submission_file.path) + assert submission_file.name == "estimator" + assert submission_file.extension == "py" + assert ( + os.path.join("submission_00000000" + str(ID_SUBMISSION), "estimator.py") + in submission_file.path + ) def test_add_submission_too_early_submission(base_db): @@ -164,25 +162,30 @@ def test_add_submission_too_early_submission(base_db): ramp_config = generate_ramp_config(read_config(config)) # check that we have an awaiting time for the event - event = (session.query(Event) - .filter(Event.name == event_name) - .one_or_none()) + event = session.query(Event).filter(Event.name == event_name).one_or_none() assert event.min_duration_between_submissions == 900 # make 2 submissions which are too close from each other - for submission_idx, submission_name in enumerate(['random_forest_10_10', - 'too_early_submission']): + for submission_idx, submission_name in enumerate( + ["random_forest_10_10", "too_early_submission"] + ): path_submission = os.path.join( - os.path.dirname(ramp_config['ramp_sandbox_dir']), submission_name + os.path.dirname(ramp_config["ramp_sandbox_dir"]), submission_name ) if submission_idx == 1: - err_msg = 'You need to wait' + err_msg = "You need to wait" with pytest.raises(TooEarlySubmissionError, match=err_msg): - add_submission(session, event_name, username, submission_name, - path_submission) + add_submission( + session, + event_name, + username, + submission_name, + path_submission, + ) else: - add_submission(session, event_name, username, submission_name, - path_submission) + add_submission( + session, event_name, username, submission_name, path_submission + ) def test_make_submission_resubmission(base_db): @@ -195,40 +198,50 @@ def test_make_submission_resubmission(base_db): # submitting the starting_kit which is used as the default submission for # the sandbox should raise an error - err_msg = ('Submission "starting_kit" of team "test_user" at event ' - '"iris_test" exists already') + err_msg = ( + 'Submission "starting_kit" of team "test_user" at event ' + '"iris_test" exists already' + ) with pytest.raises(DuplicateSubmissionError, match=err_msg): - add_submission(session, event_name, username, - os.path.basename(ramp_config['ramp_sandbox_dir']), - ramp_config['ramp_sandbox_dir']) + add_submission( + session, + event_name, + username, + os.path.basename(ramp_config["ramp_sandbox_dir"]), + ramp_config["ramp_sandbox_dir"], + ) # submitting twice a normal submission should raise an error as well - submission_name = 'random_forest_10_10' + submission_name = "random_forest_10_10" path_submission = os.path.join( - os.path.dirname(ramp_config['ramp_sandbox_dir']), submission_name + os.path.dirname(ramp_config["ramp_sandbox_dir"]), submission_name ) # first submission - add_submission(session, event_name, username, submission_name, - path_submission,) + add_submission( + session, + event_name, + username, + submission_name, + path_submission, + ) # mock that we scored the submission - set_submission_state(session, ID_SUBMISSION, 'scored') + set_submission_state(session, ID_SUBMISSION, "scored") # second submission - err_msg = ('Submission "random_forest_10_10" of team "test_user" at event ' - '"iris_test" exists already') + err_msg = ( + 'Submission "random_forest_10_10" of team "test_user" at event ' + '"iris_test" exists already' + ) with pytest.raises(DuplicateSubmissionError, match=err_msg): - add_submission(session, event_name, username, submission_name, - path_submission) + add_submission(session, event_name, username, submission_name, path_submission) # a resubmission can take place if it is tagged as "new" or failed # mock that the submission failed during the training - set_submission_state(session, ID_SUBMISSION, 'training_error') - add_submission(session, event_name, username, submission_name, - path_submission) + set_submission_state(session, ID_SUBMISSION, "training_error") + add_submission(session, event_name, username, submission_name, path_submission) # mock that the submissions are new submissions - set_submission_state(session, ID_SUBMISSION, 'new') - add_submission(session, event_name, username, submission_name, - path_submission) + set_submission_state(session, ID_SUBMISSION, "new") + add_submission(session, event_name, username, submission_name, path_submission) def test_add_submission_wrong_submission_files(base_db): @@ -239,33 +252,30 @@ def test_add_submission_wrong_submission_files(base_db): event_name, username = _setup_sign_up(session) ramp_config = generate_ramp_config(read_config(config)) - submission_name = 'corrupted_submission' + submission_name = "corrupted_submission" path_submission = os.path.join( - os.path.dirname(ramp_config['ramp_sandbox_dir']), submission_name + os.path.dirname(ramp_config["ramp_sandbox_dir"]), submission_name ) os.makedirs(path_submission) # case that there is not files in the submission - err_msg = 'No file corresponding to the workflow element' + err_msg = "No file corresponding to the workflow element" with pytest.raises(MissingSubmissionFileError, match=err_msg): - add_submission(session, event_name, username, submission_name, - path_submission) + add_submission(session, event_name, username, submission_name, path_submission) # case that there is not file corresponding to the workflow component - filename = os.path.join(path_submission, 'unknown_file.xxx') + filename = os.path.join(path_submission, "unknown_file.xxx") open(filename, "w+").close() - err_msg = 'No file corresponding to the workflow element' + err_msg = "No file corresponding to the workflow element" with pytest.raises(MissingSubmissionFileError, match=err_msg): - add_submission(session, event_name, username, submission_name, - path_submission) + add_submission(session, event_name, username, submission_name, path_submission) # case that we have the correct filename but not the right extension - filename = os.path.join(path_submission, 'estimator.xxx') + filename = os.path.join(path_submission, "estimator.xxx") open(filename, "w+").close() err_msg = 'All extensions "xxx" are unknown for the submission' with pytest.raises(MissingExtensionError, match=err_msg): - add_submission(session, event_name, username, submission_name, - path_submission) + add_submission(session, event_name, username, submission_name, path_submission) def test_submit_starting_kits(base_db): @@ -274,111 +284,145 @@ def test_submit_starting_kits(base_db): event_name, username = _setup_sign_up(session) ramp_config = generate_ramp_config(read_config(config)) - submit_starting_kits(session, event_name, username, - ramp_config['ramp_kit_submissions_dir']) + submit_starting_kits( + session, event_name, username, ramp_config["ramp_kit_submissions_dir"] + ) submissions = get_submissions(session, event_name, None) submissions_id = [sub[0] for sub in submissions] assert len(submissions) == 5 - expected_submission_name = {'starting_kit', 'starting_kit_test', - 'random_forest_10_10', 'error'} - submission_name = {get_submission_by_id(session, sub_id).name - for sub_id in submissions_id} + expected_submission_name = { + "starting_kit", + "starting_kit_test", + "random_forest_10_10", + "error", + } + submission_name = { + get_submission_by_id(session, sub_id).name for sub_id in submissions_id + } assert submission_name == expected_submission_name @pytest.mark.parametrize( "state, expected_id", - [('new', [2, 7, 8, 9, 10, 11, 12]), - ('trained', [1]), - ('tested', []), - (None, [1, 2, 7, 8, 9, 10, 11, 12])] + [ + ("new", [2, 7, 8, 9, 10, 11, 12]), + ("trained", [1]), + ("tested", []), + (None, [1, 2, 7, 8, 9, 10, 11, 12]), + ], ) def test_get_submissions(session_scope_module, state, expected_id): - submissions = get_submissions(session_scope_module, 'iris_test', - state=state) + submissions = get_submissions(session_scope_module, "iris_test", state=state) assert len(submissions) == len(expected_id) for submission_id, sub_name, sub_path in submissions: assert submission_id in expected_id - assert 'submission_{:09d}'.format(submission_id) == sub_name - path_file = os.path.join('submission_{:09d}'.format(submission_id), - 'estimator.py') + assert "submission_{:09d}".format(submission_id) == sub_name + path_file = os.path.join( + "submission_{:09d}".format(submission_id), "estimator.py" + ) assert path_file in sub_path[0] def test_get_submission_unknown_state(session_scope_module): - with pytest.raises(UnknownStateError, match='Unrecognized state'): - get_submissions(session_scope_module, 'iris_test', state='whatever') + with pytest.raises(UnknownStateError, match="Unrecognized state"): + get_submissions(session_scope_module, "iris_test", state="whatever") def test_get_submission_by_id(session_scope_module): submission = get_submission_by_id(session_scope_module, 1) assert isinstance(submission, Submission) - assert submission.basename == 'submission_000000001' - assert os.path.exists(os.path.join(submission.path, 'estimator.py')) - assert submission.state == 'trained' + assert submission.basename == "submission_000000001" + assert os.path.exists(os.path.join(submission.path, "estimator.py")) + assert submission.state == "trained" def test_get_submission_by_name(session_scope_module): - submission = get_submission_by_name(session_scope_module, 'iris_test', - 'test_user', 'starting_kit') + submission = get_submission_by_name( + session_scope_module, "iris_test", "test_user", "starting_kit" + ) assert isinstance(submission, Submission) - assert submission.basename == 'submission_000000001' - assert os.path.exists(os.path.join(submission.path, 'estimator.py')) - assert submission.state == 'trained' + assert submission.basename == "submission_000000001" + assert os.path.exists(os.path.join(submission.path, "estimator.py")) + assert submission.state == "trained" def test_get_event_nb_folds(session_scope_module): - assert get_event_nb_folds(session_scope_module, 'iris_test') == 2 + assert get_event_nb_folds(session_scope_module, "iris_test") == 2 -@pytest.mark.parametrize("submission_id, state", [(1, 'trained'), (2, 'new')]) +@pytest.mark.parametrize("submission_id, state", [(1, "trained"), (2, "new")]) def test_get_submission_state(session_scope_module, submission_id, state): assert get_submission_state(session_scope_module, submission_id) == state def test_set_submission_state(session_scope_module): submission_id = 2 - set_submission_state(session_scope_module, submission_id, 'trained') + set_submission_state(session_scope_module, submission_id, "trained") state = get_submission_state(session_scope_module, submission_id) - assert state == 'trained' + assert state == "trained" def test_set_submission_state_unknown_state(session_scope_module): - with pytest.raises(UnknownStateError, match='Unrecognized state'): - set_submission_state(session_scope_module, 2, 'unknown') + with pytest.raises(UnknownStateError, match="Unrecognized state"): + set_submission_state(session_scope_module, 2, "unknown") def test_check_time(session_scope_module): # check both set_time and get_time function submission_id = 1 - path_results = os.path.join(HERE, 'data', 'iris_predictions') + path_results = os.path.join(HERE, "data", "iris_predictions") set_time(session_scope_module, submission_id, path_results) submission_time = get_time(session_scope_module, submission_id) expected_df = pd.DataFrame( - {'fold': [0, 1], - 'train': [0.032130, 0.002414], - 'valid': [0.000583648681640625, 0.000548362731933594], - 'test': [0.000515460968017578, 0.000481128692626953]} - ).set_index('fold') + { + "fold": [0, 1], + "train": [0.032130, 0.002414], + "valid": [0.000583648681640625, 0.000548362731933594], + "test": [0.000515460968017578, 0.000481128692626953], + } + ).set_index("fold") assert_frame_equal(submission_time, expected_df, check_less_precise=True) def test_check_scores(session_scope_module): # check both set_scores and get_scores submission_id = 1 - path_results = os.path.join(HERE, 'data', 'iris_predictions') + path_results = os.path.join(HERE, "data", "iris_predictions") set_scores(session_scope_module, submission_id, path_results) scores = get_scores(session_scope_module, submission_id) multi_index = pd.MultiIndex.from_product( - [[0, 1], ['train', 'valid', 'test']], names=['fold', 'step'] + [[0, 1], ["train", "valid", "test"]], names=["fold", "step"] ) expected_df = pd.DataFrame( - {'acc': [0.604167, 0.583333, 0.733333, 0.604167, 0.583333, 0.733333], - 'error': [0.395833, 0.416667, 0.266667, 0.395833, 0.416667, 0.266667], - 'nll': [0.732763, 2.194549, 0.693464, 0.746132, 2.030762, 0.693992], - 'f1_70': [0.333333, 0.33333, 0.666667, 0.33333, 0.33333, 0.666667]}, - index=multi_index + { + "acc": [ + 0.604167, + 0.583333, + 0.733333, + 0.604167, + 0.583333, + 0.733333, + ], + "error": [ + 0.395833, + 0.416667, + 0.266667, + 0.395833, + 0.416667, + 0.266667, + ], + "nll": [ + 0.732763, + 2.194549, + 0.693464, + 0.746132, + 2.030762, + 0.693992, + ], + "f1_70": [0.333333, 0.33333, 0.666667, 0.33333, 0.33333, 0.666667], + }, + index=multi_index, ) assert_frame_equal(scores, expected_df, check_less_precise=True) @@ -386,21 +430,34 @@ def test_check_scores(session_scope_module): def test_check_bagged_scores(session_scope_module): # check both set_bagged_scores and get_bagged_scores submission_id = 1 - path_results = os.path.join(HERE, 'data', 'iris_predictions') + path_results = os.path.join(HERE, "data", "iris_predictions") set_bagged_scores(session_scope_module, submission_id, path_results) scores = get_bagged_scores(session_scope_module, submission_id) - multi_index = pd.MultiIndex(levels=[['test', 'valid'], [0, 1]], - codes=[[0, 0, 1, 1], [0, 1, 0, 1]], - names=['step', 'n_bag']) + multi_index = pd.MultiIndex( + levels=[["test", "valid"], [0, 1]], + codes=[[0, 0, 1, 1], [0, 1, 0, 1]], + names=["step", "n_bag"], + ) expected_df = pd.DataFrame( - {'acc': [0.70833333333, 0.70833333333, 0.65, 0.6486486486486], - 'error': [0.29166666667, 0.29166666667, 0.35, 0.35135135135], - 'nll': [0.80029268745, 0.66183018275, 0.52166532641, 0.58510855181], - 'f1_70': [0.66666666667, 0.33333333333, 0.33333333333, - 0.33333333333]}, - index=multi_index + { + "acc": [0.70833333333, 0.70833333333, 0.65, 0.6486486486486], + "error": [0.29166666667, 0.29166666667, 0.35, 0.35135135135], + "nll": [ + 0.80029268745, + 0.66183018275, + 0.52166532641, + 0.58510855181, + ], + "f1_70": [ + 0.66666666667, + 0.33333333333, + 0.33333333333, + 0.33333333333, + ], + }, + index=multi_index, ) - expected_df.columns = expected_df.columns.rename('scores') + expected_df.columns = expected_df.columns.rename("scores") assert_frame_equal(scores, expected_df, check_less_precise=True) @@ -416,9 +473,8 @@ def test_check_submission_max_ram(session_scope_module): def test_check_submission_error_msg(session_scope_module): # check both get_submission_error_msg and set_submission_error_msg submission_id = 1 - expected_err_msg = 'tag submission as failed' - set_submission_error_msg(session_scope_module, submission_id, - expected_err_msg) + expected_err_msg = "tag submission as failed" + set_submission_error_msg(session_scope_module, submission_id, expected_err_msg) err_msg = get_submission_error_msg(session_scope_module, submission_id) assert err_msg == expected_err_msg @@ -436,8 +492,11 @@ def test_get_source_submission(session_scope_module): event = submission.event_team.event user = submission.event_team.team.admin add_user_interaction( - session_scope_module, user=user, interaction='looking at submission', - event=event, submission=get_submission_by_id(session_scope_module, 2) + session_scope_module, + user=user, + interaction="looking at submission", + event=event, + submission=get_submission_by_id(session_scope_module, 2), ) submissions = get_source_submissions(session_scope_module, submission_id) assert not submissions @@ -446,21 +505,26 @@ def test_get_source_submission(session_scope_module): submission.submission_timestamp += datetime.timedelta(days=1) submissions = get_source_submissions(session_scope_module, submission_id) assert submissions - assert all([sub.event_team.event.name == event.name - for sub in submissions]) + assert all([sub.event_team.event.name == event.name for sub in submissions]) def test_add_submission_similarity(session_scope_module): - user = get_user_by_name(session_scope_module, 'test_user') + user = get_user_by_name(session_scope_module, "test_user") source_submission = get_submission_by_id(session_scope_module, 1) target_submission = get_submission_by_id(session_scope_module, 2) - add_submission_similarity(session_scope_module, 'target_credit', user, - source_submission, target_submission, 0.5, - datetime.datetime.utcnow()) + add_submission_similarity( + session_scope_module, + "target_credit", + user, + source_submission, + target_submission, + 0.5, + datetime.datetime.utcnow(), + ) similarity = session_scope_module.query(SubmissionSimilarity).all() assert len(similarity) == 1 similarity = similarity[0] - assert similarity.type == 'target_credit' + assert similarity.type == "target_credit" assert similarity.user == user assert similarity.source_submission == source_submission assert similarity.target_submission == target_submission @@ -473,13 +537,14 @@ def test_compute_contributivity(session_scope_module): config = ramp_config_template() ramp_config = generate_ramp_config(read_config(config)) - ramp_kit_dir = ramp_config['ramp_kit_dir'] - ramp_data_dir = ramp_config['ramp_data_dir'] - ramp_submission_dir = ramp_config['ramp_submissions_dir'] - ramp_predictions_dir = ramp_config['ramp_predictions_dir'] + ramp_kit_dir = ramp_config["ramp_kit_dir"] + ramp_data_dir = ramp_config["ramp_data_dir"] + ramp_submission_dir = ramp_config["ramp_submissions_dir"] + ramp_predictions_dir = ramp_config["ramp_predictions_dir"] - submission = get_submission_by_name(session_scope_module, 'iris_test', - 'test_user', 'starting_kit') + submission = get_submission_by_name( + session_scope_module, "iris_test", "test_user", "starting_kit" + ) # for testing blending, we need to train a submission # ouputting predictions into the submission directory @@ -488,22 +553,21 @@ def test_compute_contributivity(session_scope_module): ramp_data_dir=ramp_data_dir, ramp_submission_dir=ramp_submission_dir, submission=submission.basename, - save_output=True) + save_output=True, + ) # Mark the submission as scored in the DB - sub = (session.query(Submission) - .filter(Submission.id == submission.id) - .first()) - sub.set_state('scored') + sub = session.query(Submission).filter(Submission.id == submission.id).first() + sub.set_state("scored") session.commit() compute_contributivity( - session, 'iris_test', - ramp_kit_dir, ramp_data_dir, ramp_predictions_dir) - submissions = get_submissions(session, 'iris_test', 'scored') + session, "iris_test", ramp_kit_dir, ramp_data_dir, ramp_predictions_dir + ) + submissions = get_submissions(session, "iris_test", "scored") assert len(submissions) s = get_submission_by_id(session, submissions[0][0]) assert s.contributivity == pytest.approx(1.0) for s_on_cv_fold in s.on_cv_folds: s_on_cv_fold.contributivity == pytest.approx(1.0) - compute_historical_contributivity(session, 'iris_test') + compute_historical_contributivity(session, "iris_test") diff --git a/ramp-database/ramp_database/tools/tests/test_team.py b/ramp-database/ramp_database/tools/tests/test_team.py index 070c71bd2..4626a628b 100644 --- a/ramp-database/ramp_database/tools/tests/test_team.py +++ b/ramp-database/ramp_database/tools/tests/test_team.py @@ -34,19 +34,19 @@ def session_scope_function(database_connection): ramp_config = ramp_config_template() try: deployment_dir = create_test_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: add_users(session) add_problems(session) add_events(session) yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_ask_sign_up_team(session_scope_function): - event_name, username = 'iris_test', 'test_user' + event_name, username = "iris_test", "test_user" ask_sign_up_team(session_scope_function, event_name, username) event_team = session_scope_function.query(EventTeam).all() @@ -64,7 +64,7 @@ def test_ask_sign_up_team(session_scope_function): def test_sign_up_team(session_scope_function): - event_name, username = 'iris_test', 'test_user' + event_name, username = "iris_test", "test_user" sign_up_team(session_scope_function, event_name, username) event_team = session_scope_function.query(EventTeam).all() @@ -73,30 +73,28 @@ def test_sign_up_team(session_scope_function): # when signing up a team, the team is approved and the sandbox is setup: # the starting kit is submitted without training it. - assert event_team.last_submission_name == 'starting_kit' + assert event_team.last_submission_name == "starting_kit" assert event_team.approved is True # check the status of the sandbox submission submission = session_scope_function.query(Submission).all() assert len(submission) == 1 submission = submission[0] - assert submission.name == 'starting_kit' + assert submission.name == "starting_kit" assert submission.event_team == event_team submission_file = submission.files[0] - assert submission_file.name == 'estimator' - assert submission_file.extension == 'py' - assert (os.path.join('submission_000000001', - 'estimator.py') in submission_file.path) + assert submission_file.name == "estimator" + assert submission_file.extension == "py" + assert os.path.join("submission_000000001", "estimator.py") in submission_file.path # check the submission on cv fold - cv_folds = (session_scope_function.query(SubmissionOnCVFold) - .all()) + cv_folds = session_scope_function.query(SubmissionOnCVFold).all() for fold in cv_folds: - assert fold.state == 'new' + assert fold.state == "new" assert fold.best is False assert fold.contributivity == pytest.approx(0) def test_delete_event_team(session_scope_function): - event_name, username = 'iris_test', 'test_user' + event_name, username = "iris_test", "test_user" sign_up_team(session_scope_function, event_name, username) event_team = session_scope_function.query(EventTeam).all() @@ -107,11 +105,7 @@ def test_delete_event_team(session_scope_function): assert len(event_team) == 0 # check that the user still exist - user = (session_scope_function.query(User) - .filter(User.name == username) - .all()) + user = session_scope_function.query(User).filter(User.name == username).all() assert len(user) == 1 - event = (session_scope_function.query(Event) - .filter(Event.name == event_name) - .all()) + event = session_scope_function.query(Event).filter(Event.name == event_name).all() assert len(event) == 1 diff --git a/ramp-database/ramp_database/tools/tests/test_user.py b/ramp-database/ramp_database/tools/tests/test_user.py index 1e28e2acb..5cc572670 100644 --- a/ramp-database/ramp_database/tools/tests/test_user.py +++ b/ramp-database/ramp_database/tools/tests/test_user.py @@ -36,24 +36,30 @@ def session_scope_function(database_connection): ramp_config = ramp_config_template() try: deployment_dir = create_test_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_add_user(session_scope_function): - name = 'test_user' - password = 'test' - lastname = 'Test' - firstname = 'User' - email = 'test.user@gmail.com' - access_level = 'asked' - add_user(session_scope_function, name=name, password=password, - lastname=lastname, firstname=firstname, email=email, - access_level=access_level) + name = "test_user" + password = "test" + lastname = "Test" + firstname = "User" + email = "test.user@gmail.com" + access_level = "asked" + add_user( + session_scope_function, + name=name, + password=password, + lastname=lastname, + firstname=firstname, + email=email, + access_level=access_level, + ) user = get_user_by_name(session_scope_function, name) assert user.name == name assert check_password(password, user.hashed_password) @@ -66,69 +72,98 @@ def test_add_user(session_scope_function): assert team.name == name assert team.admin_id == user.id # check that we get an error if we try to add the same user - with pytest.raises(NameClashError, match='email is already in use'): - add_user(session_scope_function, name=name, password=password, - lastname=lastname, firstname=firstname, email=email, - access_level=access_level) + with pytest.raises(NameClashError, match="email is already in use"): + add_user( + session_scope_function, + name=name, + password=password, + lastname=lastname, + firstname=firstname, + email=email, + access_level=access_level, + ) # check that the checking is case insensitive - with pytest.raises(NameClashError, match='email is already in use'): - add_user(session_scope_function, name=name, password=password, - lastname=lastname, firstname=firstname, - email=email.capitalize(), access_level=access_level) + with pytest.raises(NameClashError, match="email is already in use"): + add_user( + session_scope_function, + name=name, + password=password, + lastname=lastname, + firstname=firstname, + email=email.capitalize(), + access_level=access_level, + ) # add a user email with some capital letters and check that only lower case # are stored in the database - name = 'new_user_name' - email = 'MixCase@mail.com' - add_user(session_scope_function, name=name, password=password, - lastname=lastname, firstname=firstname, email=email, - access_level=access_level) + name = "new_user_name" + email = "MixCase@mail.com" + add_user( + session_scope_function, + name=name, + password=password, + lastname=lastname, + firstname=firstname, + email=email, + access_level=access_level, + ) user = get_user_by_name(session_scope_function, name) - assert user.email == 'mixcase@mail.com' + assert user.email == "mixcase@mail.com" def test_delete_user(session_scope_function): - username = 'test_user' + username = "test_user" add_user( - session_scope_function, name=username, password='password', - lastname='lastname', firstname='firstname', - email='test_user@email.com', access_level='asked') - user = (session_scope_function.query(User) - .filter(User.name == username) - .all()) + session_scope_function, + name=username, + password="password", + lastname="lastname", + firstname="firstname", + email="test_user@email.com", + access_level="asked", + ) + user = session_scope_function.query(User).filter(User.name == username).all() assert len(user) == 1 delete_user(session_scope_function, username) - user = (session_scope_function.query(User) - .filter(User.name == username) - .one_or_none()) + user = ( + session_scope_function.query(User).filter(User.name == username).one_or_none() + ) assert user is None - team = (session_scope_function.query(Team) - .filter(Team.name == username) - .all()) + team = session_scope_function.query(Team).filter(Team.name == username).all() assert len(team) == 0 def test_make_user_admin(session_scope_function): - username = 'test_user' + username = "test_user" user = add_user( - session_scope_function, name=username, password='password', - lastname='lastname', firstname='firstname', - email='test_user@email.com', access_level='asked') - assert user.access_level == 'asked' + session_scope_function, + name=username, + password="password", + lastname="lastname", + firstname="firstname", + email="test_user@email.com", + access_level="asked", + ) + assert user.access_level == "asked" assert user.is_authenticated is False make_user_admin(session_scope_function, username) user = get_user_by_name(session_scope_function, username) - assert user.access_level == 'admin' + assert user.access_level == "admin" assert user.is_authenticated is True @pytest.mark.parametrize("access_level", ["asked", "user", "admin"]) def test_set_user_access_level(session_scope_function, access_level): - username = 'test_user' + username = "test_user" user = add_user( - session_scope_function, name=username, password='password', - lastname='lastname', firstname='firstname', - email='test_user@email.com', access_level='asked') - assert user.access_level == 'asked' + session_scope_function, + name=username, + password="password", + lastname="lastname", + firstname="firstname", + email="test_user@email.com", + access_level="asked", + ) + assert user.access_level == "asked" assert user.is_authenticated is False set_user_access_level(session_scope_function, username, access_level) user = get_user_by_name(session_scope_function, username) @@ -136,98 +171,153 @@ def test_set_user_access_level(session_scope_function, access_level): assert user.is_authenticated is True -@pytest.mark.parametrize( - "name, query_type", [(None, list), ('test_user', User)] -) +@pytest.mark.parametrize("name, query_type", [(None, list), ("test_user", User)]) def test_get_user_by_name(session_scope_function, name, query_type): - add_user(session_scope_function, name='test_user', password='password', - lastname='lastname', firstname='firstname', - email='test_user@email.com', access_level='asked') - add_user(session_scope_function, name='test_user_2', - password='password', lastname='lastname', - firstname='firstname', email='test_user_2@email.com', - access_level='asked') + add_user( + session_scope_function, + name="test_user", + password="password", + lastname="lastname", + firstname="firstname", + email="test_user@email.com", + access_level="asked", + ) + add_user( + session_scope_function, + name="test_user_2", + password="password", + lastname="lastname", + firstname="firstname", + email="test_user_2@email.com", + access_level="asked", + ) user = get_user_by_name(session_scope_function, name) assert isinstance(user, query_type) def test_set_user_by_instance(session_scope_function): - add_user(session_scope_function, name='test_user', password='password', - lastname='lastname', firstname='firstname', - email='test_user@email.com', access_level='asked') - add_user(session_scope_function, name='test_user_2', - password='password', lastname='lastname', - firstname='firstname', email='test_user_2@email.com', - access_level='asked') - user = get_user_by_name(session_scope_function, 'test_user') - set_user_by_instance(session_scope_function, user, lastname='a', - firstname='b', email='c', linkedin_url='d', - twitter_url='e', facebook_url='f', google_url='g', - github_url='h', website_url='i', bio='j', - is_want_news=False) - user = get_user_by_name(session_scope_function, 'test_user') - assert user.lastname == 'a' - assert user.firstname == 'b' - assert user.email == 'c' - assert user.linkedin_url == 'd' - assert user.twitter_url == 'e' - assert user.facebook_url == 'f' - assert user.google_url == 'g' - assert user.github_url == 'h' - assert user.website_url == 'i' - assert user.bio == 'j' + add_user( + session_scope_function, + name="test_user", + password="password", + lastname="lastname", + firstname="firstname", + email="test_user@email.com", + access_level="asked", + ) + add_user( + session_scope_function, + name="test_user_2", + password="password", + lastname="lastname", + firstname="firstname", + email="test_user_2@email.com", + access_level="asked", + ) + user = get_user_by_name(session_scope_function, "test_user") + set_user_by_instance( + session_scope_function, + user, + lastname="a", + firstname="b", + email="c", + linkedin_url="d", + twitter_url="e", + facebook_url="f", + google_url="g", + github_url="h", + website_url="i", + bio="j", + is_want_news=False, + ) + user = get_user_by_name(session_scope_function, "test_user") + assert user.lastname == "a" + assert user.firstname == "b" + assert user.email == "c" + assert user.linkedin_url == "d" + assert user.twitter_url == "e" + assert user.facebook_url == "f" + assert user.google_url == "g" + assert user.github_url == "h" + assert user.website_url == "i" + assert user.bio == "j" assert user.is_want_news is False -@pytest.mark.parametrize( - "name, query_type", [(None, list), ('test_user', Team)] -) +@pytest.mark.parametrize("name, query_type", [(None, list), ("test_user", Team)]) def test_get_team_by_name(session_scope_function, name, query_type): - add_user(session_scope_function, name='test_user', password='password', - lastname='lastname', firstname='firstname', - email='test_user@email.com', access_level='asked') - add_user(session_scope_function, name='test_user_2', - password='password', lastname='lastname', - firstname='firstname', email='test_user_2@email.com', - access_level='asked') + add_user( + session_scope_function, + name="test_user", + password="password", + lastname="lastname", + firstname="firstname", + email="test_user@email.com", + access_level="asked", + ) + add_user( + session_scope_function, + name="test_user_2", + password="password", + lastname="lastname", + firstname="firstname", + email="test_user_2@email.com", + access_level="asked", + ) team = get_team_by_name(session_scope_function, name) assert isinstance(team, query_type) def test_approve_user(session_scope_function): - add_user(session_scope_function, name='test_user', password='test', - lastname='Test', firstname='User', email='test.user@gmail.com', - access_level='asked') - user = get_user_by_name(session_scope_function, 'test_user') - assert user.access_level == 'asked' + add_user( + session_scope_function, + name="test_user", + password="test", + lastname="Test", + firstname="User", + email="test.user@gmail.com", + access_level="asked", + ) + user = get_user_by_name(session_scope_function, "test_user") + assert user.access_level == "asked" assert user.is_authenticated is False - approve_user(session_scope_function, 'test_user') - user = get_user_by_name(session_scope_function, 'test_user') - assert user.access_level == 'user' + approve_user(session_scope_function, "test_user") + user = get_user_by_name(session_scope_function, "test_user") + assert user.access_level == "user" assert user.is_authenticated is True @pytest.mark.parametrize( "output_format, expected_format", - [('dataframe', pd.DataFrame), - ('html', str)] + [("dataframe", pd.DataFrame), ("html", str)], ) -def test_check_user_interactions(session_scope_function, output_format, - expected_format): - add_user(session_scope_function, name='test_user', password='password', - lastname='lastname', firstname='firstname', - email='test_user@email.com', access_level='asked') - params = {'interaction': 'landing'} +def test_check_user_interactions( + session_scope_function, output_format, expected_format +): + add_user( + session_scope_function, + name="test_user", + password="password", + lastname="lastname", + firstname="firstname", + email="test_user@email.com", + access_level="asked", + ) + params = {"interaction": "landing"} add_user_interaction(session_scope_function, **params) - params = {'interaction': 'landing', - 'user': get_user_by_name(session_scope_function, 'test_user')} + params = { + "interaction": "landing", + "user": get_user_by_name(session_scope_function, "test_user"), + } add_user_interaction(session_scope_function, **params) user_interaction = get_user_interactions_by_name( - session_scope_function, output_format=output_format) + session_scope_function, output_format=output_format + ) if isinstance(user_interaction, pd.DataFrame): assert user_interaction.shape[0] == 2 assert isinstance(user_interaction, expected_format) user_interaction = get_user_interactions_by_name( - session_scope_function, name='test_user', output_format=output_format) + session_scope_function, name="test_user", output_format=output_format + ) if isinstance(user_interaction, pd.DataFrame): assert user_interaction.shape[0] == 1 diff --git a/ramp-database/ramp_database/tools/user.py b/ramp-database/ramp_database/tools/user.py index faccfbe84..9ba87fa5b 100644 --- a/ramp-database/ramp_database/tools/user.py +++ b/ramp-database/ramp_database/tools/user.py @@ -15,13 +15,27 @@ from ._query import select_user_by_email from ._query import select_user_by_name -logger = logging.getLogger('RAMP-DATABASE') - - -def add_user(session, name, password, lastname, firstname, email, - access_level='user', hidden_notes='', linkedin_url='', - twitter_url='', facebook_url='', google_url='', github_url='', - website_url='', bio='', is_want_news=True): +logger = logging.getLogger("RAMP-DATABASE") + + +def add_user( + session, + name, + password, + lastname, + firstname, + email, + access_level="user", + hidden_notes="", + linkedin_url="", + twitter_url="", + facebook_url="", + google_url="", + github_url="", + website_url="", + bio="", + is_want_news=True, +): """Add a new user in the database. Parameters @@ -68,13 +82,23 @@ def add_user(session, name, password, lastname, firstname, email, # String hashed_password = hash_password(password).decode() lower_case_email = email.lower() - user = User(name=name, hashed_password=hashed_password, - lastname=lastname, firstname=firstname, email=lower_case_email, - access_level=access_level, hidden_notes=hidden_notes, - linkedin_url=linkedin_url, twitter_url=twitter_url, - facebook_url=facebook_url, google_url=google_url, - github_url=github_url, website_url=website_url, bio=bio, - is_want_news=is_want_news) + user = User( + name=name, + hashed_password=hashed_password, + lastname=lastname, + firstname=firstname, + email=lower_case_email, + access_level=access_level, + hidden_notes=hidden_notes, + linkedin_url=linkedin_url, + twitter_url=twitter_url, + facebook_url=facebook_url, + google_url=google_url, + github_url=github_url, + website_url=website_url, + bio=bio, + is_want_news=is_want_news, + ) # Creating default team with the same name as the user # user is admin of his/her own team @@ -85,22 +109,22 @@ def add_user(session, name, password, lastname, firstname, email, session.commit() except IntegrityError as e: session.rollback() - message = '' + message = "" if select_user_by_name(session, name) is not None: - message += 'username is already in use' + message += "username is already in use" elif select_team_by_name(session, name) is not None: # We only check for team names if username is not in db - message += 'username is already in use as a team name' + message += "username is already in use as a team name" if select_user_by_email(session, lower_case_email) is not None: if message: - message += ' and ' - message += 'email is already in use' + message += " and " + message += "email is already in use" if message: raise NameClashError(message) else: raise e - logger.info('Creating {}'.format(user)) - logger.info('Creating {}'.format(team)) + logger.info("Creating {}".format(user)) + logger.info("Creating {}".format(team)) return user @@ -130,7 +154,7 @@ def make_user_admin(session, name): The name of the user. """ user = select_user_by_name(session, name) - user.access_level = 'admin' + user.access_level = "admin" user.is_authenticated = True session.commit() @@ -155,9 +179,19 @@ def set_user_access_level(session, name, access_level="user"): session.commit() -def add_user_interaction(session, interaction=None, user=None, problem=None, - event=None, ip=None, note=None, submission=None, - submission_file=None, diff=None, similarity=None): +def add_user_interaction( + session, + interaction=None, + user=None, + problem=None, + event=None, + ip=None, + note=None, + submission=None, + submission_file=None, + diff=None, + similarity=None, +): """Add a user interaction in the database. Parameters @@ -188,9 +222,17 @@ def add_user_interaction(session, interaction=None, user=None, problem=None, The similarity of the submission. """ user_interaction = UserInteraction( - session=session, interaction=interaction, user=user, problem=problem, - ip=ip, note=note, submission=submission, event=event, - submission_file=submission_file, diff=diff, similarity=similarity + session=session, + interaction=interaction, + user=user, + problem=problem, + ip=ip, + note=note, + submission=submission, + event=event, + submission_file=submission_file, + diff=diff, + similarity=similarity, ) session.add(user_interaction) session.commit() @@ -207,8 +249,8 @@ def approve_user(session, name): The name of the user. """ user = select_user_by_name(session, name) - if user.access_level == 'asked': - user.access_level = 'user' + if user.access_level == "asked": + user.access_level = "user" user.is_authenticated = True session.commit() @@ -249,8 +291,7 @@ def get_user_by_name_or_email(session, name): :class:`ramp_database.model.User` The queried user. """ - return (select_user_by_email(session, name) or - select_user_by_name(session, name)) + return select_user_by_email(session, name) or select_user_by_name(session, name) def get_team_by_name(session, name): @@ -272,8 +313,7 @@ def get_team_by_name(session, name): return select_team_by_name(session, name) -def get_user_interactions_by_name(session, name=None, - output_format='dataframe'): +def get_user_interactions_by_name(session, name=None, output_format="dataframe"): """Get the user interactions. Parameters @@ -296,45 +336,68 @@ def get_user_interactions_by_name(session, name=None, if name is None: user_interactions = user_interactions.all() else: - user_interactions = \ - (user_interactions.filter(UserInteraction.user_id == User.id) - .filter(User.name == name) - .all()) + user_interactions = ( + user_interactions.filter(UserInteraction.user_id == User.id) + .filter(User.name == name) + .all() + ) map_columns_attributes = defaultdict(list) for ui in user_interactions: - map_columns_attributes['timestamp (UTC)'].append(ui.timestamp) - map_columns_attributes['IP'].append(ui.ip) - map_columns_attributes['interaction'].append(ui.interaction) - map_columns_attributes['user'].append(getattr(ui.user, 'name', None)) - map_columns_attributes['event'].append(getattr( - getattr(ui.event_team, 'event', None), 'name', None)) - map_columns_attributes['team'].append(getattr( - getattr(ui.event_team, 'team', None), 'name', None)) - map_columns_attributes['submission_id'].append(ui.submission_id) - map_columns_attributes['submission'].append( - getattr(ui.submission, 'name_with_link', None)) - map_columns_attributes['file'].append( - getattr(ui.submission_file, 'name_with_link', None)) - map_columns_attributes['code similarity'].append( - ui.submission_file_similarity) - map_columns_attributes['diff'].append( - None if ui.submission_file_diff is None - else 'diff'.format( - ui.submission_file_diff)) - df = (pd.DataFrame(map_columns_attributes) - .sort_values('timestamp (UTC)', ascending=False) - .set_index('timestamp (UTC)')) - if output_format == 'html': - return df.to_html(escape=False, index=False, max_cols=None, - max_rows=None, justify='left') + map_columns_attributes["timestamp (UTC)"].append(ui.timestamp) + map_columns_attributes["IP"].append(ui.ip) + map_columns_attributes["interaction"].append(ui.interaction) + map_columns_attributes["user"].append(getattr(ui.user, "name", None)) + map_columns_attributes["event"].append( + getattr(getattr(ui.event_team, "event", None), "name", None) + ) + map_columns_attributes["team"].append( + getattr(getattr(ui.event_team, "team", None), "name", None) + ) + map_columns_attributes["submission_id"].append(ui.submission_id) + map_columns_attributes["submission"].append( + getattr(ui.submission, "name_with_link", None) + ) + map_columns_attributes["file"].append( + getattr(ui.submission_file, "name_with_link", None) + ) + map_columns_attributes["code similarity"].append(ui.submission_file_similarity) + map_columns_attributes["diff"].append( + None + if ui.submission_file_diff is None + else 'diff'.format(ui.submission_file_diff) + ) + df = ( + pd.DataFrame(map_columns_attributes) + .sort_values("timestamp (UTC)", ascending=False) + .set_index("timestamp (UTC)") + ) + if output_format == "html": + return df.to_html( + escape=False, + index=False, + max_cols=None, + max_rows=None, + justify="left", + ) return df -def set_user_by_instance(session, user, lastname, firstname, email, - linkedin_url='', twitter_url='', facebook_url='', - google_url='', github_url='', website_url='', bio='', - is_want_news=True): +def set_user_by_instance( + session, + user, + lastname, + firstname, + email, + linkedin_url="", + twitter_url="", + facebook_url="", + google_url="", + github_url="", + website_url="", + bio="", + is_want_news=True, +): """Set the information of a user. Parameters @@ -368,22 +431,35 @@ def set_user_by_instance(session, user, lastname, firstname, email, """ logger.info('Update the profile of "{}"'.format(user)) - for field in ('lastname', 'firstname', 'linkedin_url', 'twitter_url', - 'facebook_url', 'google_url', 'github_url', 'website_url', - 'bio', 'email', 'is_want_news'): + for field in ( + "lastname", + "firstname", + "linkedin_url", + "twitter_url", + "facebook_url", + "google_url", + "github_url", + "website_url", + "bio", + "email", + "is_want_news", + ): local_attr = locals()[field] - if field == 'email': + if field == "email": local_attr = local_attr.lower() if getattr(user, field) != local_attr: - logger.info('Update the "{}" field from {} to {}' - .format(field, getattr(user, field), local_attr)) + logger.info( + 'Update the "{}" field from {} to {}'.format( + field, getattr(user, field), local_attr + ) + ) setattr(user, field, local_attr) try: session.commit() except IntegrityError as e: session.rollback() if select_user_by_email(session, user.email) is not None: - message = 'email is already in use' + message = "email is already in use" logger.error(message) raise NameClashError(message) diff --git a/ramp-database/ramp_database/utils.py b/ramp-database/ramp_database/utils.py index b3c377fa3..7ac3f7863 100644 --- a/ramp-database/ramp_database/utils.py +++ b/ramp-database/ramp_database/utils.py @@ -72,7 +72,7 @@ def session_scope(config): def _encode_string(text): - return bytes(text, 'utf-8') if isinstance(text, str) else text + return bytes(text, "utf-8") if isinstance(text, str) else text def hash_password(password): @@ -106,6 +106,4 @@ def check_password(password, hashed_password): is_same_password : bool Return True if the two passwords are identical. """ - return bcrypt.checkpw( - _encode_string(password), _encode_string(hashed_password) - ) + return bcrypt.checkpw(_encode_string(password), _encode_string(hashed_password)) diff --git a/ramp-database/setup.py b/ramp-database/setup.py index 377a01f57..e6c07ac7e 100755 --- a/ramp-database/setup.py +++ b/ramp-database/setup.py @@ -5,43 +5,53 @@ from setuptools import find_packages, setup # get __version__ from _version.py -ver_file = os.path.join('ramp_database', '_version.py') +ver_file = os.path.join("ramp_database", "_version.py") with open(ver_file) as f: exec(f.read()) -DISTNAME = 'ramp-database' +DISTNAME = "ramp-database" DESCRIPTION = "Database model used in the RAMP bundle" -with codecs.open('README.rst', encoding='utf-8-sig') as f: +with codecs.open("README.rst", encoding="utf-8-sig") as f: LONG_DESCRIPTION = f.read() -MAINTAINER = 'A. Boucaud, B. Kegl, G. Lemaitre, J. Van den Bossche' -MAINTAINER_EMAIL = 'boucaud.alexandre@gmail.com, guillaume.lemaitre@inria.fr' -URL = 'https://github.com/paris-saclay-cds/ramp-board' -LICENSE = 'BSD (3-clause)' -DOWNLOAD_URL = 'https://github.com/paris-saclay-cds/ramp-board' +MAINTAINER = "A. Boucaud, B. Kegl, G. Lemaitre, J. Van den Bossche" +MAINTAINER_EMAIL = "boucaud.alexandre@gmail.com, guillaume.lemaitre@inria.fr" +URL = "https://github.com/paris-saclay-cds/ramp-board" +LICENSE = "BSD (3-clause)" +DOWNLOAD_URL = "https://github.com/paris-saclay-cds/ramp-board" VERSION = __version__ # noqa -CLASSIFIERS = ['Intended Audience :: Science/Research', - 'Intended Audience :: Developers', - 'License :: OSI Approved', - 'Programming Language :: Python', - 'Topic :: Software Development', - 'Topic :: Scientific/Engineering', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Operating System :: Unix', - 'Operating System :: MacOS', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8'] -INSTALL_REQUIRES = ['bcrypt', 'click', 'gitpython', 'nbconvert', 'numpy', - 'pandas', 'psycopg2-binary', 'sqlalchemy'] +CLASSIFIERS = [ + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "License :: OSI Approved", + "Programming Language :: Python", + "Topic :: Software Development", + "Topic :: Scientific/Engineering", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", +] +INSTALL_REQUIRES = [ + "bcrypt", + "click", + "gitpython", + "nbconvert", + "numpy", + "pandas", + "psycopg2-binary", + "sqlalchemy", +] EXTRAS_REQUIRE = { - 'tests': ['pytest', 'pytest-cov'], - 'docs': ['sphinx', 'sphinx_rtd_theme', 'numpydoc'] + "tests": ["pytest", "pytest-cov"], + "docs": ["sphinx", "sphinx_rtd_theme", "numpydoc"], } PACKAGE_DATA = { - 'ramp_database': [ - os.path.join('tests', 'data', 'ramp_config_iris.yml'), - os.path.join('tests', 'data', 'ramp_config_boston_housing.yml') + "ramp_database": [ + os.path.join("tests", "data", "ramp_config_iris.yml"), + os.path.join("tests", "data", "ramp_config_boston_housing.yml"), ] } @@ -62,7 +72,5 @@ install_requires=INSTALL_REQUIRES, extras_require=EXTRAS_REQUIRE, python_requires=">=3.7", - entry_points={ - 'console_scripts': ['ramp-database = ramp_database.cli:start'] - } + entry_points={"console_scripts": ["ramp-database = ramp_database.cli:start"]}, ) diff --git a/ramp-engine/ramp_engine/__init__.py b/ramp-engine/ramp_engine/__init__.py index d1ae3c100..c9895e78e 100644 --- a/ramp-engine/ramp_engine/__init__.py +++ b/ramp-engine/ramp_engine/__init__.py @@ -5,15 +5,17 @@ from ._version import __version__ -available_workers = {'conda': CondaEnvWorker, - 'aws': AWSWorker, - 'dask': DaskWorker} +available_workers = { + "conda": CondaEnvWorker, + "aws": AWSWorker, + "dask": DaskWorker, +} __all__ = [ - 'AWSWorker', - 'CondaEnvWorker', - 'DaskWorker', - 'Dispatcher', - 'available_workers', - '__version__' + "AWSWorker", + "CondaEnvWorker", + "DaskWorker", + "Dispatcher", + "available_workers", + "__version__", ] diff --git a/ramp-engine/ramp_engine/_version.py b/ramp-engine/ramp_engine/_version.py index 531186263..4fc44501a 100644 --- a/ramp-engine/ramp_engine/_version.py +++ b/ramp-engine/ramp_engine/_version.py @@ -21,4 +21,4 @@ # 'X.Y.dev0' is the canonical version of 'X.Y.dev' # -__version__ = '0.9.0.dev0' +__version__ = "0.9.0.dev0" diff --git a/ramp-engine/ramp_engine/aws/api.py b/ramp-engine/ramp_engine/aws/api.py index 82aec2242..4bcc86077 100644 --- a/ramp-engine/ramp_engine/aws/api.py +++ b/ramp-engine/ramp_engine/aws/api.py @@ -13,88 +13,89 @@ __all__ = [ - 'launch_ec2_instances', - 'terminate_ec2_instance', - 'list_ec2_instance_ids', - 'status_of_ec2_instance', - 'upload_submission', - 'download_log', - 'download_predictions', - 'launch_train', - 'abort_training', + "launch_ec2_instances", + "terminate_ec2_instance", + "list_ec2_instance_ids", + "status_of_ec2_instance", + "upload_submission", + "download_log", + "download_predictions", + "launch_train", + "abort_training", ] # we disable the boto3 loggers because they are too verbose -for k in (logging.Logger.manager.loggerDict.keys()): - if 'boto' in k: +for k in logging.Logger.manager.loggerDict.keys(): + if "boto" in k: logging.getLogger(k).disabled = True -logger = logging.getLogger('RAMP-AWS') +logger = logging.getLogger("RAMP-AWS") # configuration fields -AWS_CONFIG_SECTION = 'aws' -PROFILE_NAME_FIELD = 'profile_name' -ACCESS_KEY_ID_FIELD = 'access_key_id' -SECRET_ACCESS_KEY_FIELD = 'secret_access_key' -REGION_NAME_FIELD = 'region_name' -AMI_IMAGE_ID_FIELD = 'ami_image_id' -AMI_IMAGE_NAME_FIELD = 'ami_image_name' -AMI_USER_NAME_FIELD = 'ami_user_name' -INSTANCE_TYPE_FIELD = 'instance_type' -USE_SPOT_INSTANCE_FIELD = 'use_spot_instance' -KEY_PATH_FIELD = 'key_path' -KEY_NAME_FIELD = 'key_name' -SECURITY_GROUP_FIELD = 'security_group' -REMOTE_RAMP_KIT_FOLDER_FIELD = 'remote_ramp_kit_folder' -LOCAL_PREDICTIONS_FOLDER_FIELD = 'predictions_dir' -CHECK_STATUS_INTERVAL_SECS_FIELD = 'check_status_interval_secs' -CHECK_FINISHED_TRAINING_INTERVAL_SECS_FIELD = ( - 'check_finished_training_interval_secs') -LOCAL_LOG_FOLDER_FIELD = 'logs_dir' -TRAIN_LOOP_INTERVAL_SECS_FIELD = 'train_loop_interval_secs' -MEMORY_PROFILING_FIELD = 'memory_profiling' +AWS_CONFIG_SECTION = "aws" +PROFILE_NAME_FIELD = "profile_name" +ACCESS_KEY_ID_FIELD = "access_key_id" +SECRET_ACCESS_KEY_FIELD = "secret_access_key" +REGION_NAME_FIELD = "region_name" +AMI_IMAGE_ID_FIELD = "ami_image_id" +AMI_IMAGE_NAME_FIELD = "ami_image_name" +AMI_USER_NAME_FIELD = "ami_user_name" +INSTANCE_TYPE_FIELD = "instance_type" +USE_SPOT_INSTANCE_FIELD = "use_spot_instance" +KEY_PATH_FIELD = "key_path" +KEY_NAME_FIELD = "key_name" +SECURITY_GROUP_FIELD = "security_group" +REMOTE_RAMP_KIT_FOLDER_FIELD = "remote_ramp_kit_folder" +LOCAL_PREDICTIONS_FOLDER_FIELD = "predictions_dir" +CHECK_STATUS_INTERVAL_SECS_FIELD = "check_status_interval_secs" +CHECK_FINISHED_TRAINING_INTERVAL_SECS_FIELD = "check_finished_training_interval_secs" +LOCAL_LOG_FOLDER_FIELD = "logs_dir" +TRAIN_LOOP_INTERVAL_SECS_FIELD = "train_loop_interval_secs" +MEMORY_PROFILING_FIELD = "memory_profiling" # how long to wait for connections WAIT_MINUTES = 2 MAX_TRIES_TO_CONNECT = 1 -HOOKS_SECTION = 'hooks' -HOOK_START_TRAINING = 'start_training' -HOOK_SUCCESSFUL_TRAINING = 'successful_training' -HOOK_FAILED_TRAINING = 'failed_training' +HOOKS_SECTION = "hooks" +HOOK_START_TRAINING = "start_training" +HOOK_SUCCESSFUL_TRAINING = "successful_training" +HOOK_FAILED_TRAINING = "failed_training" HOOKS = [ HOOK_START_TRAINING, HOOK_SUCCESSFUL_TRAINING, HOOK_FAILED_TRAINING, ] -ALL_FIELDS = set([ - PROFILE_NAME_FIELD, - ACCESS_KEY_ID_FIELD, - SECRET_ACCESS_KEY_FIELD, - REGION_NAME_FIELD, - AMI_IMAGE_ID_FIELD, - AMI_IMAGE_NAME_FIELD, - AMI_USER_NAME_FIELD, - INSTANCE_TYPE_FIELD, - USE_SPOT_INSTANCE_FIELD, - KEY_PATH_FIELD, - KEY_NAME_FIELD, - SECURITY_GROUP_FIELD, - REMOTE_RAMP_KIT_FOLDER_FIELD, - LOCAL_PREDICTIONS_FOLDER_FIELD, - CHECK_STATUS_INTERVAL_SECS_FIELD, - CHECK_FINISHED_TRAINING_INTERVAL_SECS_FIELD, - LOCAL_LOG_FOLDER_FIELD, - TRAIN_LOOP_INTERVAL_SECS_FIELD, - MEMORY_PROFILING_FIELD, - HOOKS_SECTION, -]) +ALL_FIELDS = set( + [ + PROFILE_NAME_FIELD, + ACCESS_KEY_ID_FIELD, + SECRET_ACCESS_KEY_FIELD, + REGION_NAME_FIELD, + AMI_IMAGE_ID_FIELD, + AMI_IMAGE_NAME_FIELD, + AMI_USER_NAME_FIELD, + INSTANCE_TYPE_FIELD, + USE_SPOT_INSTANCE_FIELD, + KEY_PATH_FIELD, + KEY_NAME_FIELD, + SECURITY_GROUP_FIELD, + REMOTE_RAMP_KIT_FOLDER_FIELD, + LOCAL_PREDICTIONS_FOLDER_FIELD, + CHECK_STATUS_INTERVAL_SECS_FIELD, + CHECK_FINISHED_TRAINING_INTERVAL_SECS_FIELD, + LOCAL_LOG_FOLDER_FIELD, + TRAIN_LOOP_INTERVAL_SECS_FIELD, + MEMORY_PROFILING_FIELD, + HOOKS_SECTION, + ] +) REQUIRED_FIELDS = ALL_FIELDS - {HOOKS_SECTION} # constants -RAMP_AWS_BACKEND_TAG = 'ramp_aws_backend_instance' -SUBMISSIONS_FOLDER = 'submissions' +RAMP_AWS_BACKEND_TAG = "ramp_aws_backend_instance" +SUBMISSIONS_FOLDER = "submissions" def _wait_until_train_finished(config, instance_id, submission_name): @@ -104,15 +105,17 @@ def _wait_until_train_finished(config, instance_id, submission_name): the screen is still active. If the screen is not active anymore, then we consider that the training has either finished or failed. """ - logger.info('Wait until training of submission "{}" is ' - 'finished on instance "{}"...'.format(submission_name, - instance_id)) + logger.info( + 'Wait until training of submission "{}" is ' + 'finished on instance "{}"...'.format(submission_name, instance_id) + ) secs = int(config[CHECK_FINISHED_TRAINING_INTERVAL_SECS_FIELD]) while not _training_finished(config, instance_id, submission_name): time.sleep(secs) - logger.info('Training of submission "{}" is ' - 'finished on instance "{}".'.format(submission_name, - instance_id)) + logger.info( + 'Training of submission "{}" is ' + 'finished on instance "{}".'.format(submission_name, instance_id) + ) def launch_ec2_instances(config, nb=1): @@ -124,9 +127,10 @@ def launch_ec2_instances(config, nb=1): ami_name = config.get(AMI_IMAGE_NAME_FIELD) if ami_image_id and ami_name: raise ValueError( - 'The fields ami_image_id and ami_image_name cannot be both' - 'specified at the same time. Please specify either ami_image_id' - 'or ami_image_name') + "The fields ami_image_id and ami_image_name cannot be both" + "specified at the same time. Please specify either ami_image_id" + "or ami_image_name" + ) if ami_name: try: ami_image_id = _get_image_id(config, ami_name) @@ -136,40 +140,42 @@ def launch_ec2_instances(config, nb=1): key_name = config[KEY_NAME_FIELD] security_group = config[SECURITY_GROUP_FIELD] - logger.info('Launching {} new ec2 instance(s)...'.format(nb)) + logger.info("Launching {} new ec2 instance(s)...".format(nb)) # tag all instances using RAMP_AWS_BACKEND_TAG to be able # to list all instances later - tags = [{ - 'ResourceType': 'instance', - 'Tags': [ - {'Key': RAMP_AWS_BACKEND_TAG, 'Value': '1'}, - ] - }] + tags = [ + { + "ResourceType": "instance", + "Tags": [ + {"Key": RAMP_AWS_BACKEND_TAG, "Value": "1"}, + ], + } + ] sess = _get_boto_session(config) - client = sess.client('ec2') - resource = sess.resource('ec2') + client = sess.client("ec2") + resource = sess.resource("ec2") switch_to_on_demand = False if use_spot_instance: - logger.info('Attempting to use spot instance.') + logger.info("Attempting to use spot instance.") now = datetime.utcnow() + timedelta(seconds=3) wait_minutes = WAIT_MINUTES max_tries_to_connect = MAX_TRIES_TO_CONNECT request_wait = timedelta(minutes=wait_minutes) n_try = 0 response = None - while not(response) and (n_try < max_tries_to_connect): + while not (response) and (n_try < max_tries_to_connect): try: response = client.request_spot_instances( InstanceCount=nb, LaunchSpecification={ - 'SecurityGroups': [security_group], - 'ImageId': ami_image_id, - 'InstanceType': instance_type, - 'KeyName': key_name, + "SecurityGroups": [security_group], + "ImageId": ami_image_id, + "InstanceType": instance_type, + "KeyName": key_name, }, - Type='one-time', + Type="one-time", ValidFrom=now, ValidUntil=(now + request_wait), ) @@ -178,57 +184,69 @@ def launch_ec2_instances(config, nb=1): n_try += 1 if n_try < max_tries_to_connect: # wait before you try again - logger.warning('Not enough instances available: I am going' - f' to wait for {wait_minutes} minutes' - ' before trying again (this was' - f' {n_try} out of {max_tries_to_connect}' - ' tries to connect)') - time.sleep(wait_minutes*60) + logger.warning( + "Not enough instances available: I am going" + f" to wait for {wait_minutes} minutes" + " before trying again (this was" + f" {n_try} out of {max_tries_to_connect}" + " tries to connect)" + ) + time.sleep(wait_minutes * 60) else: - logger.error(f'Not enough instances available: {e}') - return None, 'retry' + logger.error(f"Not enough instances available: {e}") + return None, "retry" except Exception as e: # unknown error - logger.error(f'AWS worker error: {e}') + logger.error(f"AWS worker error: {e}") return None, e # Wait until request fulfilled - waiter = client.get_waiter('spot_instance_request_fulfilled') - request_id = \ - response['SpotInstanceRequests'][0]['SpotInstanceRequestId'] + waiter = client.get_waiter("spot_instance_request_fulfilled") + request_id = response["SpotInstanceRequests"][0]["SpotInstanceRequestId"] try: - waiter.wait(SpotInstanceRequestIds=[request_id, ]) + waiter.wait( + SpotInstanceRequestIds=[ + request_id, + ] + ) except botocore.exceptions.WaiterError: - logger.info('Spot instance request failed due to time out. Using ' - 'on-demand instance instead') + logger.info( + "Spot instance request failed due to time out. Using " + "on-demand instance instead" + ) switch_to_on_demand = True client.cancel_spot_instance_requests( - SpotInstanceRequestIds=[request_id, ] + SpotInstanceRequestIds=[ + request_id, + ] ) else: - logger.info('Spot instance request fulfilled.') + logger.info("Spot instance request fulfilled.") # Small wait before getting instance ID time.sleep(1) # Get instance ID response_updated = client.describe_spot_instance_requests( SpotInstanceRequestIds=[request_id] ) - instance_id = \ - response_updated['SpotInstanceRequests'][0]['InstanceId'] + instance_id = response_updated["SpotInstanceRequests"][0]["InstanceId"] # Create EC2.Instance class instance = resource.Instance(instance_id) instance.create_tags( - Resources=[instance_id, ], + Resources=[ + instance_id, + ], Tags=[ - { - 'Key': RAMP_AWS_BACKEND_TAG, - 'Value': '1' - }, - ]) - instances = [instance, ] - instance_ids = [instance_id, ] + {"Key": RAMP_AWS_BACKEND_TAG, "Value": "1"}, + ], + ) + instances = [ + instance, + ] + instance_ids = [ + instance_id, + ] if switch_to_on_demand or not use_spot_instance: - logger.info('Using on-demand instance.') + logger.info("Using on-demand instance.") instances = resource.create_instances( ImageId=ami_image_id, MinCount=nb, @@ -240,7 +258,7 @@ def launch_ec2_instances(config, nb=1): ) instance_ids = [instance.id for instance in instances] # Wait until instance is okay - waiter = client.get_waiter('instance_status_ok') + waiter = client.get_waiter("instance_status_ok") try: waiter.wait(InstanceIds=instance_ids) except botocore.exceptions.WaiterError as e: @@ -251,26 +269,25 @@ def launch_ec2_instances(config, nb=1): def _get_image_id(config, image_name): sess = _get_boto_session(config) - client = sess.client('ec2') + client = sess.client("ec2") # get all the images with the given image_name in the name - result = client.describe_images(Filters=[ - { - 'Name': 'name', - 'Values': [f'{image_name}*' - ], - } - ]) + result = client.describe_images( + Filters=[ + { + "Name": "name", + "Values": [f"{image_name}*"], + } + ] + ) - images = result['Images'] + images = result["Images"] if len(images) == 0: - raise ValueError( - 'No image corresponding to the name "{}"'.format(image_name)) + raise ValueError('No image corresponding to the name "{}"'.format(image_name)) # get only the newest image if there are more than one - image = sorted(images, key=lambda x: x['CreationDate'], - reverse=True)[0] - return image['ImageId'] + image = sorted(images, key=lambda x: x["CreationDate"], reverse=True)[0] + return image["ImageId"] def terminate_ec2_instance(config, instance_id): @@ -287,8 +304,8 @@ def terminate_ec2_instance(config, instance_id): instance id """ sess = _get_boto_session(config) - resource = sess.resource('ec2') - logger.info('Killing the instance {}...'.format(instance_id)) + resource = sess.resource("ec2") + logger.info("Killing the instance {}...".format(instance_id)) return resource.instances.filter(InstanceIds=[instance_id]).terminate() @@ -308,16 +325,15 @@ def list_ec2_instance_ids(config): list of str """ sess = _get_boto_session(config) - client = sess.client('ec2') + client = sess.client("ec2") instances = client.describe_instances( Filters=[ - {'Name': 'tag:' + RAMP_AWS_BACKEND_TAG, 'Values': ['1']}, - {'Name': 'instance-state-name', 'Values': ['running']}, + {"Name": "tag:" + RAMP_AWS_BACKEND_TAG, "Values": ["1"]}, + {"Name": "instance-state-name", "Values": ["running"]}, ] ) instance_ids = [ - inst['Instances'][0]['InstanceId'] - for inst in instances['Reservations'] + inst["Instances"][0]["InstanceId"] for inst in instances["Reservations"] ] return instance_ids @@ -343,17 +359,17 @@ def status_of_ec2_instance(config, instance_id): not even ready to give the status. """ sess = _get_boto_session(config) - client = sess.client('ec2') - responses = client.describe_instance_status( - InstanceIds=[instance_id])['InstanceStatuses'] + client = sess.client("ec2") + responses = client.describe_instance_status(InstanceIds=[instance_id])[ + "InstanceStatuses" + ] if len(responses) == 1: return responses[0] else: return None -def upload_submission(config, instance_id, submission_name, - submissions_dir): +def upload_submission(config, instance_id, submission_name, submissions_dir): """ Upload a submission on an ec2 instance @@ -378,9 +394,9 @@ def upload_submission(config, instance_id, submission_name, out = _upload(config, instance_id, submission_path, dest_folder) return out except subprocess.CalledProcessError as e: - logger.error(f'Unable to connect during log download: {e}') + logger.error(f"Unable to connect during log download: {e}") except Exception as e: - logger.error(f'Unknown error occured during log download: {e}') + logger.error(f"Unknown error occured during log download: {e}") return 1 @@ -408,10 +424,10 @@ def download_log(config, instance_id, submission_name, folder=None): """ ramp_kit_folder = config[REMOTE_RAMP_KIT_FOLDER_FIELD] source_path = os.path.join( - ramp_kit_folder, SUBMISSIONS_FOLDER, submission_name, 'log') + ramp_kit_folder, SUBMISSIONS_FOLDER, submission_name, "log" + ) if folder is None: - dest_path = os.path.join( - config[LOCAL_LOG_FOLDER_FIELD], submission_name, 'log') + dest_path = os.path.join(config[LOCAL_LOG_FOLDER_FIELD], submission_name, "log") else: dest_path = folder try: @@ -426,11 +442,11 @@ def download_log(config, instance_id, submission_name, folder=None): out = _download(config, instance_id, source_path, dest_path) return out except Exception as e: - logger.error(f'Unknown error occured during log download: {e}') - if n_try == n_tries-1: - raise(e) + logger.error(f"Unknown error occured during log download: {e}") + if n_try == n_tries - 1: + raise (e) else: - logger.error('Trying to download the log once again') + logger.error("Trying to download the log once again") def _get_log_content(config, submission_name): @@ -444,24 +460,23 @@ def _get_log_content(config, submission_name): a str with the content of the log file """ - path = os.path.join( - config[LOCAL_LOG_FOLDER_FIELD], - submission_name, - 'log') + path = os.path.join(config[LOCAL_LOG_FOLDER_FIELD], submission_name, "log") try: - content = codecs.open(path, encoding='utf-8').read() + content = codecs.open(path, encoding="utf-8").read() content = _filter_colors(content) return content except IOError: - logger.error('Could not open log file of "{}" when trying to get ' - 'log content'.format(submission_name)) - return '' + logger.error( + 'Could not open log file of "{}" when trying to get ' + "log content".format(submission_name) + ) + return "" def _filter_colors(content): # filter linux colors from a string # check (https://pypi.org/project/colored/) - return re.sub(r'(\x1b\[)([\d]+;[\d]+;)?[\d]+m', '', content) + return re.sub(r"(\x1b\[)([\d]+;[\d]+;)?[\d]+m", "", content) def download_mprof_data(config, instance_id, submission_name, folder=None): @@ -491,24 +506,22 @@ def download_mprof_data(config, instance_id, submission_name, folder=None): """ ramp_kit_folder = config[REMOTE_RAMP_KIT_FOLDER_FIELD] source_path = os.path.join( - ramp_kit_folder, - SUBMISSIONS_FOLDER, - submission_name, - 'mprof.dat') + ramp_kit_folder, SUBMISSIONS_FOLDER, submission_name, "mprof.dat" + ) if folder is None: - dest_path = os.path.join( - config[LOCAL_LOG_FOLDER_FIELD], submission_name) + os.sep + dest_path = ( + os.path.join(config[LOCAL_LOG_FOLDER_FIELD], submission_name) + os.sep + ) else: dest_path = folder return _download(config, instance_id, source_path, dest_path) def _get_submission_max_ram(config, submission_name): - dest_path = os.path.join( - config[LOCAL_LOG_FOLDER_FIELD], submission_name) - filename = os.path.join(dest_path, 'mprof.dat') - max_mem = 0. - for line in codecs.open(filename, encoding='utf-8').readlines()[1:]: + dest_path = os.path.join(config[LOCAL_LOG_FOLDER_FIELD], submission_name) + filename = os.path.join(dest_path, "mprof.dat") + max_mem = 0.0 + for line in codecs.open(filename, encoding="utf-8").readlines()[1:]: _, mem, _ = line.split() max_mem = max(max_mem, float(mem)) return max_mem @@ -541,11 +554,13 @@ def download_predictions(config, instance_id, submission_name, folder=None): path of the folder of `training_output` containing the predictions """ - source_path = _get_remote_training_output_folder( - config, instance_id, submission_name) + '/' + source_path = ( + _get_remote_training_output_folder(config, instance_id, submission_name) + "/" + ) if folder is None: dest_path = os.path.join( - config[LOCAL_PREDICTIONS_FOLDER_FIELD], submission_name) + config[LOCAL_PREDICTIONS_FOLDER_FIELD], submission_name + ) else: dest_path = folder try: @@ -558,12 +573,13 @@ def download_predictions(config, instance_id, submission_name, folder=None): _download(config, instance_id, source_path, dest_path) return dest_path except Exception as e: - logger.error('Unknown error occured when downloading prediction' - f' e: {str(e)}') - if n_try == n_tries-1: - raise(e) + logger.error( + "Unknown error occured when downloading prediction" f" e: {str(e)}" + ) + if n_try == n_tries - 1: + raise (e) else: - logger.error('Trying to download the prediction once again') + logger.error("Trying to download the prediction once again") def _get_remote_training_output_folder(config, instance_id, submission_name): @@ -573,8 +589,9 @@ def _get_remote_training_output_folder(config, instance_id, submission_name): ~/ramp-kits/iris/submissions/submission_000001/training_output. """ ramp_kit_folder = config[REMOTE_RAMP_KIT_FOLDER_FIELD] - path = os.path.join(ramp_kit_folder, SUBMISSIONS_FOLDER, - submission_name, 'training_output') + path = os.path.join( + ramp_kit_folder, SUBMISSIONS_FOLDER, submission_name, "training_output" + ) return path @@ -596,36 +613,40 @@ def launch_train(config, instance_id, submission_name): """ ramp_kit_folder = config[REMOTE_RAMP_KIT_FOLDER_FIELD] values = { - 'ramp_kit_folder': ramp_kit_folder, - 'submission': submission_name, - 'submission_folder': os.path.join(ramp_kit_folder, SUBMISSIONS_FOLDER, - submission_name), - 'log': os.path.join(ramp_kit_folder, SUBMISSIONS_FOLDER, - submission_name, 'log') + "ramp_kit_folder": ramp_kit_folder, + "submission": submission_name, + "submission_folder": os.path.join( + ramp_kit_folder, SUBMISSIONS_FOLDER, submission_name + ), + "log": os.path.join( + ramp_kit_folder, SUBMISSIONS_FOLDER, submission_name, "log" + ), } # we use python -u so that standard input/output are flushed # and thus we can retrieve the log file live during training # without waiting for the process to finish. # We use an espace character around "$" because it is interpreted # before being run remotely and leads to an empty string - run_cmd = (r"python -u \$(which ramp_test_submission) " - r"--submission {submission} --save-y-preds ") + run_cmd = ( + r"python -u \$(which ramp_test_submission) " + r"--submission {submission} --save-y-preds " + ) if config.get(MEMORY_PROFILING_FIELD): run_cmd = ( "mprof run --output={submission_folder}/mprof.dat " - "--include-children " + run_cmd) + "--include-children " + run_cmd + ) cmd = ( "screen -dm -S {submission} sh -c '. ~/.profile;" "cd {ramp_kit_folder};" "rm -fr {submission_folder}/training_output;" "rm -f {submission_folder}/log;" - "rm -f {submission_folder}/mprof.dat;" - + run_cmd + ">{log} 2>&1'" + "rm -f {submission_folder}/mprof.dat;" + run_cmd + ">{log} 2>&1'" ) cmd = cmd.format(**values) # tag the ec2 instance with info about submission _tag_instance_by_submission(config, instance_id, submission_name) - logger.info('Launch training of {}..'.format(submission_name)) + logger.info("Launch training of {}..".format(submission_name)) return _run(config, instance_id, cmd) @@ -644,7 +665,7 @@ def abort_training(config, instance_id, submission_name): submission_id : int submission id """ - cmd = 'screen -S {} -X quit'.format(submission_name) + cmd = "screen -S {} -X quit".format(submission_name) return _run(config, instance_id, cmd) @@ -664,7 +685,7 @@ def _upload(config, instance_id, source, dest): dest : str remote file or folder """ - dest = '{user}@{ip}:' + dest + dest = "{user}@{ip}:" + dest return _rsync(config, instance_id, source, dest) @@ -685,7 +706,7 @@ def _download(config, instance_id, source, dest): local file or folder """ - source = '{user}@{ip}:' + source + source = "{user}@{ip}:" + source return _rsync(config, instance_id, source, dest) @@ -713,18 +734,18 @@ def _rsync(config, instance_id, source, dest): ami_username = config[AMI_USER_NAME_FIELD] sess = _get_boto_session(config) - resource = sess.resource('ec2') + resource = sess.resource("ec2") inst = resource.Instance(instance_id) ip = inst.public_ip_address - fmt = {'user': ami_username, 'ip': ip} + fmt = {"user": ami_username, "ip": ip} values = { - 'user': ami_username, - 'ip': ip, - 'cmd': "ssh -o 'StrictHostKeyChecking no' -i " + key_path, - 'source': source.format(**fmt), - 'dest': dest.format(**fmt), + "user": ami_username, + "ip": ip, + "cmd": "ssh -o 'StrictHostKeyChecking no' -i " + key_path, + "source": source.format(**fmt), + "dest": dest.format(**fmt), } - cmd = "rsync -e \"{cmd}\" -avzP {source} {dest}".format(**values) + cmd = 'rsync -e "{cmd}" -avzP {source} {dest}'.format(**values) logger.debug(cmd) return subprocess.call(cmd, shell=True) @@ -762,16 +783,16 @@ def _run(config, instance_id, cmd, return_output=False): ami_username = config[AMI_USER_NAME_FIELD] sess = _get_boto_session(config) - resource = sess.resource('ec2') + resource = sess.resource("ec2") inst = resource.Instance(instance_id) ip = inst.public_ip_address values = { - 'user': ami_username, - 'ip': ip, - 'ssh': "ssh -o 'StrictHostKeyChecking no' -i " + key_path, - 'cmd': cmd, + "user": ami_username, + "ip": ip, + "ssh": "ssh -o 'StrictHostKeyChecking no' -i " + key_path, + "cmd": cmd, } - cmd = "{ssh} {user}@{ip} \"{cmd}\"".format(**values) + cmd = '{ssh} {user}@{ip} "{cmd}"'.format(**values) logger.debug(cmd) if return_output: return subprocess.check_output(cmd, shell=True) @@ -785,8 +806,8 @@ def _is_ready(config, instance_id): """ st = status_of_ec2_instance(config, instance_id) if st: - check = st['InstanceStatus']['Details'][0]['Status'] - return check == 'passed' + check = st["InstanceStatus"]["Details"][0]["Status"] + return check == "passed" else: return False @@ -798,15 +819,13 @@ def _training_finished(config, instance_id, submission_name): return not _has_screen(config, instance_id, submission_name) -def _training_successful(config, instance_id, submission_name, - actual_nb_folds=None): +def _training_successful(config, instance_id, submission_name, actual_nb_folds=None): """ Return True if a finished submission have been trained successfully. If the folder training_output exists and each fold directory contains .npz prediction files we consider that the training was successful. """ - folder = _get_remote_training_output_folder( - config, instance_id, submission_name) + folder = _get_remote_training_output_folder(config, instance_id, submission_name) cmd = "ls -l {}|grep fold_|wc -l".format(folder) nb_folds = int(_run(config, instance_id, cmd, return_output=True)) @@ -857,34 +876,32 @@ def _tag_instance_by_submission(config, instance_id, submission_name): # config, instance_id, 'team_name', submission.team.name) # name = _get_submission_label(submission) # _add_or_update_tag(config, instance_id, 'Name', name) - _add_or_update_tag(config, instance_id, 'Name', submission_name) + _add_or_update_tag(config, instance_id, "Name", submission_name) def _add_or_update_tag(config, instance_id, key, value): sess = _get_boto_session(config) - client = sess.client('ec2') + client = sess.client("ec2") tags = [ - {'Key': key, 'Value': value}, + {"Key": key, "Value": value}, ] return client.create_tags(Resources=[instance_id], Tags=tags) def _get_tags(config, instance_id): sess = _get_boto_session(config) - client = sess.client('ec2') - filters = [ - {'Name': 'resource-id', 'Values': [instance_id]} - ] + client = sess.client("ec2") + filters = [{"Name": "resource-id", "Values": [instance_id]}] response = client.describe_tags(Filters=filters) - for t in response['Tags']: - t['Key'], t['Value'] - return {t['Key']: t['Value'] for t in response['Tags']} + for t in response["Tags"]: + t["Key"], t["Value"] + return {t["Key"]: t["Value"] for t in response["Tags"]} def _delete_tag(config, instance_id, key): sess = _get_boto_session(config) - client = sess.client('ec2') - tags = [{'Key': key}] + client = sess.client("ec2") + tags = [{"Key": key}] return client.delete_tags(Resources=[instance_id], Tags=tags) @@ -905,8 +922,11 @@ def _get_boto_session(config): else: raise ValueError( 'Please specify either "{}" or both of "{}" and "{}"'.format( - PROFILE_NAME_FIELD, ACCESS_KEY_ID_FIELD, - SECRET_ACCESS_KEY_FIELD)) + PROFILE_NAME_FIELD, + ACCESS_KEY_ID_FIELD, + SECRET_ACCESS_KEY_FIELD, + ) + ) def validate_config(config): @@ -915,8 +935,7 @@ def validate_config(config): raises ValueError if it is not correct. """ if AWS_CONFIG_SECTION not in config: - raise ValueError( - 'Expects "{}" section in config'.format(AWS_CONFIG_SECTION)) + raise ValueError('Expects "{}" section in config'.format(AWS_CONFIG_SECTION)) conf = config[AWS_CONFIG_SECTION] for k in conf.keys(): if k not in ALL_FIELDS: @@ -930,38 +949,47 @@ def validate_config(config): } for k in required_fields_: if k not in conf: - raise ValueError( - 'Required field "{}" missing from config'.format(k)) + raise ValueError('Required field "{}" missing from config'.format(k)) if AMI_IMAGE_NAME_FIELD in conf and AMI_IMAGE_ID_FIELD in conf: raise ValueError( 'The fields "{}" and "{}" cannot be both ' - 'specified at the same time. Please specify only ' - 'one of them'.format(AMI_IMAGE_NAME_FIELD, AMI_IMAGE_ID_FIELD)) + "specified at the same time. Please specify only " + "one of them".format(AMI_IMAGE_NAME_FIELD, AMI_IMAGE_ID_FIELD) + ) if AMI_IMAGE_NAME_FIELD not in conf and AMI_IMAGE_ID_FIELD not in conf: raise ValueError( 'Please specify either "{}" or "{}" in config.'.format( - AMI_IMAGE_NAME_FIELD, AMI_IMAGE_ID_FIELD)) - if (PROFILE_NAME_FIELD in conf - and (ACCESS_KEY_ID_FIELD in conf - or SECRET_ACCESS_KEY_FIELD in conf)): + AMI_IMAGE_NAME_FIELD, AMI_IMAGE_ID_FIELD + ) + ) + if PROFILE_NAME_FIELD in conf and ( + ACCESS_KEY_ID_FIELD in conf or SECRET_ACCESS_KEY_FIELD in conf + ): raise ValueError( 'Please specify either "{}" or both of "{}" and "{}"'.format( - PROFILE_NAME_FIELD, ACCESS_KEY_ID_FIELD, - SECRET_ACCESS_KEY_FIELD)) - if (PROFILE_NAME_FIELD not in conf - and not (ACCESS_KEY_ID_FIELD in conf - and SECRET_ACCESS_KEY_FIELD in conf)): - raise ValueError('Please specify both "{}" and "{}"'.format( - ACCESS_KEY_ID_FIELD, SECRET_ACCESS_KEY_FIELD, - )) + PROFILE_NAME_FIELD, + ACCESS_KEY_ID_FIELD, + SECRET_ACCESS_KEY_FIELD, + ) + ) + if PROFILE_NAME_FIELD not in conf and not ( + ACCESS_KEY_ID_FIELD in conf and SECRET_ACCESS_KEY_FIELD in conf + ): + raise ValueError( + 'Please specify both "{}" and "{}"'.format( + ACCESS_KEY_ID_FIELD, + SECRET_ACCESS_KEY_FIELD, + ) + ) hooks = conf.get(HOOKS_SECTION) if hooks: for hook_name in hooks.keys(): if hook_name not in HOOKS: - hook_names = ','.join(HOOKS) + hook_names = ",".join(HOOKS) raise ValueError( - 'Invalid hook name : {}, hooks should be one of ' - 'these : {}'.format(hook_name, hook_names)) + "Invalid hook name : {}, hooks should be one of " + "these : {}".format(hook_name, hook_names) + ) def is_spot_terminated(config, instance_id): @@ -970,26 +998,31 @@ def is_spot_terminated(config, instance_id): 'instance-action' will be present.""" cmd_timeout = 1 n_retry = 9 - cmd = ("curl http://169.254.169.254/latest/meta-data/instance-action" - f" -m {cmd_timeout} --retry {n_retry}") + cmd = ( + "curl http://169.254.169.254/latest/meta-data/instance-action" + f" -m {cmd_timeout} --retry {n_retry}" + ) try: out = _run(config, instance_id, cmd, return_output=True) - out = out.decode('utf-8') + out = out.decode("utf-8") except subprocess.CalledProcessError: - logger.error('Unable to run curl: {e}') + logger.error("Unable to run curl: {e}") return False except Exception as e: - logger.error('Unhandled exception occurred when checking for' - f' instance action: {e}') + logger.error( + "Unhandled exception occurred when checking for" f" instance action: {e}" + ) return False - if out == 'none': + if out == "none": terminated = False else: - logger.info(f'An instance-action is present on {instance_id}, ' - 'indicating that this spot instance is marked for ' - 'termination.') + logger.info( + f"An instance-action is present on {instance_id}, " + "indicating that this spot instance is marked for " + "termination." + ) terminated = True return terminated @@ -997,6 +1030,10 @@ def is_spot_terminated(config, instance_id): def check_instance_status(config, instance_id): """Return the status of an instance.""" sess = _get_boto_session(config) - client = sess.client('ec2') - response = client.describe_instance_status(InstanceIds=[instance_id, ]) - return response['InstanceStatuses'][0]['InstanceState']['Name'] + client = sess.client("ec2") + response = client.describe_instance_status( + InstanceIds=[ + instance_id, + ] + ) + return response["InstanceStatuses"][0]["InstanceState"]["Name"] diff --git a/ramp-engine/ramp_engine/aws/worker.py b/ramp-engine/ramp_engine/aws/worker.py index 5c5a9b1e5..8e7af9d07 100644 --- a/ramp-engine/ramp_engine/aws/worker.py +++ b/ramp-engine/ramp_engine/aws/worker.py @@ -5,11 +5,11 @@ from . import api as aws -logger = logging.getLogger('RAMP-AWS') +logger = logging.getLogger("RAMP-AWS") -log_file = 'aws_worker.log' -formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s') # noqa -fileHandler = logging.FileHandler(log_file, mode='a') +log_file = "aws_worker.log" +formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s") # noqa +fileHandler = logging.FileHandler(log_file, mode="a") fileHandler.setFormatter(formatter) streamHandler = logging.StreamHandler() streamHandler.setFormatter(formatter) @@ -47,7 +47,7 @@ class AWSWorker(BaseWorker): def __init__(self, config, submission): super().__init__(config, submission) - self.submissions_path = self.config['submissions_dir'] + self.submissions_path = self.config["submissions_dir"] self.instance = None def setup(self): @@ -57,36 +57,41 @@ def setup(self): to the instance. """ # sanity check for the configuration variable - for required_param in ('instance_type', 'access_key_id'): + for required_param in ("instance_type", "access_key_id"): self._check_config_name(self.config, required_param) - logger.info("Setting up AWSWorker for submission '{}'".format( - self.submission)) + logger.info("Setting up AWSWorker for submission '{}'".format(self.submission)) _instances, status = aws.launch_ec2_instances(self.config) if not _instances: - if status == 'retry': + if status == "retry": # there was a timeout error, put this submission back in the # queue and try again later - logger.warning("Unable to launch instance for submission " - f"{self.submission}. Adding it back to the " - "queue and will try again later") - self.status = 'retry' + logger.warning( + "Unable to launch instance for submission " + f"{self.submission}. Adding it back to the " + "queue and will try again later" + ) + self.status = "retry" else: - logger.error("Unable to launch instance for submission " - f"{self.submission}. An error occured: {status}") - self.status = 'error' + logger.error( + "Unable to launch instance for submission " + f"{self.submission}. An error occured: {status}" + ) + self.status = "error" return else: - logger.info("Instance launched for submission '{}'".format( - self.submission)) - self.instance, = _instances + logger.info("Instance launched for submission '{}'".format(self.submission)) + (self.instance,) = _instances for _ in range(5): # try uploading the submission a few times, as this regularly fails exit_status = aws.upload_submission( - self.config, self.instance.id, self.submission, - self.submissions_path) + self.config, + self.instance.id, + self.submission, + self.submissions_path, + ) if exit_status == 0: break else: @@ -94,11 +99,12 @@ def setup(self): if exit_status != 0: logger.error( 'Cannot upload submission "{}"' - ', an error occured'.format(self.submission)) - self.status = 'error' + ", an error occured".format(self.submission) + ) + self.status = "error" else: logger.info("Uploaded submission '{}'".format(self.submission)) - self.status = 'setup' + self.status = "setup" def launch_submission(self): """Launch the submission. @@ -106,37 +112,41 @@ def launch_submission(self): Basically, this runs ``ramp_test_submission`` inside the Amazon instance. """ - if self.status == 'running': - raise RuntimeError("Cannot launch submission: one is already " - "started") - if self.status == 'error': + if self.status == "running": + raise RuntimeError("Cannot launch submission: one is already " "started") + if self.status == "error": raise RuntimeError("Cannot launch submission: the setup failed") try: exit_status = aws.launch_train( - self.config, self.instance.id, self.submission) + self.config, self.instance.id, self.submission + ) except Exception as e: - logger.error(f'Unknown error occurred: {e}') + logger.error(f"Unknown error occurred: {e}") exit_status = 1 if exit_status != 0: logger.error( 'Cannot start training of submission "{}"' - ', an error occured.'.format(self.submission)) - self.status = 'error' + ", an error occured.".format(self.submission) + ) + self.status = "error" else: - self.status = 'running' + self.status = "running" return exit_status def _is_submission_finished(self): try: return aws._training_finished( - self.config, self.instance.id, self.submission) + self.config, self.instance.id, self.submission + ) except subprocess.CalledProcessError as e: # it is no longer possible to connect to the instance # possibly it was terminated from outside. restart the submission - logger.warning("Unable to connect to the instance for submission " - f"{self.submission}. Adding the submission back to" - " the queue and will try again later") + logger.warning( + "Unable to connect to the instance for submission " + f"{self.submission}. Adding the submission back to" + " the queue and will try again later" + ) raise e def _is_submission_interrupted(self): @@ -150,45 +160,46 @@ def collect_results(self): # with dispatcher). # The event config: 'check_finished_training_interval_secs' # is used here, but again only when worker used alone. - if self.status == 'running': + if self.status == "running": aws._wait_until_train_finished( - self.config, self.instance.id, self.submission) - self.status = 'finished' - if self.status != 'finished': - raise ValueError("Cannot collect results if worker is not" - "'running' or 'finished'") + self.config, self.instance.id, self.submission + ) + self.status = "finished" + if self.status != "finished": + raise ValueError( + "Cannot collect results if worker is not" "'running' or 'finished'" + ) logger.info("Collecting submission '{}'".format(self.submission)) exit_status = 0 try: - _ = aws.download_log(self.config, - self.instance.id, self.submission) + _ = aws.download_log(self.config, self.instance.id, self.submission) except Exception as e: - logger.error("Error occurred when downloading the logs" - f" from the submission: {e}") + logger.error( + "Error occurred when downloading the logs" f" from the submission: {e}" + ) exit_status = 2 error_msg = str(e) - self.status = 'error' + self.status = "error" if exit_status == 0: - if aws._training_successful( - self.config, self.instance.id, self.submission): + if aws._training_successful(self.config, self.instance.id, self.submission): try: - _ = aws.download_predictions(self.config, - self.instance.id, - self.submission) + _ = aws.download_predictions( + self.config, self.instance.id, self.submission + ) except Exception as e: - logger.error("Downloading the prediction failed with" - f"error {e}") - self.status = 'error' + logger.error("Downloading the prediction failed with" f"error {e}") + self.status = "error" exit_status, error_msg = 1, str(e) else: - self.status = 'collected' - exit_status, error_msg = 0, '' + self.status = "collected" + exit_status, error_msg = 0, "" else: error_msg = _get_traceback( - aws._get_log_content(self.config, self.submission)) - self.status = 'collected' + aws._get_log_content(self.config, self.submission) + ) + self.status = "collected" exit_status = 1 logger.info(repr(self)) return exit_status, error_msg @@ -197,9 +208,7 @@ def teardown(self): """Terminate the Amazon instance""" # Only terminate if instance is running if self.instance: - instance_status = aws.check_instance_status( - self.config, self.instance.id - ) - if instance_status == 'running': + instance_status = aws.check_instance_status(self.config, self.instance.id) + if instance_status == "running": aws.terminate_ec2_instance(self.config, self.instance.id) super().teardown() diff --git a/ramp-engine/ramp_engine/base.py b/ramp-engine/ramp_engine/base.py index 6a6d0efd0..d9ba59ebe 100644 --- a/ramp-engine/ramp_engine/base.py +++ b/ramp-engine/ramp_engine/base.py @@ -3,11 +3,11 @@ from datetime import datetime import subprocess -logger = logging.getLogger('RAMP-WORKER') +logger = logging.getLogger("RAMP-WORKER") log_file = "worker.log" -formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s') # noqa -fileHandler = logging.FileHandler(log_file, mode='a') +formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s") # noqa +fileHandler = logging.FileHandler(log_file, mode="a") fileHandler.setFormatter(formatter) streamHandler = logging.StreamHandler() streamHandler.setFormatter(formatter) @@ -41,27 +41,29 @@ class BaseWorker(metaclass=ABCMeta): * 'retry': the worker has been interrupted (and will be retried). * 'killed' """ + def __init__(self, config, submission): self.config = config self.submission = submission - self.status = 'initialized' + self.status = "initialized" def setup(self): """Setup the worker with some given setting required before launching a submission.""" - self.status = 'setup' + self.status = "setup" @staticmethod def _check_config_name(config, param): if param not in config.keys(): - raise ValueError("The worker required the parameter '{}' in the " - "configuration given at instantiation. Only {}" - "parameters were given." - .format(param, config.keys())) + raise ValueError( + "The worker required the parameter '{}' in the " + "configuration given at instantiation. Only {}" + "parameters were given.".format(param, config.keys()) + ) def teardown(self): """Clean up (i.e., removing path, etc.) before killing the worker.""" - self.status = 'killed' + self.status = "killed" @abstractmethod def _is_submission_interrupted(self): @@ -77,17 +79,17 @@ def _is_submission_finished(self): def status(self): status = self._status try: - if status == 'running': + if status == "running": self._status_running_check_time = datetime.utcnow() if self._is_submission_interrupted(): - self._status = 'retry' + self._status = "retry" elif self._is_submission_finished(): - self._status = 'finished' + self._status = "finished" except subprocess.CalledProcessError: # there was a problem while connecting to the worker # if you are using AWS it might be that an instance was terminated # from outside. retry the submission - self._status = 'retry' + self._status = "retry" return self._status @status.setter @@ -112,27 +114,32 @@ def time_since_last_status_check(self): """ if not hasattr(self, "_status_running_check_time"): return None - elapsed_time = ((datetime.utcnow() - - self._status_running_check_time).total_seconds()) + elapsed_time = ( + datetime.utcnow() - self._status_running_check_time + ).total_seconds() return elapsed_time @abstractmethod def launch_submission(self): """Launch a submission to be trained.""" - self.status = 'running' + self.status = "running" @abstractmethod def collect_results(self): """Collect the results after submission training.""" - if self.status == 'initialized': - raise ValueError('The worker has not been setup and no submission ' - 'was launched. Call the method setup() and ' - 'launch_submission() before to collect the ' - 'results.') - elif self.status == 'setup': - raise ValueError('No submission was launched. Call the method ' - 'launch_submission() and then try again to ' - 'collect the results.') + if self.status == "initialized": + raise ValueError( + "The worker has not been setup and no submission " + "was launched. Call the method setup() and " + "launch_submission() before to collect the " + "results." + ) + elif self.status == "setup": + raise ValueError( + "No submission was launched. Call the method " + "launch_submission() and then try again to " + "collect the results." + ) def launch(self): """Launch a standalone RAMP worker. @@ -148,10 +155,11 @@ def launch(self): self.teardown() def __str__(self): - msg = ('{worker_name}({submission_name}): status="{status}"' - .format(worker_name=self.__class__.__name__, - submission_name=self.submission, - status=self.status)) + msg = '{worker_name}({submission_name}): status="{status}"'.format( + worker_name=self.__class__.__name__, + submission_name=self.submission, + status=self.status, + ) return msg def __repr__(self): @@ -174,13 +182,13 @@ def _get_traceback(content): """ if not content: - return '' + return "" # cut_exception_text = content.rfind('--->') # was like commented line above in ramp-board # but there is no ---> in logs when we use # ramp_test_submission, so we just search for the # first occurence of 'Traceback'. - cut_exception_text = content.find('Traceback') + cut_exception_text = content.find("Traceback") if cut_exception_text > 0: content = content[cut_exception_text:] return content diff --git a/ramp-engine/ramp_engine/cli.py b/ramp-engine/ramp_engine/cli.py index 322790fe1..9c116423b 100644 --- a/ramp-engine/ramp_engine/cli.py +++ b/ramp-engine/ramp_engine/cli.py @@ -10,7 +10,7 @@ from ramp_engine import available_workers -CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) +CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @click.group(context_settings=CONTEXT_SETTINGS) @@ -20,12 +20,18 @@ def main(): @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file in YAML format containing the database ' - 'information.') -@click.option("--events-dir", show_default=True, - help='Directory where the event config files are located.') -@click.option('-v', '--verbose', count=True) +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file in YAML format containing the database " "information.", +) +@click.option( + "--events-dir", + show_default=True, + help="Directory where the event config files are located.", +) +@click.option("-v", "--verbose", count=True) def daemon(config, events_dir, verbose): """Launch the RAMP dispatcher. @@ -38,8 +44,9 @@ def daemon(config, events_dir, verbose): else: level = logging.DEBUG logging.basicConfig( - format='%(asctime)s - %(levelname)s - %(name)s - %(message)s', - level=level, datefmt='%Y:%m:%d %H:%M:%S' + format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", + level=level, + datefmt="%Y:%m:%d %H:%M:%S", ) daemon = Daemon(config=config, events_dir=events_dir) @@ -47,13 +54,18 @@ def daemon(config, events_dir, verbose): @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file in YAML format containing the database ' - 'information.') -@click.option("--event-config", show_default=True, - help='Configuration file in YAML format containing the RAMP ' - 'event information.') -@click.option('-v', '--verbose', count=True) +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file in YAML format containing the database " "information.", +) +@click.option( + "--event-config", + show_default=True, + help="Configuration file in YAML format containing the RAMP " "event information.", +) +@click.option("-v", "--verbose", count=True) def dispatcher(config, event_config, verbose): """Launch the RAMP dispatcher. @@ -66,36 +78,44 @@ def dispatcher(config, event_config, verbose): else: level = logging.DEBUG logging.basicConfig( - format='%(asctime)s - %(levelname)s - %(name)s - %(message)s', - level=level, datefmt='%Y:%m:%d %H:%M:%S' + format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", + level=level, + datefmt="%Y:%m:%d %H:%M:%S", ) internal_event_config = read_config(event_config) - worker_type = available_workers[ - internal_event_config['worker']['worker_type'] - ] - - dispatcher_config = (internal_event_config['dispatcher'] - if 'dispatcher' in internal_event_config else {}) - n_workers = dispatcher_config.get('n_workers', -1) - n_threads = dispatcher_config.get('n_threads', None) - hunger_policy = dispatcher_config.get('hunger_policy', 'sleep') - time_between_collection = dispatcher_config.get( - 'time_between_collection', 1) + worker_type = available_workers[internal_event_config["worker"]["worker_type"]] + + dispatcher_config = ( + internal_event_config["dispatcher"] + if "dispatcher" in internal_event_config + else {} + ) + n_workers = dispatcher_config.get("n_workers", -1) + n_threads = dispatcher_config.get("n_threads", None) + hunger_policy = dispatcher_config.get("hunger_policy", "sleep") + time_between_collection = dispatcher_config.get("time_between_collection", 1) disp = Dispatcher( - config=config, event_config=event_config, worker=worker_type, - n_workers=n_workers, n_threads=n_threads, hunger_policy=hunger_policy, - time_between_collection=time_between_collection + config=config, + event_config=event_config, + worker=worker_type, + n_workers=n_workers, + n_threads=n_threads, + hunger_policy=hunger_policy, + time_between_collection=time_between_collection, ) disp.launch() @main.command() -@click.option("--event-config", default='config.yml', show_default=True, - help='Configuration file in YAML format containing the RAMP ' - 'event information.') -@click.option('--submission', help='The submission name') -@click.option('-v', '--verbose', is_flag=True) +@click.option( + "--event-config", + default="config.yml", + show_default=True, + help="Configuration file in YAML format containing the RAMP " "event information.", +) +@click.option("--submission", help="The submission name") +@click.option("-v", "--verbose", is_flag=True) def worker(event_config, submission, verbose): """Launch a standalone RAMP worker. @@ -108,12 +128,13 @@ def worker(event_config, submission, verbose): else: level = logging.DEBUG logging.basicConfig( - format='%(asctime)s - %(levelname)s - %(name)s - %(message)s', - level=level, datefmt='%Y:%m:%d %H:%M:%S' + format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", + level=level, + datefmt="%Y:%m:%d %H:%M:%S", ) config = read_config(event_config) worker_params = generate_worker_config(config) - worker_type = available_workers[worker_params['worker_type']] + worker_type = available_workers[worker_params["worker_type"]] worker = worker_type(worker_params, submission) worker.launch() @@ -122,5 +143,5 @@ def start(): main() -if __name__ == '__main__': +if __name__ == "__main__": start() diff --git a/ramp-engine/ramp_engine/conda.py b/ramp-engine/ramp_engine/conda.py index 4c103a4b5..904e5d986 100644 --- a/ramp-engine/ramp_engine/conda.py +++ b/ramp-engine/ramp_engine/conda.py @@ -10,7 +10,7 @@ def _conda_info_envs() -> Dict: proc = subprocess.Popen( ["conda", "info", "--envs", "--json"], stdout=subprocess.PIPE, - stderr=subprocess.PIPE + stderr=subprocess.PIPE, ) stdout, _ = proc.communicate() conda_info = json.loads(stdout) @@ -18,43 +18,57 @@ def _conda_info_envs() -> Dict: def _get_conda_env_path(conda_info: Dict, env_name: str, worker=None) -> str: - """Get path for a python executable of a conda env - """ + """Get path for a python executable of a conda env""" import os - if env_name == 'base': - return os.path.join(conda_info['envs'][0], 'bin') + if env_name == "base": + return os.path.join(conda_info["envs"][0], "bin") else: - envs_path = conda_info['envs'][1:] + envs_path = conda_info["envs"][1:] if not envs_path: - worker.status = 'error' - raise ValueError('Only the conda base environment exist. You ' - 'need to create the "{}" conda environment ' - 'to use it.'.format(env_name)) + worker.status = "error" + raise ValueError( + "Only the conda base environment exist. You " + 'need to create the "{}" conda environment ' + "to use it.".format(env_name) + ) for env in envs_path: if env_name == os.path.split(env)[-1]: - return os.path.join(env, 'bin') - worker.status = 'error' - raise ValueError(f'The specified conda environment {env_name} ' - f'does not exist. You need to create it.') + return os.path.join(env, "bin") + worker.status = "error" + raise ValueError( + f"The specified conda environment {env_name} " + f"does not exist. You need to create it." + ) -def _conda_ramp_test_submission(config: Dict, submission: str, cmd_ramp: str, - log_dir: str, wait: bool = False): +def _conda_ramp_test_submission( + config: Dict, + submission: str, + cmd_ramp: str, + log_dir: str, + wait: bool = False, +): import os import subprocess if not os.path.exists(log_dir): os.makedirs(log_dir) - log_file = open(os.path.join(log_dir, 'log'), 'wb+') + log_file = open(os.path.join(log_dir, "log"), "wb+") proc = subprocess.Popen( - [cmd_ramp, - '--submission', submission, - '--ramp-kit-dir', config['kit_dir'], - '--ramp-data-dir', config['data_dir'], - '--ramp-submission-dir', config['submissions_dir'], - '--save-output', - '--ignore-warning'], + [ + cmd_ramp, + "--submission", + submission, + "--ramp-kit-dir", + config["kit_dir"], + "--ramp-data-dir", + config["data_dir"], + "--ramp-submission-dir", + config["submissions_dir"], + "--save-output", + "--ignore-warning", + ], stdout=log_file, stderr=log_file, ) diff --git a/ramp-engine/ramp_engine/daemon.py b/ramp-engine/ramp_engine/daemon.py index 62706fad4..ba90217f7 100644 --- a/ramp-engine/ramp_engine/daemon.py +++ b/ramp-engine/ramp_engine/daemon.py @@ -29,14 +29,10 @@ class Daemon: def __init__(self, config, events_dir): self.config = config - self._database_config = read_config( - config, filter_section="sqlalchemy" - ) + self._database_config = read_config(config, filter_section="sqlalchemy") self.events_dir = os.path.abspath(events_dir) if not os.path.isdir(self.events_dir): - raise ValueError( - "The path {} is not existing.".format(events_dir) - ) + raise ValueError("The path {} is not existing.".format(events_dir)) self._proc = deque() signal.signal(signal.SIGINT, self.kill_dispatcher) signal.signal(signal.SIGTERM, self.kill_dispatcher) @@ -45,14 +41,15 @@ def __init__(self, config, events_dir): def launch_dispatchers(self, session): events = [e for e in session.query(Event).all() if e.is_open] for e in events: - event_config = os.path.join( - self.events_dir, e.name, "config.yml" - ) + event_config = os.path.join(self.events_dir, e.name, "config.yml") cmd_dispatcher = [ - "ramp-launch", "dispatcher", - "--config", self.config, - "--event-config", event_config, - "--verbose" + "ramp-launch", + "dispatcher", + "--config", + self.config, + "--event-config", + event_config, + "--verbose", ] proc = subprocess.Popen( cmd_dispatcher, @@ -60,17 +57,13 @@ def launch_dispatchers(self, session): stderr=subprocess.PIPE, ) self._proc.append((e.name, proc)) - logger.info( - "Launch dispatcher for the event {}".format(e.name) - ) + logger.info("Launch dispatcher for the event {}".format(e.name)) def kill_dispatcher(self, signum, frame): while len(self._proc) != 0: event, proc = self._proc.pop() proc.kill() - logger.info( - "Kill dispatcher for the event {}".format(event) - ) + logger.info("Kill dispatcher for the event {}".format(event)) self._poison_pill = True def launch(self): diff --git a/ramp-engine/ramp_engine/dispatcher.py b/ramp-engine/ramp_engine/dispatcher.py index d78a42ef6..339444a5d 100644 --- a/ramp-engine/ramp_engine/dispatcher.py +++ b/ramp-engine/ramp_engine/dispatcher.py @@ -29,11 +29,11 @@ from .local import CondaEnvWorker -logger = logging.getLogger('RAMP-DISPATCHER') +logger = logging.getLogger("RAMP-DISPATCHER") -log_file = 'dispatcher.log' -formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s') # noqa -fileHandler = logging.FileHandler(log_file, mode='a') +log_file = "dispatcher.log" +formatter = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s") # noqa +fileHandler = logging.FileHandler(log_file, mode="a") fileHandler.setFormatter(formatter) streamHandler = logging.StreamHandler() streamHandler.setFormatter(formatter) @@ -86,12 +86,23 @@ class Dispatcher: Thus, if the time between checks is too small, the repetitive SSH requests may be potentially blocked by the cloud provider. """ - def __init__(self, config, event_config, worker=None, n_workers=1, - n_threads=None, hunger_policy=None, - time_between_collection=1): + + def __init__( + self, + config, + event_config, + worker=None, + n_workers=1, + n_threads=None, + hunger_policy=None, + time_between_collection=1, + ): self.worker = CondaEnvWorker if worker is None else worker - self.n_workers = (max(multiprocessing.cpu_count() + 1 + n_workers, 1) - if n_workers < 0 else n_workers) + self.n_workers = ( + max(multiprocessing.cpu_count() + 1 + n_workers, 1) + if n_workers < 0 + else n_workers + ) self.hunger_policy = hunger_policy self.time_between_collection = time_between_collection # init the poison pill to kill the dispatcher @@ -101,14 +112,12 @@ def __init__(self, config, event_config, worker=None, n_workers=1, self._processing_worker_queue = LifoQueue(maxsize=self.n_workers) self._processed_submission_queue = Queue() # split the different configuration required - if (isinstance(config, str) and - isinstance(event_config, str)): - self._database_config = read_config(config, - filter_section='sqlalchemy') + if isinstance(config, str) and isinstance(event_config, str): + self._database_config = read_config(config, filter_section="sqlalchemy") self._ramp_config = generate_ramp_config(event_config, config) else: - self._database_config = config['sqlalchemy'] - self._ramp_config = event_config['ramp'] + self._database_config = config["sqlalchemy"] + self._ramp_config = event_config["ramp"] self._worker_config = generate_worker_config(event_config, config) # set the number of threads for openmp, openblas, and mkl self.n_threads = n_threads @@ -117,16 +126,16 @@ def __init__(self, config, event_config, worker=None, n_workers=1, raise TypeError( "The parameter 'n_threads' should be a positive integer. " "Got {} instead.".format(repr(self.n_threads)) - ) - for lib in ('OMP', 'MKL', 'OPENBLAS'): - os.environ[lib + '_NUM_THREADS'] = str(self.n_threads) - self._logger = logger.getChild(self._ramp_config['event_name']) + ) + for lib in ("OMP", "MKL", "OPENBLAS"): + os.environ[lib + "_NUM_THREADS"] = str(self.n_threads) + self._logger = logger.getChild(self._ramp_config["event_name"]) def fetch_from_db(self, session): """Fetch the submission from the database and create the workers.""" - submissions = get_submissions(session, - self._ramp_config['event_name'], - state='new') + submissions = get_submissions( + session, self._ramp_config["event_name"], state="new" + ) if not submissions: return for submission_id, submission_name, _ in submissions: @@ -136,124 +145,131 @@ def fetch_from_db(self, session): continue # create the worker worker = self.worker(self._worker_config, submission_name) - set_submission_state(session, submission_id, 'sent_to_training') + set_submission_state(session, submission_id, "sent_to_training") update_user_leaderboards( - session, self._ramp_config['event_name'], - submission .team.name, new_only=True, + session, + self._ramp_config["event_name"], + submission.team.name, + new_only=True, + ) + self._awaiting_worker_queue.put_nowait( + (worker, (submission_id, submission_name)) ) - self._awaiting_worker_queue.put_nowait((worker, (submission_id, - submission_name))) self._logger.info( - f'Submission {submission_name} added to the queue of ' - 'submission to be processed' + f"Submission {submission_name} added to the queue of " + "submission to be processed" ) def launch_workers(self, session): """Launch the awaiting workers if possible.""" - while (not self._processing_worker_queue.full() and - not self._awaiting_worker_queue.empty()): - worker, (submission_id, submission_name) = \ - self._awaiting_worker_queue.get() - self._logger.info(f'Starting worker: {worker}') + while ( + not self._processing_worker_queue.full() + and not self._awaiting_worker_queue.empty() + ): + worker, ( + submission_id, + submission_name, + ) = self._awaiting_worker_queue.get() + self._logger.info(f"Starting worker: {worker}") try: worker.setup() if worker.status != "error": worker.launch_submission() except Exception as e: - self._logger.error( - f'Worker finished with unhandled exception:\n {e}' - ) - worker.status = 'error' - if worker.status == 'error': - set_submission_state(session, submission_id, 'checking_error') + self._logger.error(f"Worker finished with unhandled exception:\n {e}") + worker.status = "error" + if worker.status == "error": + set_submission_state(session, submission_id, "checking_error") worker.teardown() # kill the worker self._logger.info( - f'Worker {worker} killed due to an error ' - f'while connecting to AWS worker' + f"Worker {worker} killed due to an error " + f"while connecting to AWS worker" + ) + stderr = ( + "There was a problem with sending your submission" + " for training. This problem is on RAMP side" + " and most likely it is not related to your" + " code. If this happened for the first time" + " to this submission you might" + " consider submitting the same code once again." + " Else, please contact the event organizers." ) - stderr = ("There was a problem with sending your submission" - " for training. This problem is on RAMP side" - " and most likely it is not related to your" - " code. If this happened for the first time" - " to this submission you might" - " consider submitting the same code once again." - " Else, please contact the event organizers." - ) set_submission_error_msg(session, submission_id, stderr) continue - set_submission_state(session, submission_id, 'training') + set_submission_state(session, submission_id, "training") submission = get_submission_by_id(session, submission_id) update_user_leaderboards( - session, self._ramp_config['event_name'], - submission.team.name, new_only=True, + session, + self._ramp_config["event_name"], + submission.team.name, + new_only=True, ) self._processing_worker_queue.put_nowait( - (worker, (submission_id, submission_name))) - self._logger.info( - f'Store the worker {worker} into the processing queue' + (worker, (submission_id, submission_name)) ) + self._logger.info(f"Store the worker {worker} into the processing queue") def collect_result(self, session): """Collect result from processed workers.""" try: workers, submissions = zip( - *[self._processing_worker_queue.get() - for _ in range(self._processing_worker_queue.qsize())] + *[ + self._processing_worker_queue.get() + for _ in range(self._processing_worker_queue.qsize()) + ] ) except ValueError: - if self.hunger_policy == 'sleep': + if self.hunger_policy == "sleep": time.sleep(5) - elif self.hunger_policy == 'exit': + elif self.hunger_policy == "exit": self._poison_pill = True return - for worker, (submission_id, submission_name) in zip(workers, - submissions): + for worker, (submission_id, submission_name) in zip(workers, submissions): dt = worker.time_since_last_status_check() if (dt is not None) and (dt < self.time_between_collection): self._processing_worker_queue.put_nowait( - (worker, (submission_id, submission_name))) + (worker, (submission_id, submission_name)) + ) time.sleep(0) continue - elif worker.status == 'running': + elif worker.status == "running": self._processing_worker_queue.put_nowait( - (worker, (submission_id, submission_name))) + (worker, (submission_id, submission_name)) + ) time.sleep(0) - elif worker.status == 'retry': - set_submission_state(session, submission_id, 'new') + elif worker.status == "retry": + set_submission_state(session, submission_id, "new") self._logger.info( - f'Submission: {submission_id} has been interrupted. ' - 'It will be added to queue again and retried.' + f"Submission: {submission_id} has been interrupted. " + "It will be added to queue again and retried." ) worker.teardown() else: - self._logger.info(f'Collecting results from worker {worker}') + self._logger.info(f"Collecting results from worker {worker}") returncode, stderr = worker.collect_results() if returncode: if returncode == 124: - self._logger.info( - f'Worker {worker} killed due to timeout.' - ) - submission_status = 'training_error' + self._logger.info(f"Worker {worker} killed due to timeout.") + submission_status = "training_error" elif returncode == 2: # Error occurred when downloading the logs - submission_status = 'checking_error' + submission_status = "checking_error" else: self._logger.info( - f'Worker {worker} killed due to an error ' - f'during training: {stderr}' + f"Worker {worker} killed due to an error " + f"during training: {stderr}" ) - submission_status = 'training_error' + submission_status = "training_error" else: - submission_status = 'tested' - set_submission_state( - session, submission_id, submission_status - ) + submission_status = "tested" + set_submission_state(session, submission_id, submission_status) set_submission_error_msg(session, submission_id, stderr) self._processed_submission_queue.put_nowait( - (submission_id, submission_name)) + (submission_id, submission_name) + ) worker.teardown() def update_database_results(self, session): @@ -261,46 +277,47 @@ def update_database_results(self, session): make_update_leaderboard = False while not self._processed_submission_queue.empty(): make_update_leaderboard = True - submission_id, submission_name = \ - self._processed_submission_queue.get_nowait() - if 'error' in get_submission_state(session, submission_id): + ( + submission_id, + submission_name, + ) = self._processed_submission_queue.get_nowait() + if "error" in get_submission_state(session, submission_id): continue self._logger.info( - f'Write info in database for submission {submission_name}' + f"Write info in database for submission {submission_name}" ) path_predictions = os.path.join( - self._worker_config['predictions_dir'], submission_name + self._worker_config["predictions_dir"], submission_name ) set_time(session, submission_id, path_predictions) set_scores(session, submission_id, path_predictions) set_bagged_scores(session, submission_id, path_predictions) - set_submission_state(session, submission_id, 'scored') + set_submission_state(session, submission_id, "scored") if make_update_leaderboard: - self._logger.info('Update all leaderboards') - update_leaderboards(session, self._ramp_config['event_name']) - update_all_user_leaderboards(session, - self._ramp_config['event_name']) - self._logger.info('Leaderboards updated') + self._logger.info("Update all leaderboards") + update_leaderboards(session, self._ramp_config["event_name"]) + update_all_user_leaderboards(session, self._ramp_config["event_name"]) + self._logger.info("Leaderboards updated") @staticmethod def _reset_submission_after_failure(session, even_name): submissions = get_submissions(session, even_name, state=None) for submission_id, _, _ in submissions: submission_state = get_submission_state(session, submission_id) - if submission_state in ('training', 'sent_to_training'): - set_submission_state(session, submission_id, 'new') + if submission_state in ("training", "sent_to_training"): + set_submission_state(session, submission_id, "new") def launch(self): """Launch the dispatcher.""" - self._logger.info('Starting the RAMP dispatcher') + self._logger.info("Starting the RAMP dispatcher") with session_scope(self._database_config) as session: - self._logger.info('Open a session to the database') + self._logger.info("Open a session to the database") self._logger.info( - 'Reset unfinished trained submission from previous session' + "Reset unfinished trained submission from previous session" ) self._reset_submission_after_failure( - session, self._ramp_config['event_name'] + session, self._ramp_config["event_name"] ) try: while not self._poison_pill: @@ -312,6 +329,6 @@ def launch(self): # reset the submissions to 'new' in case of error or unfinished # training self._reset_submission_after_failure( - session, self._ramp_config['event_name'] + session, self._ramp_config["event_name"] ) - self._logger.info('Dispatcher killed by the poison pill') + self._logger.info("Dispatcher killed by the poison pill") diff --git a/ramp-engine/ramp_engine/local.py b/ramp-engine/ramp_engine/local.py index be92d265a..31cf5667f 100644 --- a/ramp-engine/ramp_engine/local.py +++ b/ramp-engine/ramp_engine/local.py @@ -7,7 +7,7 @@ from .conda import _conda_info_envs, _get_conda_env_path from .conda import _conda_ramp_test_submission -logger = logging.getLogger('RAMP-WORKER') +logger = logging.getLogger("RAMP-WORKER") class CondaEnvWorker(BaseWorker): @@ -47,6 +47,7 @@ class CondaEnvWorker(BaseWorker): * 'finished': the worker finished to train the submission. * 'collected': the results of the training have been collected. """ + def __init__(self, config, submission): super().__init__(config=config, submission=submission) @@ -57,11 +58,16 @@ def setup(self): the configuration passed when instantiating the worker. """ # sanity check for the configuration variable - for required_param in ('kit_dir', 'data_dir', 'submissions_dir', - 'logs_dir', 'predictions_dir'): + for required_param in ( + "kit_dir", + "data_dir", + "submissions_dir", + "logs_dir", + "predictions_dir", + ): self._check_config_name(self.config, required_param) # find the path to the conda environment - env_name = self.config.get('conda_env', 'base') + env_name = self.config.get("conda_env", "base") conda_info = _conda_info_envs() self._python_bin_path = _get_conda_env_path(conda_info, env_name, self) @@ -70,11 +76,14 @@ def setup(self): def teardown(self): """Remove the predictions stores within the submission.""" - if self.status not in ('collected', 'retry'): + if self.status not in ("collected", "retry"): raise ValueError("Collect the results before to kill the worker.") - output_training_dir = os.path.join(self.config['kit_dir'], - 'submissions', self.submission, - 'training_output') + output_training_dir = os.path.join( + self.config["kit_dir"], + "submissions", + self.submission, + "training_output", + ) if os.path.exists(output_training_dir): shutil.rmtree(output_training_dir) super().teardown() @@ -105,7 +114,7 @@ def check_timeout(self): @property def timeout(self): - return self.config.get('timeout', 7200) + return self.config.get("timeout", 7200) def launch_submission(self): """Launch the submission. @@ -114,11 +123,12 @@ def launch_submission(self): environment given in the configuration. The submission is launched in a subprocess to free to not lock the Python main process. """ - cmd_ramp = os.path.join(self._python_bin_path, 'ramp-test') - if self.status == 'running': - raise ValueError('Wait that the submission is processed before to ' - 'launch a new one.') - self._log_dir = os.path.join(self.config['logs_dir'], self.submission) + cmd_ramp = os.path.join(self._python_bin_path, "ramp-test") + if self.status == "running": + raise ValueError( + "Wait that the submission is processed before to " "launch a new one." + ) + self._log_dir = os.path.join(self.config["logs_dir"], self.submission) self._proc, self._log_file = _conda_ramp_test_submission( self.config, self.submission, @@ -138,35 +148,36 @@ def collect_results(self): beforehand. """ super().collect_results() - if self.status in ['finished', 'running', 'timeout']: + if self.status in ["finished", "running", "timeout"]: # communicate() will wait for the process to be completed self._proc.communicate() self._log_file.close() - with open(os.path.join(self._log_dir, 'log'), 'rb') as f: + with open(os.path.join(self._log_dir, "log"), "rb") as f: log_output = f.read() - error_msg = _get_traceback(log_output.decode('utf-8')) - if self.status == 'timeout': - error_msg += ('\nWorker killed due to timeout after {}s.' - .format(self.timeout)) - if self.status == 'timeout': + error_msg = _get_traceback(log_output.decode("utf-8")) + if self.status == "timeout": + error_msg += "\nWorker killed due to timeout after {}s.".format( + self.timeout + ) + if self.status == "timeout": returncode = 124 else: returncode = self._proc.returncode - pred_dir = os.path.join( - self.config['predictions_dir'], self.submission - ) + pred_dir = os.path.join(self.config["predictions_dir"], self.submission) output_training_dir = os.path.join( - self.config['submissions_dir'], self.submission, - 'training_output') + self.config["submissions_dir"], + self.submission, + "training_output", + ) if os.path.exists(pred_dir): shutil.rmtree(pred_dir) if returncode: if os.path.exists(output_training_dir): shutil.rmtree(output_training_dir) - self.status = 'collected' + self.status = "collected" return (returncode, error_msg) # copy the predictions into the disk # no need to create the directory, it will be handle by copytree shutil.copytree(output_training_dir, pred_dir) - self.status = 'collected' + self.status = "collected" return (returncode, error_msg) diff --git a/ramp-engine/ramp_engine/remote.py b/ramp-engine/ramp_engine/remote.py index 3d2b04389..6b067b60c 100644 --- a/ramp-engine/ramp_engine/remote.py +++ b/ramp-engine/ramp_engine/remote.py @@ -12,7 +12,7 @@ from .conda import _conda_info_envs, _get_conda_env_path from .conda import _conda_ramp_test_submission -logger = logging.getLogger('RAMP-WORKER') +logger = logging.getLogger("RAMP-WORKER") def _check_dask_workers_single_machine(worker_urls: List[str]) -> bool: @@ -34,21 +34,23 @@ def _check_dask_workers_single_machine(worker_urls: List[str]) -> bool: if len(worker_hosts) == 1 and worker_hosts != {None}: return True else: - raise ValueError(f'All dask workers should be on 1 machine, ' - f'found {len(worker_hosts)}: {worker_hosts}') + raise ValueError( + f"All dask workers should be on 1 machine, " + f"found {len(worker_hosts)}: {worker_hosts}" + ) def _read_file(path: Union[str, Path]) -> bytes: """Open and read a file""" - with open(path, 'rb') as fh: + with open(path, "rb") as fh: return fh.read() def _serialize_folder(path: Union[str, Path]) -> bytes: """Serialize a folder as a bytes object of its .tar.gz""" with BytesIO() as fh: - with tarfile.open(fileobj=fh, mode='w:gz') as fh_tar: - fh_tar.add(path, arcname='.') + with tarfile.open(fileobj=fh, mode="w:gz") as fh_tar: + fh_tar.add(path, arcname=".") fh.seek(0) return fh.read() @@ -61,7 +63,7 @@ def _deserialize_folder(stream: bytes, out_dir: Union[str, Path]): shutil.rmtree(out_dir, ignore_errors=True) with BytesIO(stream) as fh: fh.seek(0) - with tarfile.open(fileobj=fh, mode='r:gz') as fh_tar: + with tarfile.open(fileobj=fh, mode="r:gz") as fh_tar: fh_tar.extractall(out_dir) @@ -118,6 +120,7 @@ class DaskWorker(BaseWorker): * 'finished': the worker finished to train the submission. * 'collected': the results of the training have been collected. """ + def __init__(self, config, submission): super().__init__(config=config, submission=submission) @@ -131,29 +134,32 @@ def setup(self): from dask.distributed import Client # sanity check for the configuration variable - for required_param in ('kit_dir', 'data_dir', 'submissions_dir', - 'logs_dir', 'predictions_dir', - 'dask_scheduler'): + for required_param in ( + "kit_dir", + "data_dir", + "submissions_dir", + "logs_dir", + "predictions_dir", + "dask_scheduler", + ): self._check_config_name(self.config, required_param) # find the path to the conda environment - env_name = self.config.get('conda_env', 'base') - self._client = Client(self.config['dask_scheduler']) + env_name = self.config.get("conda_env", "base") + self._client = Client(self.config["dask_scheduler"]) dask_worker_urls = list(self._client.nthreads()) _check_dask_workers_single_machine(dask_worker_urls) # Check if Dask workers are on the same host as the scheduler self._is_local_cluster = ( - urlparse(self.config['dask_scheduler']).hostname + urlparse(self.config["dask_scheduler"]).hostname == urlparse(dask_worker_urls[0]).hostname ) # Fail early if the Dask worker is not working properly - self._client.submit(lambda: 1+1).result() + self._client.submit(lambda: 1 + 1).result() - conda_info = self._client.submit( - _conda_info_envs, pure=False - ).result() + conda_info = self._client.submit(_conda_info_envs, pure=False).result() self._python_bin_path = _get_conda_env_path(conda_info, env_name, self) @@ -161,11 +167,14 @@ def setup(self): def teardown(self): """Remove the predictions stored within the submission.""" - if self.status != 'collected': + if self.status != "collected": raise ValueError("Collect the results before to kill the worker.") - output_training_dir = os.path.join(self.config['kit_dir'], - 'submissions', self.submission, - 'training_output') + output_training_dir = os.path.join( + self.config["kit_dir"], + "submissions", + self.submission, + "training_output", + ) self._client.submit( shutil.rmtree, output_training_dir, ignore_errors=True, pure=False ).result() @@ -198,7 +207,7 @@ def check_timeout(self): @property def timeout(self): - return self.config.get('timeout', 7200) + return self.config.get("timeout", 7200) def launch_submission(self): """Launch the submission. @@ -207,23 +216,21 @@ def launch_submission(self): environment given in the configuration. The submission is launched in a subprocess to free to not lock the Python main process. """ - cmd_ramp = os.path.join(self._python_bin_path, 'ramp-test') - if self.status == 'running': - raise ValueError('Wait that the submission is processed before to ' - 'launch a new one.') + cmd_ramp = os.path.join(self._python_bin_path, "ramp-test") + if self.status == "running": + raise ValueError( + "Wait that the submission is processed before to " "launch a new one." + ) - self._log_dir = os.path.join(self.config['logs_dir'], self.submission) + self._log_dir = os.path.join(self.config["logs_dir"], self.submission) # TODO: need to copy submission to the remote folder if not self._is_local_cluster: submission_dir = os.path.join( - self.config['submissions_dir'], self.submission + self.config["submissions_dir"], self.submission ) # remove remote submission and local prediction dir if it exists self._client.submit( - shutil.rmtree, - submission_dir, - ignore_errors=True, - pure=False + shutil.rmtree, submission_dir, ignore_errors=True, pure=False ).result() # Upload the submission dir to the remote machine stream = _serialize_folder(submission_dir) @@ -233,8 +240,8 @@ def launch_submission(self): # If there is a training_output in the submission, remove it self._client.submit( _remove_link_or_dir, - os.path.join(submission_dir, 'training_output'), - pure=False + os.path.join(submission_dir, "training_output"), + pure=False, ).result() self._proc = self._client.submit( @@ -261,7 +268,7 @@ def collect_results(self): from distributed.utils import CancelledError super().collect_results() - if self.status in ['finished', 'running', 'timeout']: + if self.status in ["finished", "running", "timeout"]: returncode = 1 try: # Wait for the computation to run. @@ -269,19 +276,20 @@ def collect_results(self): except CancelledError: pass log_output = self._client.submit( - _read_file, os.path.join(self._log_dir, 'log'), pure=False + _read_file, os.path.join(self._log_dir, "log"), pure=False ).result() - error_msg = _get_traceback(log_output.decode('utf-8')) - if self.status == 'timeout': - error_msg += ('\nWorker killed due to timeout after {}s.' - .format(self.timeout)) + error_msg = _get_traceback(log_output.decode("utf-8")) + if self.status == "timeout": + error_msg += "\nWorker killed due to timeout after {}s.".format( + self.timeout + ) returncode = 124 - pred_dir = os.path.join( - self.config['predictions_dir'], self.submission - ) + pred_dir = os.path.join(self.config["predictions_dir"], self.submission) output_training_dir = os.path.join( - self.config['submissions_dir'], self.submission, - 'training_output') + self.config["submissions_dir"], + self.submission, + "training_output", + ) self._client.submit( shutil.rmtree, pred_dir, ignore_errors=True, pure=False ).result() @@ -290,9 +298,9 @@ def collect_results(self): shutil.rmtree, output_training_dir, ignore_errors=True, - pure=False + pure=False, ).result() - self.status = 'collected' + self.status = "collected" return (returncode, error_msg) if self._is_local_cluster: shutil.copytree(output_training_dir, pred_dir) @@ -305,5 +313,5 @@ def collect_results(self): # remove the local predictions dir if it exists shutil.rmtree(pred_dir, ignore_errors=True) _deserialize_folder(stream, pred_dir) - self.status = 'collected' + self.status = "collected" return (returncode, error_msg) diff --git a/ramp-engine/ramp_engine/tests/kits/iris/problem.py b/ramp-engine/ramp_engine/tests/kits/iris/problem.py index 0dadae60c..aff46c180 100644 --- a/ramp-engine/ramp_engine/tests/kits/iris/problem.py +++ b/ramp-engine/ramp_engine/tests/kits/iris/problem.py @@ -3,20 +3,19 @@ import rampwf as rw from sklearn.model_selection import StratifiedShuffleSplit -problem_title = 'Iris classification' -_target_column_name = 'species' -_prediction_label_names = ['setosa', 'versicolor', 'virginica'] +problem_title = "Iris classification" +_target_column_name = "species" +_prediction_label_names = ["setosa", "versicolor", "virginica"] # A type (class) which will be used to create wrapper objects for y_pred -Predictions = rw.prediction_types.make_multiclass( - label_names=_prediction_label_names) +Predictions = rw.prediction_types.make_multiclass(label_names=_prediction_label_names) # An object implementing the workflow workflow = rw.workflows.Estimator() score_types = [ - rw.score_types.Accuracy(name='acc'), - rw.score_types.ClassificationError(name='error'), - rw.score_types.NegativeLogLikelihood(name='nll'), - rw.score_types.F1Above(name='f1_70', threshold=0.7), + rw.score_types.Accuracy(name="acc"), + rw.score_types.ClassificationError(name="error"), + rw.score_types.NegativeLogLikelihood(name="nll"), + rw.score_types.F1Above(name="f1_70", threshold=0.7), ] @@ -26,17 +25,17 @@ def get_cv(X, y): def _read_data(path, f_name): - data = pd.read_csv(os.path.join(path, 'data', f_name)) + data = pd.read_csv(os.path.join(path, "data", f_name)) y_array = data[_target_column_name].values X_array = data.drop([_target_column_name], axis=1) return X_array, y_array -def get_train_data(path='.'): - f_name = 'train.csv' +def get_train_data(path="."): + f_name = "train.csv" return _read_data(path, f_name) -def get_test_data(path='.'): - f_name = 'test.csv' +def get_test_data(path="."): + f_name = "test.csv" return _read_data(path, f_name) diff --git a/ramp-engine/ramp_engine/tests/kits/iris/submissions/random_forest_10_10/estimator.py b/ramp-engine/ramp_engine/tests/kits/iris/submissions/random_forest_10_10/estimator.py index 494d240b8..3860343c3 100755 --- a/ramp-engine/ramp_engine/tests/kits/iris/submissions/random_forest_10_10/estimator.py +++ b/ramp-engine/ramp_engine/tests/kits/iris/submissions/random_forest_10_10/estimator.py @@ -2,6 +2,5 @@ def get_estimator(): - clf = RandomForestClassifier(n_estimators=10, max_leaf_nodes=10, - random_state=61) + clf = RandomForestClassifier(n_estimators=10, max_leaf_nodes=10, random_state=61) return clf diff --git a/ramp-engine/ramp_engine/tests/kits/iris/submissions/starting_kit/estimator.py b/ramp-engine/ramp_engine/tests/kits/iris/submissions/starting_kit/estimator.py index 661d88c2b..64220a376 100755 --- a/ramp-engine/ramp_engine/tests/kits/iris/submissions/starting_kit/estimator.py +++ b/ramp-engine/ramp_engine/tests/kits/iris/submissions/starting_kit/estimator.py @@ -2,6 +2,5 @@ def get_estimator(): - clf = RandomForestClassifier(n_estimators=1, max_leaf_nodes=2, - random_state=61) + clf = RandomForestClassifier(n_estimators=1, max_leaf_nodes=2, random_state=61) return clf diff --git a/ramp-engine/ramp_engine/tests/kits/iris/submissions/starting_kit_local/estimator.py b/ramp-engine/ramp_engine/tests/kits/iris/submissions/starting_kit_local/estimator.py index 661d88c2b..64220a376 100755 --- a/ramp-engine/ramp_engine/tests/kits/iris/submissions/starting_kit_local/estimator.py +++ b/ramp-engine/ramp_engine/tests/kits/iris/submissions/starting_kit_local/estimator.py @@ -2,6 +2,5 @@ def get_estimator(): - clf = RandomForestClassifier(n_estimators=1, max_leaf_nodes=2, - random_state=61) + clf = RandomForestClassifier(n_estimators=1, max_leaf_nodes=2, random_state=61) return clf diff --git a/ramp-engine/ramp_engine/tests/test_aws.py b/ramp-engine/ramp_engine/tests/test_aws.py index f18803bd1..b430361df 100644 --- a/ramp-engine/ramp_engine/tests/test_aws.py +++ b/ramp-engine/ramp_engine/tests/test_aws.py @@ -30,8 +30,9 @@ logging.basicConfig( level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S') + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) def add_empty_dir(dir_name): @@ -39,227 +40,224 @@ def add_empty_dir(dir_name): os.mkdir(dir_name) -@mock.patch('ramp_engine.aws.api.launch_ec2_instances') -def test_launch_ec2_instances_put_back_into_queue(test_launch_ec2_instances, - caplog): - ''' checks if the retry status and the correct log is added if the - api returns None instances and status retry ''' +@mock.patch("ramp_engine.aws.api.launch_ec2_instances") +def test_launch_ec2_instances_put_back_into_queue(test_launch_ec2_instances, caplog): + """checks if the retry status and the correct log is added if the + api returns None instances and status retry""" - test_launch_ec2_instances.return_value = None, 'retry' + test_launch_ec2_instances.return_value = None, "retry" # setup the AWS worker - event_config = read_config(ramp_aws_config_template())['worker'] + event_config = read_config(ramp_aws_config_template())["worker"] - worker = AWSWorker(event_config, submission='starting_kit_local') + worker = AWSWorker(event_config, submission="starting_kit_local") worker.config = event_config # worker should be put back into the queue worker.setup() - assert worker.status == 'retry' - assert 'Adding it back to the queue and will try again' in caplog.text + assert worker.status == "retry" + assert "Adding it back to the queue and will try again" in caplog.text -@mock.patch('ramp_engine.aws.api._rsync') -@mock.patch('ramp_engine.aws.api.launch_ec2_instances') -def test_aws_worker_upload_error(test_launch_ec2_instances, test_rsync, - caplog): +@mock.patch("ramp_engine.aws.api._rsync") +@mock.patch("ramp_engine.aws.api.launch_ec2_instances") +def test_aws_worker_upload_error(test_launch_ec2_instances, test_rsync, caplog): # mock dummy AWS instance class DummyInstance: id = 1 test_launch_ec2_instances.return_value = (DummyInstance(),), 0 # mock the called process error - test_rsync.side_effect = subprocess.CalledProcessError(255, 'test') + test_rsync.side_effect = subprocess.CalledProcessError(255, "test") # setup the AWS worker - event_config = read_config(ramp_aws_config_template())['worker'] + event_config = read_config(ramp_aws_config_template())["worker"] - worker = AWSWorker(event_config, submission='starting_kit_local') + worker = AWSWorker(event_config, submission="starting_kit_local") worker.config = event_config # CalledProcessError is thrown inside worker.setup() - assert worker.status == 'error' - assert 'Unable to connect during log download' in caplog.text + assert worker.status == "error" + assert "Unable to connect during log download" in caplog.text -@mock.patch('ramp_engine.aws.api._rsync') +@mock.patch("ramp_engine.aws.api._rsync") @mock.patch("ramp_engine.base.BaseWorker.collect_results") -def test_aws_worker_download_log_error(superclass, test_rsync, - caplog): +def test_aws_worker_download_log_error(superclass, test_rsync, caplog): # mock dummy AWS instance class DummyInstance: - id = 'test' + id = "test" - test_rsync.side_effect = subprocess.CalledProcessError(255, 'test') + test_rsync.side_effect = subprocess.CalledProcessError(255, "test") # setup the AWS worker superclass.return_value = True - event_config = read_config(ramp_aws_config_template())['worker'] + event_config = read_config(ramp_aws_config_template())["worker"] - worker = AWSWorker(event_config, submission='starting_kit_local') + worker = AWSWorker(event_config, submission="starting_kit_local") worker.config = event_config - worker.status = 'finished' + worker.status = "finished" worker.instance = DummyInstance # worker will now through an CalledProcessError exit_status, error_msg = worker.collect_results() - assert 'Error occurred when downloading the logs' in caplog.text - assert 'Trying to download the log once again' in caplog.text + assert "Error occurred when downloading the logs" in caplog.text + assert "Trying to download the log once again" in caplog.text assert exit_status == 2 - assert 'test' in error_msg - assert worker.status == 'error' + assert "test" in error_msg + assert worker.status == "error" -@mock.patch('ramp_engine.aws.api._rsync') -@mock.patch('ramp_engine.aws.api._training_successful') -@mock.patch('ramp_engine.aws.api.download_log') +@mock.patch("ramp_engine.aws.api._rsync") +@mock.patch("ramp_engine.aws.api._training_successful") +@mock.patch("ramp_engine.aws.api.download_log") @mock.patch("ramp_engine.base.BaseWorker.collect_results") -def test_aws_worker_download_prediction_error(superclass, test_download_log, - test_train, test_rsync, caplog): +def test_aws_worker_download_prediction_error( + superclass, test_download_log, test_train, test_rsync, caplog +): # mock dummy AWS instance class DummyInstance: - id = 'test' + id = "test" - test_rsync.side_effect = subprocess.CalledProcessError(255, 'test') + test_rsync.side_effect = subprocess.CalledProcessError(255, "test") test_download_log.return_value = (0,) # setup the AWS worker superclass.return_value = True test_train.return_value = True - event_config = read_config(ramp_aws_config_template())['worker'] + event_config = read_config(ramp_aws_config_template())["worker"] - worker = AWSWorker(event_config, submission='starting_kit_local') + worker = AWSWorker(event_config, submission="starting_kit_local") worker.config = event_config - worker.status = 'finished' + worker.status = "finished" worker.instance = DummyInstance # worker will now through an CalledProcessError exit_status, error_msg = worker.collect_results() - assert 'Downloading the prediction failed with' in caplog.text - assert 'Trying to download the prediction once again' in caplog.text + assert "Downloading the prediction failed with" in caplog.text + assert "Trying to download the prediction once again" in caplog.text assert exit_status == 1 - assert 'test' in error_msg + assert "test" in error_msg -@mock.patch('ramp_engine.aws.api._rsync') +@mock.patch("ramp_engine.aws.api._rsync") def test_rsync_download_predictions(test_rsync, caplog): - error = subprocess.CalledProcessError(255, 'test') - event_config = read_config(ramp_aws_config_template())['worker'] + error = subprocess.CalledProcessError(255, "test") + event_config = read_config(ramp_aws_config_template())["worker"] instance_id = 0 - submission_name = 'test_submission' + submission_name = "test_submission" # test for 2 errors by rsync followed by a log output - test_rsync.side_effect = [error, error, 'test_log'] - out = download_predictions(event_config, instance_id, - submission_name, folder=None) - assert 'Trying to download the prediction' in caplog.text - assert 'test_submission' in out + test_rsync.side_effect = [error, error, "test_log"] + out = download_predictions(event_config, instance_id, submission_name, folder=None) + assert "Trying to download the prediction" in caplog.text + assert "test_submission" in out # test for 3 errors by rsync followed by a log output test_rsync.side_effect = [error, error, error] with pytest.raises(subprocess.CalledProcessError): - out = download_predictions(event_config, instance_id, submission_name, - folder=None) - assert 'Trying to download the prediction' in caplog.text - assert 'error occured when downloading prediction' in caplog.text + out = download_predictions( + event_config, instance_id, submission_name, folder=None + ) + assert "Trying to download the prediction" in caplog.text + assert "error occured when downloading prediction" in caplog.text -@mock.patch('ramp_engine.aws.api._rsync') +@mock.patch("ramp_engine.aws.api._rsync") def test_rsync_download_log(test_rsync, caplog): - error = subprocess.CalledProcessError(255, 'test') - event_config = read_config(ramp_aws_config_template())['worker'] + error = subprocess.CalledProcessError(255, "test") + event_config = read_config(ramp_aws_config_template())["worker"] instance_id = 0 - submission_name = 'test_submission' + submission_name = "test_submission" # test for 2 errors by rsync followed by a log output - test_rsync.side_effect = [error, error, 'test_log'] + test_rsync.side_effect = [error, error, "test_log"] out = download_log(event_config, instance_id, submission_name) - assert 'Trying to download the log' in caplog.text - assert out == 'test_log' + assert "Trying to download the log" in caplog.text + assert out == "test_log" # test for 3 errors by rsync followed by a log output test_rsync.side_effect = [error, error, error] with pytest.raises(subprocess.CalledProcessError): out = download_log(event_config, instance_id, submission_name) - assert 'Trying to download the log' in caplog.text + assert "Trying to download the log" in caplog.text -@mock.patch('ramp_engine.aws.api._rsync') +@mock.patch("ramp_engine.aws.api._rsync") def test_rsync_upload_fails(test_rsync): - test_rsync.side_effect = subprocess.CalledProcessError(255, 'test') - event_config = read_config(ramp_aws_config_template())['worker'] + test_rsync.side_effect = subprocess.CalledProcessError(255, "test") + event_config = read_config(ramp_aws_config_template())["worker"] instance_id = 0 - submission_name = 'test_submission' - submissions_dir = 'temp' - out = upload_submission(event_config, instance_id, submission_name, - submissions_dir) + submission_name = "test_submission" + submissions_dir = "temp" + out = upload_submission(event_config, instance_id, submission_name, submissions_dir) assert out == 1 # error ocurred and it was caught -@mock.patch('ramp_engine.aws.api._run') +@mock.patch("ramp_engine.aws.api._run") def test_is_spot_terminated_with_CalledProcessError(test_run, caplog): - test_run.side_effect = subprocess.CalledProcessError(28, 'test') - event_config = read_config(ramp_aws_config_template())['worker'] + test_run.side_effect = subprocess.CalledProcessError(28, "test") + event_config = read_config(ramp_aws_config_template())["worker"] instance_id = 0 is_spot_terminated(event_config, instance_id) - assert 'Unable to run curl' in caplog.text + assert "Unable to run curl" in caplog.text -@pytest.mark.parametrize( - "use_spot_instance", - [None, True, False] - ) +@pytest.mark.parametrize("use_spot_instance", [None, True, False]) @mock.patch("boto3.session.Session") def test_launch_ec2_instances(boto_session_cls, use_spot_instance): - ''' Check 'use_spot_instance' config with None, True and False''' + """Check 'use_spot_instance' config with None, True and False""" # dummy mock session session = boto_session_cls.return_value client = session.client.return_value describe_images = client.describe_images images = {"Images": [{"ImageId": 1, "CreationDate": 123}]} describe_images.return_value = images - event_config = read_config(ramp_aws_config_template())['worker'] + event_config = read_config(ramp_aws_config_template())["worker"] - event_config['use_spot_instance'] = use_spot_instance + event_config["use_spot_instance"] = use_spot_instance launch_ec2_instances(event_config) -@mock.patch('ramp_engine.aws.api.launch_train') +@mock.patch("ramp_engine.aws.api.launch_train") def test_aws_worker_launch_train_error(launch_train, caplog): # mock dummy AWS instance class DummyInstance: id = 1 - launch_train.side_effect = subprocess.CalledProcessError(255, 'test') + + launch_train.side_effect = subprocess.CalledProcessError(255, "test") # setup the AWS worker - event_config = read_config(ramp_aws_config_template())['worker'] + event_config = read_config(ramp_aws_config_template())["worker"] - worker = AWSWorker(event_config, submission='starting_kit_local') + worker = AWSWorker(event_config, submission="starting_kit_local") worker.config = event_config - worker.submission = 'dummy submissions' + worker.submission = "dummy submissions" worker.instance = DummyInstance # CalledProcessError is thrown inside status = worker.launch_submission() - assert 'test' in caplog.text - assert 'Cannot start training of submission' in caplog.text - assert worker.status == 'error' + assert "test" in caplog.text + assert "Cannot start training of submission" in caplog.text + assert worker.status == "error" assert status == 1 @pytest.mark.parametrize( - 'aws_msg_type, result_none, log_msg', - [('max_spot', True, 'MaxSpotInstanceCountExceeded'), - ('unhandled', True, 'this is temporary message'), - ('correct', False, 'Spot instance request fulfilled') - ] + "aws_msg_type, result_none, log_msg", + [ + ("max_spot", True, "MaxSpotInstanceCountExceeded"), + ("unhandled", True, "this is temporary message"), + ("correct", False, "Spot instance request fulfilled"), + ], ) # set shorter waiting time than in the actual settings @mock.patch("ramp_engine.aws.api.WAIT_MINUTES", 0.03) @mock.patch("ramp_engine.aws.api.MAX_TRIES_TO_CONNECT", 4) @mock.patch("boto3.session.Session") -def test_creating_instances(boto_session_cls, caplog, - aws_msg_type, result_none, log_msg): - ''' test launching more instances than limit on AWS enabled''' +def test_creating_instances( + boto_session_cls, caplog, aws_msg_type, result_none, log_msg +): + """test launching more instances than limit on AWS enabled""" # info: caplog is a pytest fixture to collect logging info # dummy mock session of AWS session = boto_session_cls.return_value @@ -268,29 +266,29 @@ def test_creating_instances(boto_session_cls, caplog, images = {"Images": [{"ImageId": 1, "CreationDate": 123}]} describe_images.return_value = images - error = { - "ClientError": { - "Code": "Max spot instance count exceeded" - } - } - event_config = read_config(ramp_aws_config_template())['worker'] - event_config['use_spot_instance'] = True + error = {"ClientError": {"Code": "Max spot instance count exceeded"}} + event_config = read_config(ramp_aws_config_template())["worker"] + event_config["use_spot_instance"] = True request_spot_instances = client.request_spot_instances error_max_instances = botocore.exceptions.ClientError( - error, "MaxSpotInstanceCountExceeded") + error, "MaxSpotInstanceCountExceeded" + ) error_unhandled = botocore.exceptions.ParamValidationError( - report='this is temporary message') - correct_response = {'SpotInstanceRequests': - [{'SpotInstanceRequestId': ['temp']}] - } - - if aws_msg_type == 'max_spot': - aws_response = [error_max_instances, error_max_instances, - error_max_instances, error_max_instances] - elif aws_msg_type == 'unhandled': + report="this is temporary message" + ) + correct_response = {"SpotInstanceRequests": [{"SpotInstanceRequestId": ["temp"]}]} + + if aws_msg_type == "max_spot": + aws_response = [ + error_max_instances, + error_max_instances, + error_max_instances, + error_max_instances, + ] + elif aws_msg_type == "unhandled": aws_response = [error_unhandled, error_unhandled] - elif aws_msg_type == 'correct': + elif aws_msg_type == "correct": aws_response = [error_max_instances, correct_response] request_spot_instances.side_effect = aws_response @@ -299,22 +297,23 @@ def test_creating_instances(boto_session_cls, caplog, assert log_msg in caplog.text -@mock.patch('ramp_engine.aws.api.is_spot_terminated') -@mock.patch('ramp_engine.aws.api.launch_train') -@mock.patch('ramp_engine.aws.api._training_finished') -def test_restart_on_sudden_instance_termination(training_finished, - launch_train, spot_terminated, - caplog): +@mock.patch("ramp_engine.aws.api.is_spot_terminated") +@mock.patch("ramp_engine.aws.api.launch_train") +@mock.patch("ramp_engine.aws.api._training_finished") +def test_restart_on_sudden_instance_termination( + training_finished, launch_train, spot_terminated, caplog +): class DummyInstance: id = 1 + launch_train.return_value = 0 # setup the AWS worker - event_config = read_config(ramp_aws_config_template())['worker'] + event_config = read_config(ramp_aws_config_template())["worker"] - worker = AWSWorker(event_config, submission='starting_kit_local') + worker = AWSWorker(event_config, submission="starting_kit_local") worker.config = event_config - worker.submission = 'dummy submissions' + worker.submission = "dummy submissions" worker.instance = DummyInstance # set the submission did not yet finish training @@ -322,55 +321,57 @@ class DummyInstance: spot_terminated.return_value = False worker.launch_submission() - assert worker.status == 'running' - assert caplog.text == '' + assert worker.status == "running" + assert caplog.text == "" # call CalledProcessError on checking if submission was finished - training_finished.side_effect = subprocess.CalledProcessError(255, 'test') + training_finished.side_effect = subprocess.CalledProcessError(255, "test") # make sure that the worker status is set to 'retry' - assert worker.status == 'retry' - assert 'Unable to connect to the instance' in caplog.text - assert 'Adding the submission back to the queue' in caplog.text + assert worker.status == "retry" + assert "Unable to connect to the instance" in caplog.text + assert "Adding the submission back to the queue" in caplog.text def test_aws_worker(): - if not os.path.isfile(os.path.join(HERE, 'config.yml')): + if not os.path.isfile(os.path.join(HERE, "config.yml")): pytest.skip("Only for local tests for now") - ramp_kit_dir = os.path.join(HERE, 'kits', 'iris') + ramp_kit_dir = os.path.join(HERE, "kits", "iris") # make sure prediction and log dirs exist, if not, add them - add_empty_dir(os.path.join(ramp_kit_dir, 'predictions')) - add_empty_dir(os.path.join(ramp_kit_dir, 'logs')) + add_empty_dir(os.path.join(ramp_kit_dir, "predictions")) + add_empty_dir(os.path.join(ramp_kit_dir, "logs")) # if the prediction / log files are still there, remove them - for subdir in os.listdir(os.path.join(ramp_kit_dir, 'predictions')): + for subdir in os.listdir(os.path.join(ramp_kit_dir, "predictions")): if os.path.isdir(subdir): shutil.rmtree(subdir) - for subdir in os.listdir(os.path.join(ramp_kit_dir, 'logs')): + for subdir in os.listdir(os.path.join(ramp_kit_dir, "logs")): if os.path.isdir(subdir): shutil.rmtree(subdir) - worker_config = read_config(os.path.join(HERE, 'config.yml'))['worker'] - worker = AWSWorker(worker_config, submission='starting_kit_local') + worker_config = read_config(os.path.join(HERE, "config.yml"))["worker"] + worker = AWSWorker(worker_config, submission="starting_kit_local") worker.setup() - assert worker.status == 'setup' + assert worker.status == "setup" worker.launch_submission() - assert worker.status in ('running', 'finished') + assert worker.status in ("running", "finished") worker.collect_results() - assert worker.status == 'collected' - assert os.path.isdir(os.path.join( - ramp_kit_dir, 'predictions', 'starting_kit_local', 'fold_0')) - assert os.path.isfile(os.path.join( - ramp_kit_dir, 'logs', 'starting_kit_local', 'log')) + assert worker.status == "collected" + assert os.path.isdir( + os.path.join(ramp_kit_dir, "predictions", "starting_kit_local", "fold_0") + ) + assert os.path.isfile( + os.path.join(ramp_kit_dir, "logs", "starting_kit_local", "log") + ) worker.teardown() - assert worker.status == 'killed' + assert worker.status == "killed" def test_aws_dispatcher(session_toy): # noqa # copy of test_integration_dispatcher but with AWS - if not os.path.isfile(os.path.join(HERE, 'config.yml')): + if not os.path.isfile(os.path.join(HERE, "config.yml")): pytest.skip("Only for local tests for now") config = read_config(database_config_template()) @@ -378,17 +379,20 @@ def test_aws_dispatcher(session_toy): # noqa event_config = read_config(event_config) # patch the event_config to match local config.yml for AWS - aws_event_config = read_config(os.path.join(HERE, 'config.yml')) - event_config['worker'] = aws_event_config['worker'] + aws_event_config = read_config(os.path.join(HERE, "config.yml")) + event_config["worker"] = aws_event_config["worker"] dispatcher = Dispatcher( - config=config, event_config=event_config, worker=AWSWorker, - n_workers=-1, hunger_policy='exit' + config=config, + event_config=event_config, + worker=AWSWorker, + n_workers=-1, + hunger_policy="exit", ) dispatcher.launch() # the iris kit contain a submission which should fail for each user submission = get_submissions( - session_toy, event_config['ramp']['event_name'], 'training_error' + session_toy, event_config["ramp"]["event_name"], "training_error" ) assert len(submission) == 2 diff --git a/ramp-engine/ramp_engine/tests/test_cli.py b/ramp-engine/ramp_engine/tests/test_cli.py index dfe7dd3f9..58d9e1ceb 100644 --- a/ramp-engine/ramp_engine/tests/test_cli.py +++ b/ramp-engine/ramp_engine/tests/test_cli.py @@ -24,32 +24,36 @@ def make_toy_db(database_connection): yield finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) -@pytest.mark.parametrize( - "verbose_params", [None, "--verbose", "-vv"] -) +@pytest.mark.parametrize("verbose_params", [None, "--verbose", "-vv"]) def test_dispatcher(verbose_params, make_toy_db): runner = CliRunner() - cmd = ["dispatcher", - "--config", database_config_template(), - "--event-config", ramp_config_template()] + cmd = [ + "dispatcher", + "--config", + database_config_template(), + "--event-config", + ramp_config_template(), + ] if verbose_params is not None: cmd += [verbose_params] result = runner.invoke(main, cmd) assert result.exit_code == 0, result.output -@pytest.mark.parametrize( - "verbose_params", [None, "--verbose", "-vv"] -) +@pytest.mark.parametrize("verbose_params", [None, "--verbose", "-vv"]) def test_worker(verbose_params, make_toy_db): runner = CliRunner() - cmd = ["worker", - "--event-config", ramp_config_template(), - "--submission", "starting_kit"] + cmd = [ + "worker", + "--event-config", + ramp_config_template(), + "--submission", + "starting_kit", + ] if verbose_params is not None: cmd += [verbose_params] result = runner.invoke(main, cmd) diff --git a/ramp-engine/ramp_engine/tests/test_conda_worker.py b/ramp-engine/ramp_engine/tests/test_conda_worker.py index d50977670..6d55787aa 100644 --- a/ramp-engine/ramp_engine/tests/test_conda_worker.py +++ b/ramp-engine/ramp_engine/tests/test_conda_worker.py @@ -17,8 +17,8 @@ def _is_conda_env_installed(): # is available. Otherwise skip all tests. try: conda_info = _conda_info_envs() - envs_path = conda_info['envs'][1:] - if not envs_path or not any(['ramp-iris' in env for env in envs_path]): + envs_path = conda_info["envs"][1:] + if not envs_path or not any(["ramp-iris" in env for env in envs_path]): return True return False except: # noqa @@ -28,56 +28,66 @@ def _is_conda_env_installed(): pytestmark = pytest.mark.skipif( _is_conda_env_installed(), - reason=('CondaEnvWorker required conda and an environment named ' - '"ramp-iris". No such environment was found. Check the ' - '"ci_tools/environment_iris_kit.yml" file to create such ' - 'environment.') + reason=( + "CondaEnvWorker required conda and an environment named " + '"ramp-iris". No such environment was found. Check the ' + '"ci_tools/environment_iris_kit.yml" file to create such ' + "environment." + ), ) @contextmanager -def get_conda_worker(submission_name, Worker=CondaEnvWorker, - conda_env='ramp-iris', dask_scheduler=None): +def get_conda_worker( + submission_name, + Worker=CondaEnvWorker, + conda_env="ramp-iris", + dask_scheduler=None, +): module_path = os.path.dirname(__file__) - config = {'kit_dir': os.path.join(module_path, 'kits', 'iris'), - 'data_dir': os.path.join(module_path, 'kits', 'iris'), - 'submissions_dir': os.path.join(module_path, 'kits', - 'iris', 'submissions'), - 'logs_dir': os.path.join(module_path, 'kits', 'iris', 'log'), - 'predictions_dir': os.path.join( - module_path, 'kits', 'iris', 'predictions'), - 'conda_env': conda_env} + config = { + "kit_dir": os.path.join(module_path, "kits", "iris"), + "data_dir": os.path.join(module_path, "kits", "iris"), + "submissions_dir": os.path.join(module_path, "kits", "iris", "submissions"), + "logs_dir": os.path.join(module_path, "kits", "iris", "log"), + "predictions_dir": os.path.join(module_path, "kits", "iris", "predictions"), + "conda_env": conda_env, + } if issubclass(Worker, DaskWorker): - pytest.importorskip('dask') - pytest.importorskip('dask.distributed') - config['dask_scheduler'] = dask_scheduler + pytest.importorskip("dask") + pytest.importorskip("dask.distributed") + config["dask_scheduler"] = dask_scheduler - worker = Worker(config=config, submission='starting_kit') + worker = Worker(config=config, submission="starting_kit") yield worker # remove all directories that we potentially created _remove_directory(worker) - if issubclass(Worker, DaskWorker) and hasattr(worker, '_client'): + if issubclass(Worker, DaskWorker) and hasattr(worker, "_client"): worker._client.close() def _remove_directory(worker): - if 'kit_dir' not in worker.config: + if "kit_dir" not in worker.config: return output_training_dir = os.path.join( - worker.config['kit_dir'], 'submissions', worker.submission, - 'training_output' + worker.config["kit_dir"], + "submissions", + worker.submission, + "training_output", ) - for directory in (output_training_dir, - worker.config['logs_dir'], - worker.config['predictions_dir']): + for directory in ( + output_training_dir, + worker.config["logs_dir"], + worker.config["predictions_dir"], + ): if os.path.exists(directory): shutil.rmtree(directory) -@pytest.mark.parametrize("submission", ('starting_kit', 'random_forest_10_10')) +@pytest.mark.parametrize("submission", ("starting_kit", "random_forest_10_10")) @pytest.mark.parametrize("Worker", ALL_WORKERS) def test_conda_worker_launch(submission, Worker, dask_scheduler): with get_conda_worker( @@ -85,60 +95,66 @@ def test_conda_worker_launch(submission, Worker, dask_scheduler): ) as worker: worker.launch() # check that teardown removed the predictions - output_training_dir = os.path.join(worker.config['kit_dir'], - 'submissions', - worker.submission, - 'training_output') - assert not os.path.exists(output_training_dir), \ - "teardown() failed to remove the predictions" - - -@pytest.mark.parametrize("submission", ('starting_kit', 'random_forest_10_10')) + output_training_dir = os.path.join( + worker.config["kit_dir"], + "submissions", + worker.submission, + "training_output", + ) + assert not os.path.exists( + output_training_dir + ), "teardown() failed to remove the predictions" + + +@pytest.mark.parametrize("submission", ("starting_kit", "random_forest_10_10")) @pytest.mark.parametrize("Worker", ALL_WORKERS) def test_conda_worker(submission, Worker, dask_scheduler): with get_conda_worker( submission, Worker=Worker, dask_scheduler=dask_scheduler ) as worker: - assert worker.status == 'initialized' + assert worker.status == "initialized" worker.setup() - assert worker.status == 'setup' + assert worker.status == "setup" worker.launch_submission() - assert worker.status == 'running' + assert worker.status == "running" exit_status, _ = worker.collect_results() assert exit_status == 0 - assert worker.status == 'collected' + assert worker.status == "collected" worker.teardown() # check that teardown removed the predictions - output_training_dir = os.path.join(worker.config['kit_dir'], - 'submissions', - worker.submission, - 'training_output') - assert not os.path.exists(output_training_dir), \ - "teardown() failed to remove the predictions" + output_training_dir = os.path.join( + worker.config["kit_dir"], + "submissions", + worker.submission, + "training_output", + ) + assert not os.path.exists( + output_training_dir + ), "teardown() failed to remove the predictions" @pytest.mark.parametrize("Worker", ALL_WORKERS) def test_conda_worker_without_conda_env_specified(Worker, dask_scheduler): with get_conda_worker( - 'starting_kit', Worker=Worker, dask_scheduler=dask_scheduler + "starting_kit", Worker=Worker, dask_scheduler=dask_scheduler ) as worker: # remove the conda_env parameter from the configuration - del worker.config['conda_env'] + del worker.config["conda_env"] # if the conda environment is not given in the configuration, we should # fall back on the base environment of conda # the conda environment is set during setup; thus no need to launch # submission worker.setup() - assert 'envs' not in worker._python_bin_path + assert "envs" not in worker._python_bin_path @pytest.mark.parametrize("Worker", ALL_WORKERS) def test_conda_worker_error_missing_config_param(Worker, dask_scheduler): with get_conda_worker( - 'starting_kit', Worker=Worker, dask_scheduler=dask_scheduler + "starting_kit", Worker=Worker, dask_scheduler=dask_scheduler ) as worker: # we remove one of the required parameter - del worker.config['kit_dir'] + del worker.config["kit_dir"] err_msg = "The worker required the parameter 'kit_dir'" with pytest.raises(ValueError, match=err_msg): @@ -147,20 +163,22 @@ def test_conda_worker_error_missing_config_param(Worker, dask_scheduler): @pytest.mark.parametrize("Worker", ALL_WORKERS) def test_conda_worker_error_unknown_env(Worker, dask_scheduler): - conda_env = 'xxx' + conda_env = "xxx" with get_conda_worker( - 'starting_kit', conda_env=conda_env, Worker=Worker, - dask_scheduler=dask_scheduler + "starting_kit", + conda_env=conda_env, + Worker=Worker, + dask_scheduler=dask_scheduler, ) as worker: msg_err = f"specified conda environment {conda_env} does not exist." with pytest.raises(ValueError, match=msg_err): worker.setup() - assert worker.status == 'error' + assert worker.status == "error" @pytest.mark.parametrize("Worker", ALL_WORKERS) def test_conda_worker_error_multiple_launching(Worker, dask_scheduler): - submission = 'starting_kit' + submission = "starting_kit" with get_conda_worker( submission, Worker=Worker, dask_scheduler=dask_scheduler ) as worker: @@ -177,10 +195,10 @@ def test_conda_worker_error_multiple_launching(Worker, dask_scheduler): @pytest.mark.parametrize("Worker", ALL_WORKERS) def test_conda_worker_error_soon_teardown(Worker, dask_scheduler): with get_conda_worker( - 'starting_kit', Worker=Worker, dask_scheduler=dask_scheduler + "starting_kit", Worker=Worker, dask_scheduler=dask_scheduler ) as worker: worker.setup() - err_msg = 'Collect the results before to kill the worker.' + err_msg = "Collect the results before to kill the worker." with pytest.raises(ValueError, match=err_msg): worker.teardown() @@ -188,7 +206,7 @@ def test_conda_worker_error_soon_teardown(Worker, dask_scheduler): @pytest.mark.parametrize("Worker", ALL_WORKERS) def test_conda_worker_error_soon_collection(Worker, dask_scheduler): with get_conda_worker( - 'starting_kit', Worker=Worker, dask_scheduler=dask_scheduler + "starting_kit", Worker=Worker, dask_scheduler=dask_scheduler ) as worker: err_msg = r"Call the method setup\(\) and launch_submission\(\) before" with pytest.raises(ValueError, match=err_msg): @@ -202,27 +220,30 @@ def test_conda_worker_error_soon_collection(Worker, dask_scheduler): @pytest.mark.parametrize("Worker", ALL_WORKERS) def test_conda_worker_timeout(Worker, dask_scheduler): with get_conda_worker( - 'random_forest_10_10', Worker=Worker, dask_scheduler=dask_scheduler + "random_forest_10_10", Worker=Worker, dask_scheduler=dask_scheduler ) as worker: - worker.config['timeout'] = 1 + worker.config["timeout"] = 1 - assert worker.status == 'initialized' + assert worker.status == "initialized" worker.setup() - assert worker.status == 'setup' + assert worker.status == "setup" worker.launch_submission() assert not worker.check_timeout() - assert worker.status == 'running' + assert worker.status == "running" sleep(2) assert worker.check_timeout() is True - assert worker.status == 'timeout' + assert worker.status == "timeout" exit_status, _ = worker.collect_results() assert exit_status > 0 - assert worker.status == 'collected' + assert worker.status == "collected" worker.teardown() # check that teardown removed the predictions - output_training_dir = os.path.join(worker.config['kit_dir'], - 'submissions', - worker.submission, - 'training_output') - assert not os.path.exists(output_training_dir), \ - "teardown() failed to remove the predictions" + output_training_dir = os.path.join( + worker.config["kit_dir"], + "submissions", + worker.submission, + "training_output", + ) + assert not os.path.exists( + output_training_dir + ), "teardown() failed to remove the predictions" diff --git a/ramp-engine/ramp_engine/tests/test_daemon.py b/ramp-engine/ramp_engine/tests/test_daemon.py index 9cabba096..6968d4c45 100644 --- a/ramp-engine/ramp_engine/tests/test_daemon.py +++ b/ramp-engine/ramp_engine/tests/test_daemon.py @@ -23,17 +23,17 @@ def session_toy(database_connection): ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_daemon_error_init(): with pytest.raises(ValueError, match="The path xxx is not existing"): - Daemon(config=database_config_template(), events_dir='xxx') + Daemon(config=database_config_template(), events_dir="xxx") def test_daemon(session_toy): @@ -44,7 +44,7 @@ def test_daemon(session_toy): event.closing_timestamp = datetime.datetime.utcnow() session_toy.commit() - events_dir = os.path.join(os.path.dirname(__file__), 'events') + events_dir = os.path.join(os.path.dirname(__file__), "events") daemon = Daemon(config=database_config_template(), events_dir=events_dir) try: diff --git a/ramp-engine/ramp_engine/tests/test_dispatcher.py b/ramp-engine/ramp_engine/tests/test_dispatcher.py index aadff2fbf..69e5e07b0 100644 --- a/ramp-engine/ramp_engine/tests/test_dispatcher.py +++ b/ramp-engine/ramp_engine/tests/test_dispatcher.py @@ -33,11 +33,11 @@ def session_toy(database_connection): ramp_config = ramp_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) @@ -47,10 +47,10 @@ def session_toy_aws(database_connection): ramp_config_aws = ramp_aws_config_template() try: deployment_dir = create_toy_db(database_config, ramp_config_aws) - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: yield session finally: - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) shutil.rmtree(deployment_dir, ignore_errors=True) @@ -58,12 +58,12 @@ def session_toy_aws(database_connection): def test_error_handling_worker_setup_error(session_toy, caplog): # make sure the error on the worker.setup is dealt with correctly # set mock worker - class Worker_mock(): + class Worker_mock: def __init__(self, *args, **kwargs): self.state = None def setup(self): - raise Exception('Test error') + raise Exception("Test error") def teardown(self): pass @@ -73,58 +73,67 @@ def teardown(self): worker = Worker_mock() dispatcher = Dispatcher( - config=config, event_config=event_config, worker=Worker_mock, - n_workers=-1, hunger_policy='exit' + config=config, + event_config=event_config, + worker=Worker_mock, + n_workers=-1, + hunger_policy="exit", ) dispatcher.launch() submissions = get_submissions( - session_toy, event_config['ramp']['event_name'], 'checking_error' + session_toy, event_config["ramp"]["event_name"], "checking_error" ) assert len(submissions) == 6 - worker.status = 'error' - assert 'Test error' in caplog.text + worker.status = "error" + assert "Test error" in caplog.text def _update_worker_config(event_config, Worker, dask_scheduler): if issubclass(Worker, DaskWorker): - pytest.importorskip('dask') - pytest.importorskip('dask.distributed') - event_config['worker']['dask_scheduler'] = dask_scheduler - event_config['worker']['worker_type'] = 'dask' + pytest.importorskip("dask") + pytest.importorskip("dask.distributed") + event_config["worker"]["dask_scheduler"] = dask_scheduler + event_config["worker"]["worker_type"] = "dask" -@pytest.mark.parametrize('Worker', ALL_WORKERS) +@pytest.mark.parametrize("Worker", ALL_WORKERS) def test_integration_dispatcher(session_toy, Worker, dask_scheduler): config = read_config(database_config_template()) event_config = read_config(ramp_config_template()) _update_worker_config(event_config, Worker, dask_scheduler) dispatcher = Dispatcher( - config=config, event_config=event_config, worker=Worker, - n_workers=-1, hunger_policy='exit' + config=config, + event_config=event_config, + worker=Worker, + n_workers=-1, + hunger_policy="exit", ) dispatcher.launch() # the iris kit contain a submission which should fail for each user submissions = get_submissions( - session_toy, event_config['ramp']['event_name'], 'training_error' + session_toy, event_config["ramp"]["event_name"], "training_error" ) assert len(submissions) == 2 submission = get_submission_by_id(session_toy, submissions[0][0]) - assert 'ValueError' in submission.error_msg + assert "ValueError" in submission.error_msg -@pytest.mark.parametrize('Worker', ALL_WORKERS) +@pytest.mark.parametrize("Worker", ALL_WORKERS) def test_unit_test_dispatcher(session_toy, Worker, dask_scheduler): # make sure that the size of the list is bigger than the number of # submissions config = read_config(database_config_template()) event_config = read_config(ramp_config_template()) _update_worker_config(event_config, Worker, dask_scheduler) - dispatcher = Dispatcher(config=config, - event_config=event_config, - worker=Worker, n_workers=100, - hunger_policy='exit') + dispatcher = Dispatcher( + config=config, + event_config=event_config, + worker=Worker, + n_workers=100, + hunger_policy="exit", + ) # check that all the queue are empty assert dispatcher._awaiting_worker_queue.empty() @@ -132,12 +141,12 @@ def test_unit_test_dispatcher(session_toy, Worker, dask_scheduler): assert dispatcher._processed_submission_queue.empty() # check that all submissions are queued - submissions = get_submissions(session_toy, 'iris_test', 'new') + submissions = get_submissions(session_toy, "iris_test", "new") dispatcher.fetch_from_db(session_toy) # we should remove the starting kit from the length of the submissions for # each user assert dispatcher._awaiting_worker_queue.qsize() == len(submissions) - 2 - submissions = get_submissions(session_toy, 'iris_test', 'sent_to_training') + submissions = get_submissions(session_toy, "iris_test", "sent_to_training") assert len(submissions) == 6 # start the training @@ -146,14 +155,13 @@ def test_unit_test_dispatcher(session_toy, Worker, dask_scheduler): while not dispatcher._processing_worker_queue.empty(): dispatcher.collect_result(session_toy) - assert len(get_submissions(session_toy, 'iris_test', 'new')) == 2 - assert (len(get_submissions(session_toy, 'iris_test', 'training_error')) == - 2) - assert len(get_submissions(session_toy, 'iris_test', 'tested')) == 4 + assert len(get_submissions(session_toy, "iris_test", "new")) == 2 + assert len(get_submissions(session_toy, "iris_test", "training_error")) == 2 + assert len(get_submissions(session_toy, "iris_test", "tested")) == 4 dispatcher.update_database_results(session_toy) assert dispatcher._processed_submission_queue.empty() - event = get_event(session_toy, 'iris_test') + event = get_event(session_toy, "iris_test") assert event.private_leaderboard_html assert event.public_leaderboard_html_with_links assert event.public_leaderboard_html_no_links @@ -163,22 +171,23 @@ def test_unit_test_dispatcher(session_toy, Worker, dask_scheduler): assert event.private_competition_leaderboard_html -@pytest.mark.parametrize( - "n_threads", [None, 4] -) -@pytest.mark.parametrize('Worker', ALL_WORKERS) +@pytest.mark.parametrize("n_threads", [None, 4]) +@pytest.mark.parametrize("Worker", ALL_WORKERS) def test_dispatcher_num_threads(n_threads, Worker, dask_scheduler): - libraries = ('OMP', 'MKL', 'OPENBLAS') + libraries = ("OMP", "MKL", "OPENBLAS") config = read_config(database_config_template()) event_config = read_config(ramp_config_template()) _update_worker_config(event_config, Worker, dask_scheduler) # check that by default we don't set the environment by default - dispatcher = Dispatcher(config=config, - event_config=event_config, - worker=Worker, n_workers=100, - n_threads=n_threads, - hunger_policy='exit') + dispatcher = Dispatcher( + config=config, + event_config=event_config, + worker=Worker, + n_workers=100, + n_threads=n_threads, + hunger_policy="exit", + ) if n_threads is None: assert dispatcher.n_threads is n_threads for lib in libraries: @@ -196,21 +205,27 @@ def test_dispatcher_error(): # check that passing a not a number will raise a TypeError err_msg = "The parameter 'n_threads' should be a positive integer" with pytest.raises(TypeError, match=err_msg): - Dispatcher(config=config, - event_config=event_config, - worker=CondaEnvWorker, n_workers=100, - n_threads='whatever', - hunger_policy='exit') + Dispatcher( + config=config, + event_config=event_config, + worker=CondaEnvWorker, + n_workers=100, + n_threads="whatever", + hunger_policy="exit", + ) -@pytest.mark.parametrize('Worker', ALL_WORKERS) +@pytest.mark.parametrize("Worker", ALL_WORKERS) def test_dispatcher_timeout(session_toy, Worker, dask_scheduler): config = read_config(database_config_template()) event_config = read_config(ramp_config_template()) _update_worker_config(event_config, Worker, dask_scheduler) dispatcher = Dispatcher( - config=config, event_config=event_config, worker=Worker, - n_workers=-1, hunger_policy='exit' + config=config, + event_config=event_config, + worker=Worker, + n_workers=-1, + hunger_policy="exit", ) # override the timeout of the worker dispatcher._worker_config["timeout"] = 1 @@ -219,7 +234,7 @@ def test_dispatcher_timeout(session_toy, Worker, dask_scheduler): # we should have at least 3 submissions which will fail: # 2 for errors and 1 for timeout submissions = get_submissions( - session_toy, event_config['ramp']['event_name'], 'training_error' + session_toy, event_config["ramp"]["event_name"], "training_error" ) assert len(submissions) >= 2 @@ -227,19 +242,24 @@ def test_dispatcher_timeout(session_toy, Worker, dask_scheduler): def test_dispatcher_worker_retry(session_toy): config = read_config(database_config_template()) event_config = read_config(ramp_config_template()) - dispatcher = Dispatcher(config=config, - event_config=event_config, - worker=CondaEnvWorker, n_workers=10, - hunger_policy='exit') + dispatcher = Dispatcher( + config=config, + event_config=event_config, + worker=CondaEnvWorker, + n_workers=10, + hunger_policy="exit", + ) dispatcher.fetch_from_db(session_toy) dispatcher.launch_workers(session_toy) # Get one worker and set status to 'retry' - worker, (submission_id, submission_name) = \ - dispatcher._processing_worker_queue.get() - setattr(worker, 'status', 'retry') - assert worker.status == 'retry' + worker, ( + submission_id, + submission_name, + ) = dispatcher._processing_worker_queue.get() + setattr(worker, "status", "retry") + assert worker.status == "retry" # Add back to queue dispatcher._processing_worker_queue.put_nowait( (worker, (submission_id, submission_name)) @@ -247,7 +267,7 @@ def test_dispatcher_worker_retry(session_toy): while not dispatcher._processing_worker_queue.empty(): dispatcher.collect_result(session_toy) - submissions = get_submissions(session_toy, 'iris_test', 'new') + submissions = get_submissions(session_toy, "iris_test", "new") assert submission_name in [sub[1] for sub in submissions] @@ -258,41 +278,49 @@ def test_dispatcher_aws_not_launching(session_toy_aws, caplog): config = read_config(database_config_template()) event_config = read_config(ramp_aws_config_template()) - dispatcher = Dispatcher(config=config, - event_config=event_config, - worker=AWSWorker, n_workers=10, - hunger_policy='exit') + dispatcher = Dispatcher( + config=config, + event_config=event_config, + worker=AWSWorker, + n_workers=10, + hunger_policy="exit", + ) dispatcher.fetch_from_db(session_toy_aws) - submissions = get_submissions(session_toy_aws, 'iris_aws_test', 'new') + submissions = get_submissions(session_toy_aws, "iris_aws_test", "new") dispatcher.launch_workers(session_toy_aws) - assert 'AuthFailure' in caplog.text + assert "AuthFailure" in caplog.text # training should not have started - assert 'training' not in caplog.text + assert "training" not in caplog.text num_running_workers = dispatcher._processing_worker_queue.qsize() assert num_running_workers == 0 - submissions2 = get_submissions(session_toy_aws, 'iris_aws_test', 'new') + submissions2 = get_submissions(session_toy_aws, "iris_aws_test", "new") # assert that all the submissions are still in the 'new' state assert len(submissions) == len(submissions2) -@mock.patch('ramp_engine.aws.api.download_log') -@mock.patch('ramp_engine.aws.api.check_instance_status') -@mock.patch('ramp_engine.aws.api._get_log_content') -@mock.patch('ramp_engine.aws.api._training_successful') -@mock.patch('ramp_engine.aws.api._training_finished') -@mock.patch('ramp_engine.aws.api.is_spot_terminated') -@mock.patch('ramp_engine.aws.api.launch_train') -@mock.patch('ramp_engine.aws.api.upload_submission') -@mock.patch('ramp_engine.aws.api.launch_ec2_instances') -def test_info_on_training_error(test_launch_ec2_instances, upload_submission, - launch_train, - is_spot_terminated, training_finished, - training_successful, - get_log_content, check_instance_status, - download_log, - session_toy_aws, - caplog): +@mock.patch("ramp_engine.aws.api.download_log") +@mock.patch("ramp_engine.aws.api.check_instance_status") +@mock.patch("ramp_engine.aws.api._get_log_content") +@mock.patch("ramp_engine.aws.api._training_successful") +@mock.patch("ramp_engine.aws.api._training_finished") +@mock.patch("ramp_engine.aws.api.is_spot_terminated") +@mock.patch("ramp_engine.aws.api.launch_train") +@mock.patch("ramp_engine.aws.api.upload_submission") +@mock.patch("ramp_engine.aws.api.launch_ec2_instances") +def test_info_on_training_error( + test_launch_ec2_instances, + upload_submission, + launch_train, + is_spot_terminated, + training_finished, + training_successful, + get_log_content, + check_instance_status, + download_log, + session_toy_aws, + caplog, +): # make sure that the Python error from the solution is passed to the # dispatcher # everything shoud be mocked as correct output from AWS instances @@ -300,6 +328,7 @@ def test_info_on_training_error(test_launch_ec2_instances, upload_submission, # mock dummy AWS instance class DummyInstance: id = 1 + test_launch_ec2_instances.return_value = (DummyInstance(),), 0 upload_submission.return_value = 0 launch_train.return_value = 0 @@ -310,19 +339,20 @@ class DummyInstance: config = read_config(database_config_template()) event_config = read_config(ramp_aws_config_template()) - dispatcher = Dispatcher(config=config, - event_config=event_config, - worker=AWSWorker, n_workers=10, - hunger_policy='exit') + dispatcher = Dispatcher( + config=config, + event_config=event_config, + worker=AWSWorker, + n_workers=10, + hunger_policy="exit", + ) dispatcher.fetch_from_db(session_toy_aws) dispatcher.launch_workers(session_toy_aws) num_running_workers = dispatcher._processing_worker_queue.qsize() # worker, (submission_id, submission_name) = \ # dispatcher._processing_worker_queue.get() # assert worker.status == 'running' - submissions = get_submissions(session_toy_aws, - 'iris_aws_test', - 'training') + submissions = get_submissions(session_toy_aws, "iris_aws_test", "training") ids = [submissions[idx][0] for idx in range(len(submissions))] assert len(submissions) > 1 assert num_running_workers == len(ids) @@ -332,9 +362,9 @@ class DummyInstance: # now we will end the submission with training error training_finished.return_value = True - training_error_msg = 'Python error here' + training_error_msg = "Python error here" get_log_content.return_value = training_error_msg - check_instance_status.return_value = 'finished' + check_instance_status.return_value = "finished" dispatcher.collect_result(session_toy_aws) @@ -343,9 +373,7 @@ class DummyInstance: assert num_running_workers == 0 - submissions = get_submissions(session_toy_aws, - 'iris_aws_test', - 'training_error') + submissions = get_submissions(session_toy_aws, "iris_aws_test", "training_error") assert len(submissions) == len(ids) submission = get_submission_by_id(session_toy_aws, submissions[0][0]) diff --git a/ramp-engine/ramp_engine/tests/test_remote_worker.py b/ramp-engine/ramp_engine/tests/test_remote_worker.py index 685c5ef00..3274f974a 100644 --- a/ramp-engine/ramp_engine/tests/test_remote_worker.py +++ b/ramp-engine/ramp_engine/tests/test_remote_worker.py @@ -8,48 +8,45 @@ def test_dask_workers_single_machine(): workers_nthreads = { - 'tcp://127.0.0.1:38901': 4, - 'tcp://127.0.0.1:43229': 4, - 'tcp://127.0.0.1:46663': 4 + "tcp://127.0.0.1:38901": 4, + "tcp://127.0.0.1:43229": 4, + "tcp://127.0.0.1:46663": 4, } assert _check_dask_workers_single_machine(workers_nthreads.keys()) is True - workers_nthreads = { - 'tcp://127.0.0.1:38901': 4, - 'tcp://127.0.0.2:43229': 4 - } + workers_nthreads = {"tcp://127.0.0.1:38901": 4, "tcp://127.0.0.2:43229": 4} - msg = 'dask workers should .* on 1 machine, found 2:' + msg = "dask workers should .* on 1 machine, found 2:" with pytest.raises(ValueError, match=msg): _check_dask_workers_single_machine(workers_nthreads.keys()) - msg = 'dask workers should .* on 1 machine, found 0:' + msg = "dask workers should .* on 1 machine, found 0:" with pytest.raises(ValueError, match=msg): - _check_dask_workers_single_machine('some invalid string') + _check_dask_workers_single_machine("some invalid string") def test_serialize_deserialize_folder(tmpdir): with pytest.raises(FileNotFoundError): - _serialize_folder('some_invalid_path') + _serialize_folder("some_invalid_path") base_dir = Path(tmpdir) - src_dir = base_dir / 'src' + src_dir = base_dir / "src" src_dir.mkdir() - with open(src_dir / '1.txt', 'wt') as fh: - fh.write('a') - (src_dir / 'dir2').mkdir() + with open(src_dir / "1.txt", "wt") as fh: + fh.write("a") + (src_dir / "dir2").mkdir() stream = _serialize_folder(src_dir) assert isinstance(stream, bytes) - dest_dir = base_dir / 'dest' + dest_dir = base_dir / "dest" _deserialize_folder(stream, dest_dir) assert sorted(os.listdir(src_dir)) == sorted(os.listdir(dest_dir)) # create some other file objects in the destination dir and try # to deserialize a second time - (dest_dir / 'dir3').mkdir() + (dest_dir / "dir3").mkdir() _deserialize_folder(stream, dest_dir) assert sorted(os.listdir(src_dir)) == sorted(os.listdir(dest_dir)) diff --git a/ramp-engine/setup.py b/ramp-engine/setup.py index 5574c2549..3d3edc8c2 100644 --- a/ramp-engine/setup.py +++ b/ramp-engine/setup.py @@ -5,42 +5,44 @@ from setuptools import find_packages, setup # get __version__ from _version.py -ver_file = os.path.join('ramp_engine', '_version.py') +ver_file = os.path.join("ramp_engine", "_version.py") with open(ver_file) as f: exec(f.read()) -DISTNAME = 'ramp-engine' +DISTNAME = "ramp-engine" DESCRIPTION = "Submissions orchestrator and processors for the RAMP bundle" -with codecs.open('README.rst', encoding='utf-8-sig') as f: +with codecs.open("README.rst", encoding="utf-8-sig") as f: LONG_DESCRIPTION = f.read() -MAINTAINER = 'A. Boucaud, B. Kegl, G. Lemaitre, J. Van den Bossche' -MAINTAINER_EMAIL = 'boucaud.alexandre@gmail.com, guillaume.lemaitre@inria.fr' -URL = 'https://github.com/paris-saclay-cds/ramp-board' -LICENSE = 'BSD (3-clause)' -DOWNLOAD_URL = 'https://github.com/paris-saclay-cds/ramp-board' +MAINTAINER = "A. Boucaud, B. Kegl, G. Lemaitre, J. Van den Bossche" +MAINTAINER_EMAIL = "boucaud.alexandre@gmail.com, guillaume.lemaitre@inria.fr" +URL = "https://github.com/paris-saclay-cds/ramp-board" +LICENSE = "BSD (3-clause)" +DOWNLOAD_URL = "https://github.com/paris-saclay-cds/ramp-board" VERSION = __version__ # noqa -CLASSIFIERS = ['Intended Audience :: Science/Research', - 'Intended Audience :: Developers', - 'License :: OSI Approved', - 'Programming Language :: Python', - 'Topic :: Software Development', - 'Topic :: Scientific/Engineering', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Operating System :: Unix', - 'Operating System :: MacOS', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8'] -INSTALL_REQUIRES = ['click', 'numpy', 'psycopg2-binary', 'sqlalchemy'] +CLASSIFIERS = [ + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "License :: OSI Approved", + "Programming Language :: Python", + "Topic :: Software Development", + "Topic :: Scientific/Engineering", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", +] +INSTALL_REQUIRES = ["click", "numpy", "psycopg2-binary", "sqlalchemy"] EXTRAS_REQUIRE = { - 'tests': ['pytest', 'pytest-cov'], - 'docs': ['sphinx', 'sphinx_rtd_theme', 'numpydoc'] + "tests": ["pytest", "pytest-cov"], + "docs": ["sphinx", "sphinx_rtd_theme", "numpydoc"], } PACKAGE_DATA = { - 'ramp_engine': [ - os.path.join('tests', 'events', 'iris_test', 'config.yml'), - os.path.join('tests', 'events', 'boston_housing', 'config.yml') + "ramp_engine": [ + os.path.join("tests", "events", "iris_test", "config.yml"), + os.path.join("tests", "events", "boston_housing", "config.yml"), ] } @@ -61,7 +63,5 @@ install_requires=INSTALL_REQUIRES, extras_require=EXTRAS_REQUIRE, python_requires=">=3.7", - entry_points={ - 'console_scripts': ['ramp-launch = ramp_engine.cli:start'] - } + entry_points={"console_scripts": ["ramp-launch = ramp_engine.cli:start"]}, ) diff --git a/ramp-frontend/ramp_frontend/__init__.py b/ramp-frontend/ramp_frontend/__init__.py index e28f3e80b..51de4bd70 100644 --- a/ramp-frontend/ramp_frontend/__init__.py +++ b/ramp-frontend/ramp_frontend/__init__.py @@ -10,9 +10,7 @@ from ._version import __version__ # noqa -all = [ - '__version__' -] +all = ["__version__"] HERE = os.path.dirname(__file__) db = SQLAlchemy(model_class=Model) @@ -39,32 +37,34 @@ def create_app(config): logger_config = config.pop("LOGGER") dictConfig(logger_config) else: - dictConfig({ - 'version': 1, - 'formatters': {'default': { - 'format': '[%(asctime)s] [%(levelname)s] %(message)s', - }}, - 'handlers': {'wsgi': { - 'class': 'logging.StreamHandler', - 'stream': 'ext://flask.logging.wsgi_errors_stream', - 'formatter': 'default' - }}, - 'root': { - 'level': 'INFO', - 'handlers': ['wsgi'] + dictConfig( + { + "version": 1, + "formatters": { + "default": { + "format": "[%(asctime)s] [%(levelname)s] %(message)s", + } + }, + "handlers": { + "wsgi": { + "class": "logging.StreamHandler", + "stream": "ext://flask.logging.wsgi_errors_stream", + "formatter": "default", + } + }, + "root": {"level": "INFO", "handlers": ["wsgi"]}, } - }) + ) - app = Flask('ramp-frontend', root_path=HERE) + app = Flask("ramp-frontend", root_path=HERE) app.config.update(config) with app.app_context(): db.init_app(app) # register the login manager login_manager.init_app(app) - login_manager.login_view = 'auth.login' - login_manager.login_message = ('Please log in or sign up to access ' - 'this page.') + login_manager.login_view = "auth.login" + login_manager.login_message = "Please log in or sign up to access " "this page." # register the email manager mail.init_app(app) # register our blueprint @@ -73,6 +73,7 @@ def create_app(config): from .views import general from .views import leaderboard from .views import ramp + app.register_blueprint(admin.mod) app.register_blueprint(auth.mod) app.register_blueprint(general.mod) diff --git a/ramp-frontend/ramp_frontend/_version.py b/ramp-frontend/ramp_frontend/_version.py index 726e34836..58d460c8c 100644 --- a/ramp-frontend/ramp_frontend/_version.py +++ b/ramp-frontend/ramp_frontend/_version.py @@ -21,4 +21,4 @@ # 'X.Y.dev0' is the canonical version of 'X.Y.dev' # -__version__ = '0.9.0.dev0' +__version__ = "0.9.0.dev0" diff --git a/ramp-frontend/ramp_frontend/cli.py b/ramp-frontend/ramp_frontend/cli.py index 56df6af98..478e78f75 100644 --- a/ramp-frontend/ramp_frontend/cli.py +++ b/ramp-frontend/ramp_frontend/cli.py @@ -2,7 +2,7 @@ from .wsgi import make_app -CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) +CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @click.group(context_settings=CONTEXT_SETTINGS) @@ -11,21 +11,32 @@ def main(): @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file in YAML format') -@click.option("--port", default=8080, show_default=True, - help='The port where to launch the website') -@click.option("--host", default='127.0.0.1', show_default=True, - help='The IP address where to launch the website') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file in YAML format", +) +@click.option( + "--port", + default=8080, + show_default=True, + help="The port where to launch the website", +) +@click.option( + "--host", + default="127.0.0.1", + show_default=True, + help="The IP address where to launch the website", +) def test_launch(config, port, host): app = make_app(config) - app.run(port=port, use_reloader=False, - host=host, processes=1, threaded=False) + app.run(port=port, use_reloader=False, host=host, processes=1, threaded=False) def start(): main() -if __name__ == '__main__': +if __name__ == "__main__": start() diff --git a/ramp-frontend/ramp_frontend/forms.py b/ramp-frontend/ramp_frontend/forms.py index 4aee48390..245db7873 100644 --- a/ramp-frontend/ramp_frontend/forms.py +++ b/ramp-frontend/ramp_frontend/forms.py @@ -21,15 +21,15 @@ def _space_check(form, field): - if ' ' in field.data: - raise ValidationError('Field cannot contain space.') + if " " in field.data: + raise ValidationError("Field cannot contain space.") def _ascii_check(form, field): try: - field.data.encode('ascii') + field.data.encode("ascii") except Exception: - raise ValidationError('Field cannot contain non-ascii characters.') + raise ValidationError("Field cannot contain non-ascii characters.") class LoginForm(FlaskForm): @@ -42,8 +42,9 @@ class LoginForm(FlaskForm): password : str The user password. """ - user_name = StringField('user_name', [validators.DataRequired()]) - password = PasswordField('password', [validators.DataRequired()]) + + user_name = StringField("user_name", [validators.DataRequired()]) + password = PasswordField("password", [validators.DataRequired()]) class UserUpdateProfileForm(FlaskForm): @@ -76,19 +77,25 @@ class UserUpdateProfileForm(FlaskForm): is_want_news : bool, default is True Whether the user want some info from us. """ - user_name = StringField('user_name', [ - validators.DataRequired(), validators.Length(min=1, max=20), - _space_check]) - firstname = StringField('firstname', [validators.DataRequired()]) - lastname = StringField('lastname', [validators.DataRequired()]) - email = StringField('email', [validators.DataRequired()]) - linkedin_url = StringField('linkedin_url') - twitter_url = StringField('twitter_url') - facebook_url = StringField('facebook_url') - google_url = StringField('google_url') - github_url = StringField('github_url') - website_url = StringField('website_url') - bio = StringField('bio') + + user_name = StringField( + "user_name", + [ + validators.DataRequired(), + validators.Length(min=1, max=20), + _space_check, + ], + ) + firstname = StringField("firstname", [validators.DataRequired()]) + lastname = StringField("lastname", [validators.DataRequired()]) + email = StringField("email", [validators.DataRequired()]) + linkedin_url = StringField("linkedin_url") + twitter_url = StringField("twitter_url") + facebook_url = StringField("facebook_url") + google_url = StringField("google_url") + github_url = StringField("github_url") + website_url = StringField("website_url") + bio = StringField("bio") is_want_news = BooleanField() @@ -124,7 +131,8 @@ class UserCreateProfileForm(UserUpdateProfileForm): is_want_news : bool, default is True Whether the user want some info from us. """ - password = PasswordField('password', [validators.DataRequired()]) + + password = PasswordField("password", [validators.DataRequired()]) class CodeForm(FlaskForm): @@ -138,6 +146,7 @@ class CodeForm(FlaskForm): The place holder containing the name of the submission file and the code associated. """ + names_codes: List[Tuple[str, int]] = [] @@ -152,8 +161,10 @@ class SubmitForm(FlaskForm): submission_name : str The name of the submission. """ - submission_name = StringField('submission_name', - [validators.DataRequired(), _space_check]) + + submission_name = StringField( + "submission_name", [validators.DataRequired(), _space_check] + ) class UploadForm(FlaskForm): @@ -167,7 +178,8 @@ class UploadForm(FlaskForm): file : file File to be uploaded and loaded into the sandbox code form. """ - file = FileField('file') + + file = FileField("file") class EventUpdateProfileForm(FlaskForm): @@ -198,8 +210,9 @@ class EventUpdateProfileForm(FlaskForm): public_opening_timestamp : datetime The date and time when the public phase of the event is opening. """ + title = StringField( - 'event_title', [validators.DataRequired(), validators.Length(max=80)] + "event_title", [validators.DataRequired(), validators.Length(max=80)] ) is_send_trained_mails = BooleanField() is_send_submitted_mails = BooleanField() @@ -207,34 +220,36 @@ class EventUpdateProfileForm(FlaskForm): is_controled_signup = BooleanField() is_competitive = BooleanField() min_duration_between_submissions_hour = IntegerField( - 'min_h', [validators.NumberRange(min=0)] + "min_h", [validators.NumberRange(min=0)] ) min_duration_between_submissions_minute = IntegerField( - 'min_m', [validators.NumberRange(min=0, max=59)] + "min_m", [validators.NumberRange(min=0, max=59)] ) min_duration_between_submissions_second = IntegerField( - 'min_s', [validators.NumberRange(min=0, max=59)] + "min_s", [validators.NumberRange(min=0, max=59)] ) opening_timestamp = DateTimeField( - 'opening_timestamp', [], format='%Y-%m-%d %H:%M:%S' + "opening_timestamp", [], format="%Y-%m-%d %H:%M:%S" ) closing_timestamp = DateTimeField( - 'closing_timestamp', [], format='%Y-%m-%d %H:%M:%S' + "closing_timestamp", [], format="%Y-%m-%d %H:%M:%S" ) public_opening_timestamp = DateTimeField( - 'public_opening_timestamp', [], format='%Y-%m-%d %H:%M:%S' + "public_opening_timestamp", [], format="%Y-%m-%d %H:%M:%S" ) class MultiCheckboxField(SelectMultipleField): """A form containing multiple checkboxes.""" + widget = ListWidget(prefix_label=False) option_widget = CheckboxInput() class ImportForm(FlaskForm): """The form allowing to select which model to view.""" - selected_f_names = MultiCheckboxField('selected_f_names') + + selected_f_names = MultiCheckboxField("selected_f_names") class CreditForm(FlaskForm): @@ -252,8 +267,9 @@ class CreditForm(FlaskForm): name_credits : list The name for the credits. """ - note = StringField('submission_name') - self_credit = StringField('self credit') + + note = StringField("submission_name") + self_credit = StringField("self credit") name_credits: List[str] = [] @@ -279,43 +295,45 @@ class AskForEventForm(FlaskForm): closing_timestamp : datetime The date and time when the event is closing. """ + suffix = StringField( - 'event_suffix', - [validators.DataRequired(), validators.Length(max=20), _ascii_check, - _space_check] + "event_suffix", + [ + validators.DataRequired(), + validators.Length(max=20), + _ascii_check, + _space_check, + ], ) title = StringField( - 'event_title', - [validators.DataRequired(), validators.Length(max=80)] + "event_title", [validators.DataRequired(), validators.Length(max=80)] ) n_students = IntegerField( - 'n_students', - [validators.DataRequired(), validators.NumberRange(min=0)] + "n_students", + [validators.DataRequired(), validators.NumberRange(min=0)], ) min_duration_between_submissions_hour = IntegerField( - 'min_h', [validators.NumberRange(min=0)] + "min_h", [validators.NumberRange(min=0)] ) min_duration_between_submissions_minute = IntegerField( - 'min_m', [validators.NumberRange(min=0, max=59)] + "min_m", [validators.NumberRange(min=0, max=59)] ) min_duration_between_submissions_second = IntegerField( - 'min_s', [validators.NumberRange(min=0, max=59)] + "min_s", [validators.NumberRange(min=0, max=59)] ) opening_date = DateField( - 'opening_date', [validators.DataRequired()], format='%Y-%m-%d' + "opening_date", [validators.DataRequired()], format="%Y-%m-%d" ) closing_date = DateField( - 'closing_date', [validators.DataRequired()], format='%Y-%m-%d' + "closing_date", [validators.DataRequired()], format="%Y-%m-%d" ) class EmailForm(FlaskForm): email = StringField( - 'Email', validators=[validators.DataRequired(), validators.Email()] + "Email", validators=[validators.DataRequired(), validators.Email()] ) class PasswordForm(FlaskForm): - password = PasswordField( - 'Password', validators=[validators.DataRequired()] - ) + password = PasswordField("Password", validators=[validators.DataRequired()]) diff --git a/ramp-frontend/ramp_frontend/testing.py b/ramp-frontend/ramp_frontend/testing.py index b1453d0ec..aa3e7e037 100644 --- a/ramp-frontend/ramp_frontend/testing.py +++ b/ramp-frontend/ramp_frontend/testing.py @@ -27,10 +27,11 @@ def login(client, username, password): response : :class:`flask.wrappers.Response` The response of the client. """ - return client.post('/login', data=dict( - user_name=username, - password=password - ), follow_redirects=True) + return client.post( + "/login", + data=dict(user_name=username, password=password), + follow_redirects=True, + ) def logout(client): @@ -49,7 +50,7 @@ def logout(client): response : :class:`flask.wrappers.Response` The response of the client. """ - return client.get('/logout', follow_redirects=True) + return client.get("/logout", follow_redirects=True) @contextmanager diff --git a/ramp-frontend/ramp_frontend/tests/test_admin.py b/ramp-frontend/ramp_frontend/tests/test_admin.py index 90b652f1b..fa610637d 100644 --- a/ramp-frontend/ramp_frontend/tests/test_admin.py +++ b/ramp-frontend/ramp_frontend/tests/test_admin.py @@ -25,7 +25,7 @@ from ramp_frontend.testing import login_scope -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def client_session(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() @@ -33,64 +33,70 @@ def client_session(database_connection): deployment_dir = create_toy_db(database_config, ramp_config) flask_config = generate_flask_config(database_config) app = create_app(flask_config) - app.config['TESTING'] = True - app.config['WTF_CSRF_ENABLED'] = False - with session_scope(database_config['sqlalchemy']) as session: + app.config["TESTING"] = True + app.config["WTF_CSRF_ENABLED"] = False + with session_scope(database_config["sqlalchemy"]) as session: yield app.test_client(), session finally: shutil.rmtree(deployment_dir, ignore_errors=True) try: # In case of failure we should close the global flask engine from ramp_frontend import db as db_flask + db_flask.session.close() except RuntimeError: pass - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) @pytest.mark.parametrize( "page", - ["/approve_users", - "/manage_users", - "/sign_up/test_user", - "/events/iris_test/sign_up/test_user", - "/events/iris_test/update", - "/user_interactions", - "/events/iris_test/dashboard_submissions"] + [ + "/approve_users", + "/manage_users", + "/sign_up/test_user", + "/events/iris_test/sign_up/test_user", + "/events/iris_test/update", + "/user_interactions", + "/events/iris_test/dashboard_submissions", + ], ) def test_check_login_required(client_session, page): client, _ = client_session rv = client.get(page) assert rv.status_code == 302 - assert 'http://localhost/login' in rv.location + assert "http://localhost/login" in rv.location rv = client.get(page, follow_redirects=True) assert rv.status_code == 200 @pytest.mark.parametrize( "page, request_function", - [("/approve_users", ["get", "post"]), - ('/manage_users', ['get']), - ("/sign_up/test_user", ["get"]), - ("/events/iris_test/sign_up/test_user", ["get"]), - ("/events/iris_test/update", ["get", "post"]), - ("/user_interactions", ["get"]), - ("/events/iris_test/dashboard_submissions", ["get"])] + [ + ("/approve_users", ["get", "post"]), + ("/manage_users", ["get"]), + ("/sign_up/test_user", ["get"]), + ("/events/iris_test/sign_up/test_user", ["get"]), + ("/events/iris_test/update", ["get", "post"]), + ("/user_interactions", ["get"]), + ("/events/iris_test/dashboard_submissions", ["get"]), + ], ) def test_check_admin_required(client_session, page, request_function): client, _ = client_session - with login_scope(client, 'test_user', 'test') as client: + with login_scope(client, "test_user", "test") as client: for rf in request_function: rv = getattr(client, rf)(page) with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert (flash_message['message'] == - 'Sorry User, you do not have admin rights') + flash_message = dict(cs["_flashes"]) + assert ( + flash_message["message"] == "Sorry User, you do not have admin rights" + ) assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' + assert rv.location == "http://localhost/problems" rv = getattr(client, rf)(page, follow_redirects=True) assert rv.status_code == 200 @@ -99,120 +105,129 @@ def test_approve_users_remove(client_session): client, session = client_session # create 2 new users - add_user(session, 'xx', 'xx', 'xx', 'xx', 'xx', access_level='user') - add_user(session, 'yy', 'yy', 'yy', 'yy', 'yy', access_level='asked') + add_user(session, "xx", "xx", "xx", "xx", "xx", access_level="user") + add_user(session, "yy", "yy", "yy", "yy", "yy", access_level="asked") # ask for sign up for an event for the first user - _, _, event_team = ask_sign_up_team(session, 'iris_test', 'xx') + _, _, event_team = ask_sign_up_team(session, "iris_test", "xx") - with login_scope(client, 'test_iris_admin', 'test') as client: + with login_scope(client, "test_iris_admin", "test") as client: # GET check that we get all new user to be approved - rv = client.get('/approve_users') + rv = client.get("/approve_users") assert rv.status_code == 200 # line for user approval - assert b'yy yy - yy' in rv.data + assert b"yy yy - yy" in rv.data # line for the event approval - assert b'iris_test - xx' + assert b"iris_test - xx" # POST check that we are able to approve a user and event - data = ImmutableMultiDict([ - ('submit_button', 'Remove!'), - ('approve_users', 'yy'), - ('approve_event_teams', str(event_team.id)) - ]) - rv = client.post('/approve_users', data=data) + data = ImmutableMultiDict( + [ + ("submit_button", "Remove!"), + ("approve_users", "yy"), + ("approve_event_teams", str(event_team.id)), + ] + ) + rv = client.post("/approve_users", data=data) assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' + assert rv.location == "http://localhost/problems" # ensure that the previous change have been committed within our # session session.commit() - user = get_user_by_name(session, 'yy') + user = get_user_by_name(session, "yy") assert user is None - event_team = get_event_team_by_name(session, 'iris_test', 'xx') + event_team = get_event_team_by_name(session, "iris_test", "xx") assert event_team is None with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert re.match(r"Removed users:\nyy\nRemoved event_team:\n" - r"Event\(iris_test\)/Team\(.*xx.*\)\n", - flash_message['Removed users']) + flash_message = dict(cs["_flashes"]) + assert re.match( + r"Removed users:\nyy\nRemoved event_team:\n" + r"Event\(iris_test\)/Team\(.*xx.*\)\n", + flash_message["Removed users"], + ) def test_approve_users_approve(client_session): client, session = client_session # create 2 new users - add_user(session, 'cc', 'cc', 'cc', 'cc', 'cc', access_level='user') - add_user(session, 'dd', 'dd', 'dd', 'dd', 'dd', access_level='asked') + add_user(session, "cc", "cc", "cc", "cc", "cc", access_level="user") + add_user(session, "dd", "dd", "dd", "dd", "dd", access_level="asked") # ask for sign up for an event for the first user - _, _, event_team = ask_sign_up_team(session, 'iris_test', 'cc') + _, _, event_team = ask_sign_up_team(session, "iris_test", "cc") - with login_scope(client, 'test_iris_admin', 'test') as client: + with login_scope(client, "test_iris_admin", "test") as client: # GET check that we get all new user to be approved - rv = client.get('/approve_users') + rv = client.get("/approve_users") assert rv.status_code == 200 # line for user approval - assert b'dd dd - dd' in rv.data + assert b"dd dd - dd" in rv.data # line for the event approval - assert b'iris_test - cc' + assert b"iris_test - cc" # POST check that we are able to approve a user and event - data = ImmutableMultiDict([ - ('submit_button', 'Approve!'), - ('approve_users', 'dd'), - ('approve_event_teams', str(event_team.id))] + data = ImmutableMultiDict( + [ + ("submit_button", "Approve!"), + ("approve_users", "dd"), + ("approve_event_teams", str(event_team.id)), + ] ) - rv = client.post('/approve_users', data=data) + rv = client.post("/approve_users", data=data) assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' + assert rv.location == "http://localhost/problems" # ensure that the previous change have been committed within our # session session.commit() - user = get_user_by_name(session, 'dd') - assert user.access_level == 'user' - event_team = get_event_team_by_name(session, 'iris_test', 'cc') + user = get_user_by_name(session, "dd") + assert user.access_level == "user" + event_team = get_event_team_by_name(session, "iris_test", "cc") assert event_team.approved with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert re.match(r"Approved users:\ndd\nApproved event_team:\n" - r"Event\(iris_test\)/Team\(.*cc.*\)\n", - flash_message['Approved users']) + flash_message = dict(cs["_flashes"]) + assert re.match( + r"Approved users:\ndd\nApproved event_team:\n" + r"Event\(iris_test\)/Team\(.*cc.*\)\n", + flash_message["Approved users"], + ) def test_approve_single_user(client_session): client, session = client_session - add_user(session, 'gg', 'gg', 'gg', 'gg', 'gg', access_level='asked') - with login_scope(client, 'test_iris_admin', 'test') as client: - rv = client.get('/sign_up/gg') + add_user(session, "gg", "gg", "gg", "gg", "gg", access_level="asked") + with login_scope(client, "test_iris_admin", "test") as client: + rv = client.get("/sign_up/gg") assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' + assert rv.location == "http://localhost/problems" with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert re.match("User(.*gg.*) is signed up", - flash_message['Successful sign-up']) + flash_message = dict(cs["_flashes"]) + assert re.match( + "User(.*gg.*) is signed up", flash_message["Successful sign-up"] + ) # ensure that the previous change have been committed within our # session session.commit() - user = get_user_by_name(session, 'gg') - assert user.access_level == 'user' + user = get_user_by_name(session, "gg") + assert user.access_level == "user" rv = client.get("/sign_up/unknown_user") session.commit() assert rv.status_code == 302 assert rv.location == "http://localhost/problems" with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert flash_message['message'] == 'No user unknown_user' + flash_message = dict(cs["_flashes"]) + assert flash_message["message"] == "No user unknown_user" def test_approve_sign_up_for_event(client_session): client, session = client_session - with login_scope(client, 'test_iris_admin', 'test') as client: + with login_scope(client, "test_iris_admin", "test") as client: # check the redirection if the user or the event does not exist rv = client.get("/events/xxx/sign_up/test_user") @@ -220,43 +235,43 @@ def test_approve_sign_up_for_event(client_session): assert rv.status_code == 302 assert rv.location == "http://localhost/problems" with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert flash_message['message'] == 'No event xxx or no user test_user' + flash_message = dict(cs["_flashes"]) + assert flash_message["message"] == "No event xxx or no user test_user" rv = client.get("/events/iris_test/sign_up/xxxx") session.commit() assert rv.status_code == 302 assert rv.location == "http://localhost/problems" with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert flash_message['message'] == 'No event iris_test or no user xxxx' + flash_message = dict(cs["_flashes"]) + assert flash_message["message"] == "No event iris_test or no user xxxx" - add_user(session, 'zz', 'zz', 'zz', 'zz', 'zz', access_level='user') - _, _, event_team = ask_sign_up_team(session, 'iris_test', 'zz') + add_user(session, "zz", "zz", "zz", "zz", "zz", access_level="user") + _, _, event_team = ask_sign_up_team(session, "iris_test", "zz") assert not event_team.approved - rv = client.get('/events/iris_test/sign_up/zz') + rv = client.get("/events/iris_test/sign_up/zz") assert rv.status_code == 302 assert rv.location == "http://localhost/problems" session.commit() - event_team = get_event_team_by_name(session, 'iris_test', 'zz') + event_team = get_event_team_by_name(session, "iris_test", "zz") assert event_team.approved with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert "is signed up for Event" in flash_message['Successful sign-up'] + flash_message = dict(cs["_flashes"]) + assert "is signed up for Event" in flash_message["Successful sign-up"] def test_manage_users(client_session): client, session = client_session # create 2 new users - add_user(session, 'ff', 'ff', 'ff', 'ff', 'ff', access_level='user') - add_user(session, 'll', 'll', 'll', 'll', 'll', access_level='asked') + add_user(session, "ff", "ff", "ff", "ff", "ff", access_level="user") + add_user(session, "ll", "ll", "ll", "ll", "ll", access_level="asked") # ask for sign up for an event for the first user - _, _, event_team = ask_sign_up_team(session, 'iris_test', 'xx') + _, _, event_team = ask_sign_up_team(session, "iris_test", "xx") - with login_scope(client, 'test_iris_admin', 'test') as client: + with login_scope(client, "test_iris_admin", "test") as client: # GET check that we get all users - rv = client.get('/manage_users') + rv = client.get("/manage_users") assert rv.status_code == 200 # assert b'yy yy - yy' in rv.data @@ -264,50 +279,50 @@ def test_manage_users(client_session): def test_update_event(client_session): client, session = client_session - with login_scope(client, 'test_iris_admin', 'test') as client: + with login_scope(client, "test_iris_admin", "test") as client: # case tha the event does not exist - rv = client.get('/events/boston_housing/update') + rv = client.get("/events/boston_housing/update") assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' + assert rv.location == "http://localhost/problems" with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert 'no event named "boston_housing"' in flash_message['message'] + flash_message = dict(cs["_flashes"]) + assert 'no event named "boston_housing"' in flash_message["message"] # GET: pre-fill the forms - rv = client.get('/events/iris_test/update') + rv = client.get("/events/iris_test/update") assert rv.status_code == 200 - assert b'Minimum duration between submissions' in rv.data + assert b"Minimum duration between submissions" in rv.data # POST: update the event data event_info = { - 'suffix': 'test', - 'title': 'Iris new title', - 'is_send_trained_mail': True, - 'is_public': True, - 'is_controled_signup': True, - 'is_competitive': False, - 'min_duration_between_submissions_hour': 0, - 'min_duration_between_submissions_minute': 0, - 'min_duration_between_submissions_second': 0, - 'opening_timestamp': "2000-01-01 00:00:00", - 'closing_timestamp': "2100-01-01 00:00:00", - 'public_opening_timestamp': "2000-01-01 00:00:00", + "suffix": "test", + "title": "Iris new title", + "is_send_trained_mail": True, + "is_public": True, + "is_controled_signup": True, + "is_competitive": False, + "min_duration_between_submissions_hour": 0, + "min_duration_between_submissions_minute": 0, + "min_duration_between_submissions_second": 0, + "opening_timestamp": "2000-01-01 00:00:00", + "closing_timestamp": "2100-01-01 00:00:00", + "public_opening_timestamp": "2000-01-01 00:00:00", } - rv = client.post('/events/iris_test/update', data=event_info) + rv = client.post("/events/iris_test/update", data=event_info) assert rv.status_code == 302 assert rv.location == "http://localhost/problems" - event = get_event(session, 'iris_test') + event = get_event(session, "iris_test") assert event.min_duration_between_submissions == 0 def test_user_interactions(client_session): client, _ = client_session - with login_scope(client, 'test_iris_admin', 'test') as client: - rv = client.get('/user_interactions') + with login_scope(client, "test_iris_admin", "test") as client: + rv = client.get("/user_interactions") assert rv.status_code == 200 - assert b'landing' in rv.data + assert b"landing" in rv.data # TODO: To be tested when we implemented properly the leaderboard diff --git a/ramp-frontend/ramp_frontend/tests/test_auth.py b/ramp-frontend/ramp_frontend/tests/test_auth.py index 4839ed5e2..76a6b8ee6 100644 --- a/ramp-frontend/ramp_frontend/tests/test_auth.py +++ b/ramp-frontend/ramp_frontend/tests/test_auth.py @@ -26,7 +26,7 @@ from ramp_frontend.testing import _fail_no_smtp_server -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def client_session(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() @@ -34,19 +34,20 @@ def client_session(database_connection): deployment_dir = create_toy_db(database_config, ramp_config) flask_config = generate_flask_config(database_config) app = create_app(flask_config) - app.config['TESTING'] = True - app.config['WTF_CSRF_ENABLED'] = False - with session_scope(database_config['sqlalchemy']) as session: + app.config["TESTING"] = True + app.config["WTF_CSRF_ENABLED"] = False + with session_scope(database_config["sqlalchemy"]) as session: yield app.test_client(), session finally: shutil.rmtree(deployment_dir, ignore_errors=True) try: # In case of failure we should close the global flask engine from ramp_frontend import db as db_flask + db_flask.session.close() except RuntimeError: pass - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) @@ -54,77 +55,83 @@ def test_login(client_session): client, session = client_session # GET without any previous login - rv = client.get('/login') + rv = client.get("/login") assert rv.status_code == 200 - assert b'Login' in rv.data - assert b'Username' in rv.data - assert b'Password' in rv.data + assert b"Login" in rv.data + assert b"Username" in rv.data + assert b"Password" in rv.data # GET with a previous login - with login_scope(client, 'test_user', 'test') as client: - rv = client.get('/login') + with login_scope(client, "test_user", "test") as client: + rv = client.get("/login") assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' - rv = client.get('/login', follow_redirects=True) + assert rv.location == "http://localhost/problems" + rv = client.get("/login", follow_redirects=True) assert rv.status_code == 200 # POST with unknown username - login_info = {'user_name': 'unknown', 'password': 'xxx'} - rv = client.post('/login', data=login_info) + login_info = {"user_name": "unknown", "password": "xxx"} + rv = client.post("/login", data=login_info) with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert flash_message['message'] == 'User "unknown" does not exist' + flash_message = dict(cs["_flashes"]) + assert flash_message["message"] == 'User "unknown" does not exist' assert rv.status_code == 302 - assert rv.location == 'http://localhost/login' - rv = client.post('/login', data=login_info, follow_redirects=True) + assert rv.location == "http://localhost/login" + rv = client.post("/login", data=login_info, follow_redirects=True) assert rv.status_code == 200 # POST with wrong password - login_info = {'user_name': 'test_user', 'password': 'xxx'} - rv = client.post('/login', data=login_info) + login_info = {"user_name": "test_user", "password": "xxx"} + rv = client.post("/login", data=login_info) with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert flash_message['message'] == 'Wrong password' + flash_message = dict(cs["_flashes"]) + assert flash_message["message"] == "Wrong password" assert rv.status_code == 302 - assert rv.location == 'http://localhost/login' - rv = client.post('/login', data=login_info, follow_redirects=True) + assert rv.location == "http://localhost/login" + rv = client.post("/login", data=login_info, follow_redirects=True) assert rv.status_code == 200 # POST with a right login and password - login_info = {'user_name': 'test_user', 'password': 'test'} - rv = client.post('/login', data=login_info) + login_info = {"user_name": "test_user", "password": "test"} + rv = client.post("/login", data=login_info) assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' - user = get_user_by_name_or_email(session, login_info['user_name']) + assert rv.location == "http://localhost/problems" + user = get_user_by_name_or_email(session, login_info["user_name"]) assert user.is_authenticated logout(client) - rv = client.post('/login', data=login_info, follow_redirects=True) + rv = client.post("/login", data=login_info, follow_redirects=True) assert rv.status_code == 200 logout(client) # POST with a right email as login and password - login_info = {'user_name': 'test_user', 'password': 'test', - 'email': 'test.user@gmail.com'} - rv = client.post('/login', data=login_info) + login_info = { + "user_name": "test_user", + "password": "test", + "email": "test.user@gmail.com", + } + rv = client.post("/login", data=login_info) assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' - user = get_user_by_name_or_email(session, - login_info['email']) + assert rv.location == "http://localhost/problems" + user = get_user_by_name_or_email(session, login_info["email"]) assert user.is_authenticated logout(client) - rv = client.post('/login', data=login_info, follow_redirects=True) + rv = client.post("/login", data=login_info, follow_redirects=True) assert rv.status_code == 200 logout(client) # POST with right login and password from a different location webpage - login_info = {'user_name': 'test_user', 'password': 'test'} - landing_page = {'next': 'http://localhost/events/iris_test'} - rv = client.post('/login', data=login_info, query_string=landing_page) + login_info = {"user_name": "test_user", "password": "test"} + landing_page = {"next": "http://localhost/events/iris_test"} + rv = client.post("/login", data=login_info, query_string=landing_page) assert rv.status_code == 302 - assert rv.location == landing_page['next'] + assert rv.location == landing_page["next"] logout(client) - rv = client.post('/login', data=login_info, query_string=landing_page, - follow_redirects=True) + rv = client.post( + "/login", + data=login_info, + query_string=landing_page, + follow_redirects=True, + ) assert rv.status_code == 200 logout(client) @@ -133,21 +140,21 @@ def test_logout(client_session): client, session = client_session # logout without previous login - rv = client.get('/logout') + rv = client.get("/logout") assert rv.status_code == 302 - assert rv.location == 'http://localhost/login?next=%2Flogout' - rv = client.get('/logout', follow_redirects=True) + assert rv.location == "http://localhost/login?next=%2Flogout" + rv = client.get("/logout", follow_redirects=True) assert rv.status_code == 200 # logout from a previous login - login(client, 'test_user', 'test') - rv = client.get('/logout') + login(client, "test_user", "test") + rv = client.get("/logout") assert rv.status_code == 302 - assert rv.location == 'http://localhost/login' - user = get_user_by_name(session, 'test_user') + assert rv.location == "http://localhost/login" + user = get_user_by_name(session, "test_user") assert not user.is_authenticated - login(client, 'test_user', 'test') - rv = client.get('/logout', follow_redirects=True) + login(client, "test_user", "test") + rv = client.get("/logout", follow_redirects=True) assert rv.status_code == 200 @@ -155,23 +162,29 @@ def test_delete_profile(client_session): client, session = client_session # try to delete profile without previous login - rv = client.get('/delete_profile') + rv = client.get("/delete_profile") assert rv.status_code == 302 - assert rv.location == 'http://localhost/login?next=%2Fdelete_profile' - rv = client.get('/delete_profile', follow_redirects=True) + assert rv.location == "http://localhost/login?next=%2Fdelete_profile" + rv = client.get("/delete_profile", follow_redirects=True) assert rv.status_code == 200 # delete profile from a previous login - user = add_user(session, name='test_user_tmp', - password='password', lastname='lastname', - firstname='firstname', email='test_user_tmp@email.com', - access_level='asked', github_url="some") + user = add_user( + session, + name="test_user_tmp", + password="password", + lastname="lastname", + firstname="firstname", + email="test_user_tmp@email.com", + access_level="asked", + github_url="some", + ) user_id = user.id user_password = user.hashed_password - login(client, 'test_user_tmp', 'password') - rv = client.get('/delete_profile', follow_redirects=False) + login(client, "test_user_tmp", "password") + rv = client.get("/delete_profile", follow_redirects=False) assert rv.status_code == 302 - assert rv.location == 'http://localhost/' + assert rv.location == "http://localhost/" session.refresh(user) assert not user.is_authenticated assert user.firstname == "deleted" @@ -183,17 +196,16 @@ def test_delete_profile(client_session): assert user.admined_teams[0].name == f"deleted_{user_id}" -@pytest.mark.parametrize("request_function", ['get', 'post']) +@pytest.mark.parametrize("request_function", ["get", "post"]) def test_sign_up_already_logged_in(client_session, request_function): client, _ = client_session # sign-up when already logged-in - with login_scope(client, 'test_user', 'test') as client: - rv = getattr(client, request_function)('/sign_up') + with login_scope(client, "test_user", "test") as client: + rv = getattr(client, request_function)("/sign_up") assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' - rv = getattr(client, request_function)('/sign_up', - follow_redirects=True) + assert rv.location == "http://localhost/problems" + rv = getattr(client, request_function)("/sign_up", follow_redirects=True) assert rv.status_code == 200 @@ -201,46 +213,59 @@ def test_sign_up(client_session): client, session = client_session # GET on sign-up - rv = client.get('/sign_up') + rv = client.get("/sign_up") assert rv.status_code == 200 - assert b'Sign Up' in rv.data + assert b"Sign Up" in rv.data # POST on sign-up - user_profile = {'user_name': 'xx', 'password': 'xx', 'firstname': 'xx', - 'lastname': 'xx', 'email': 'xx'} - rv = client.post('/sign_up', data=user_profile) + user_profile = { + "user_name": "xx", + "password": "xx", + "firstname": "xx", + "lastname": "xx", + "email": "xx", + } + rv = client.post("/sign_up", data=user_profile) assert rv.status_code == 302 - user = get_user_by_name(session, 'xx') - assert user.name == 'xx' - user_profile = {'user_name': 'yy', 'password': 'yy', 'firstname': 'yy', - 'lastname': 'yy', 'email': 'yy'} - rv = client.post('/sign_up', data=user_profile, follow_redirects=True) + user = get_user_by_name(session, "xx") + assert user.name == "xx" + user_profile = { + "user_name": "yy", + "password": "yy", + "firstname": "yy", + "lastname": "yy", + "email": "yy", + } + rv = client.post("/sign_up", data=user_profile, follow_redirects=True) assert rv.status_code == 200 - def _assert_flash(url, data, status_code=302, - message='username is already in use'): - rv = client.post('/sign_up', data=data) + def _assert_flash(url, data, status_code=302, message="username is already in use"): + rv = client.post("/sign_up", data=data) with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert (flash_message['message'] == message) + flash_message = dict(cs["_flashes"]) + assert flash_message["message"] == message assert rv.status_code == status_code # check that we catch a flash error if we try to sign-up with an identical # username - user_profile = {'user_name': 'xx', 'password': 'xx', - 'firstname': 'xx', 'lastname': 'xx', - 'email': 'test_user@gmail.com'} - _assert_flash('/sign_up', data=user_profile, - message='username is already in use') - - user_profile.update(user_name='new', email="yy") - _assert_flash('/sign_up', data=user_profile, - message='email is already in use') - - user_profile.update(user_name='yy', email="yy") - _assert_flash('/sign_up', data=user_profile, - message=("username is already in use " - "and email is already in use")) + user_profile = { + "user_name": "xx", + "password": "xx", + "firstname": "xx", + "lastname": "xx", + "email": "test_user@gmail.com", + } + _assert_flash("/sign_up", data=user_profile, message="username is already in use") + + user_profile.update(user_name="new", email="yy") + _assert_flash("/sign_up", data=user_profile, message="email is already in use") + + user_profile.update(user_name="yy", email="yy") + _assert_flash( + "/sign_up", + data=user_profile, + message=("username is already in use " "and email is already in use"), + ) @_fail_no_smtp_server @@ -251,32 +276,32 @@ def test_sign_up_with_approval(client_session): with client.application.app_context(): with mail.record_messages() as outbox: user_profile = { - 'user_name': 'new_user_1', 'password': 'xx', 'firstname': 'xx', - 'lastname': 'xx', 'email': 'new_user_1@mail.com' + "user_name": "new_user_1", + "password": "xx", + "firstname": "xx", + "lastname": "xx", + "email": "new_user_1@mail.com", } - rv = client.post('/sign_up', data=user_profile) + rv = client.post("/sign_up", data=user_profile) # check the flash box to inform the user about the mail with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert 'We sent a confirmation email.' in flash_message['message'] + flash_message = dict(cs["_flashes"]) + assert "We sent a confirmation email." in flash_message["message"] # check that the email has been sent assert len(outbox) == 1 - assert ('Click on the following link to confirm your email' - in outbox[0].body) + assert "Click on the following link to confirm your email" in outbox[0].body # get the link to reset the password - reg_exp = re.search( - "http://localhost/confirm_email/.*", outbox[0].body - ) + reg_exp = re.search("http://localhost/confirm_email/.*", outbox[0].body) confirm_email_link = reg_exp.group() # remove the part with 'localhost' for the next query confirm_email_link = confirm_email_link[ - confirm_email_link.find('/confirm_email'): + confirm_email_link.find("/confirm_email") : ] # check the redirection assert rv.status_code == 302 - user = get_user_by_name(session, 'new_user_1') + user = get_user_by_name(session, "new_user_1") assert user is not None - assert user.access_level == 'not_confirmed' + assert user.access_level == "not_confirmed" # POST method of the email confirmation with client.application.app_context(): @@ -285,50 +310,51 @@ def test_sign_up_with_approval(client_session): # check the flash box to inform the user to wait for admin's # approval with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert ('An email has been sent to the RAMP administrator' in - flash_message['message']) + flash_message = dict(cs["_flashes"]) + assert ( + "An email has been sent to the RAMP administrator" + in flash_message["message"] + ) # check that we send an email to the administrator assert len(outbox) == 1 assert "Approve registration of new_user_1" in outbox[0].subject # ensure that we have the last changes session.commit() - user = get_user_by_name(session, 'new_user_1') - assert user.access_level == 'asked' + user = get_user_by_name(session, "new_user_1") + assert user.access_level == "asked" assert rv.status_code == 302 - assert rv.location == 'http://localhost/login' + assert rv.location == "http://localhost/login" # POST to check that we raise the right errors # resend the confirmation for a user which already confirmed rv = client.post(confirm_email_link) with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert ('Your email address already has been confirmed' - in flash_message['error']) + flash_message = dict(cs["_flashes"]) + assert "Your email address already has been confirmed" in flash_message["error"] assert rv.status_code == 302 - assert rv.location == 'http://localhost/' + assert rv.location == "http://localhost/" # check when the user was already approved - for status in ('user', 'admin'): - user = get_user_by_name(session, 'new_user_1') + for status in ("user", "admin"): + user = get_user_by_name(session, "new_user_1") user.access_level = status session.commit() rv = client.post(confirm_email_link) with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert 'Your account is already approved.' in flash_message['error'] + flash_message = dict(cs["_flashes"]) + assert "Your account is already approved." in flash_message["error"] assert rv.status_code == 302 - assert rv.location == 'http://localhost/login' + assert rv.location == "http://localhost/login" # delete the user in the middle session.delete(user) session.commit() rv = client.post(confirm_email_link) with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert 'You did not sign-up yet to RAMP.' in flash_message['error'] + flash_message = dict(cs["_flashes"]) + assert "You did not sign-up yet to RAMP." in flash_message["error"] assert rv.status_code == 302 - assert rv.location == 'http://localhost/sign_up' + assert rv.location == "http://localhost/sign_up" # access a token which does not exist - rv = client.post('/confirm_email/xxx') + rv = client.post("/confirm_email/xxx") assert rv.status_code == 404 @@ -336,34 +362,46 @@ def test_update_profile(client_session): client, session = client_session # try to change the profile without being logged-in - rv = client.get('/update_profile') + rv = client.get("/update_profile") assert rv.status_code == 302 - assert rv.location == 'http://localhost/login?next=%2Fupdate_profile' - rv = client.get('/update_profile', follow_redirects=True) + assert rv.location == "http://localhost/login?next=%2Fupdate_profile" + rv = client.get("/update_profile", follow_redirects=True) assert rv.status_code == 200 - with login_scope(client, 'test_user', 'test') as client: + with login_scope(client, "test_user", "test") as client: # GET function once logged-in - rv = client.get('/update_profile') + rv = client.get("/update_profile") assert rv.status_code == 200 - for attr in [b'Username', b'First name', b'Last name', b'Email', - b'User', b'Test', b'test.user@gmail.com']: + for attr in [ + b"Username", + b"First name", + b"Last name", + b"Email", + b"User", + b"Test", + b"test.user@gmail.com", + ]: assert attr in rv.data # POST function once logged-in - user_profile = {'lastname': 'XXX', 'firstname': 'YYY', - 'email': 'xxx@gmail.com'} - rv = client.post('/update_profile', data=user_profile) + user_profile = { + "lastname": "XXX", + "firstname": "YYY", + "email": "xxx@gmail.com", + } + rv = client.post("/update_profile", data=user_profile) assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' - user = get_user_by_name(session, 'test_user') - assert user.lastname == 'XXX' - assert user.firstname == 'YYY' - assert user.email == 'xxx@gmail.com' - user_profile = {'lastname': 'Test', 'firstname': 'User', - 'email': 'test.user@gmail.com'} - rv = client.post('/update_profile', data=user_profile, - follow_redirects=True) + assert rv.location == "http://localhost/problems" + user = get_user_by_name(session, "test_user") + assert user.lastname == "XXX" + assert user.firstname == "YYY" + assert user.email == "xxx@gmail.com" + user_profile = { + "lastname": "Test", + "firstname": "User", + "email": "test.user@gmail.com", + } + rv = client.post("/update_profile", data=user_profile, follow_redirects=True) assert rv.status_code == 200 @@ -372,64 +410,63 @@ def test_reset_password(client_session): client, session = client_session # GET method - rv = client.get('/reset_password') + rv = client.get("/reset_password") assert rv.status_code == 200 - assert b'If you are a registered user, we are going to send' in rv.data + assert b"If you are a registered user, we are going to send" in rv.data # POST method # check that we raise an error if the email does not exist - rv = client.post('/reset_password', data={'email': 'random@mail.com'}) + rv = client.post("/reset_password", data={"email": "random@mail.com"}) assert rv.status_code == 200 - assert b'You can sign-up instead.' in rv.data + assert b"You can sign-up instead." in rv.data # set a user to "asked" access level - user = get_user_by_name(session, 'test_user') - user.access_level = 'asked' + user = get_user_by_name(session, "test_user") + user.access_level = "asked" session.commit() - rv = client.post('/reset_password', data={'email': user.email}) + rv = client.post("/reset_password", data={"email": user.email}) assert rv.status_code == 200 - assert b'Your account has not been yet approved.' in rv.data + assert b"Your account has not been yet approved." in rv.data # set back the account to 'user' access level - user.access_level = 'user' + user.access_level = "user" session.commit() - rv = client.post('/reset_password', data={'email': user.email}) + rv = client.post("/reset_password", data={"email": user.email}) with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert flash_message['message'] == ('An email to reset your password has ' - 'been sent') + flash_message = dict(cs["_flashes"]) + assert flash_message["message"] == ( + "An email to reset your password has " "been sent" + ) assert rv.status_code == 302 - assert rv.location == 'http://localhost/login' + assert rv.location == "http://localhost/login" with client.application.app_context(): with mail.record_messages() as outbox: - rv = client.post('/reset_password', data={'email': user.email}) + rv = client.post("/reset_password", data={"email": user.email}) assert len(outbox) == 1 - assert 'click on the link to reset your password' in outbox[0].body + assert "click on the link to reset your password" in outbox[0].body # get the link to reset the password - reg_exp = re.search( - "http://localhost/reset/.*", outbox[0].body - ) + reg_exp = re.search("http://localhost/reset/.*", outbox[0].body) reset_password_link = reg_exp.group() # remove the part with 'localhost' for the next query reset_password_link = reset_password_link[ - reset_password_link.find('/reset'): + reset_password_link.find("/reset") : ] # check that we can reset the password using the previous link # GET method rv = client.get(reset_password_link) assert rv.status_code == 200 - assert b'Change my password' in rv.data + assert b"Change my password" in rv.data # POST method - new_password = 'new_password' - rv = client.post(reset_password_link, data={'password': new_password}) + new_password = "new_password" + rv = client.post(reset_password_link, data={"password": new_password}) assert rv.status_code == 302 - assert rv.location == 'http://localhost/login' + assert rv.location == "http://localhost/login" # make a commit to be sure that the update has been done session.commit() - user = get_user_by_name(session, 'test_user') + user = get_user_by_name(session, "test_user") assert check_password(new_password, user.hashed_password) @@ -438,30 +475,28 @@ def test_reset_token_error(client_session): client, session = client_session # POST method - new_password = 'new_password' - rv = client.post('/reset/xxx', data={'password': new_password}) + new_password = "new_password" + rv = client.post("/reset/xxx", data={"password": new_password}) assert rv.status_code == 404 # Get get the link to a real token but remove the user in between - user = get_user_by_name(session, 'test_user') + user = get_user_by_name(session, "test_user") with client.application.app_context(): with mail.record_messages() as outbox: - rv = client.post('/reset_password', data={'email': user.email}) + rv = client.post("/reset_password", data={"email": user.email}) assert len(outbox) == 1 - assert 'click on the link to reset your password' in outbox[0].body + assert "click on the link to reset your password" in outbox[0].body # get the link to reset the password - reg_exp = re.search( - "http://localhost/reset/.*", outbox[0].body - ) + reg_exp = re.search("http://localhost/reset/.*", outbox[0].body) reset_password_link = reg_exp.group() # remove the part with 'localhost' for the next query reset_password_link = reset_password_link[ - reset_password_link.find('/reset'): + reset_password_link.find("/reset") : ] - user = get_user_by_name(session, 'test_user') + user = get_user_by_name(session, "test_user") session.delete(user) session.commit() - new_password = 'new_password' - rv = client.post(reset_password_link, data={'password': new_password}) + new_password = "new_password" + rv = client.post(reset_password_link, data={"password": new_password}) assert rv.status_code == 404 diff --git a/ramp-frontend/ramp_frontend/tests/test_cli.py b/ramp-frontend/ramp_frontend/tests/test_cli.py index c65824604..bd76a64dd 100644 --- a/ramp-frontend/ramp_frontend/tests/test_cli.py +++ b/ramp-frontend/ramp_frontend/tests/test_cli.py @@ -24,18 +24,25 @@ def make_toy_db(database_connection): yield finally: shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_test_launch(make_toy_db): # pass environment to subprocess - cmd = ['python', '-m'] - cmd += ["ramp_frontend.cli", "test-launch", - "--config", database_config_template()] + cmd = ["python", "-m"] + cmd += [ + "ramp_frontend.cli", + "test-launch", + "--config", + database_config_template(), + ] proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - env=os.environ.copy()) + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=os.environ.copy(), + ) # wait for 5 seconds before to terminate the server time.sleep(5) proc.send_signal(signal.SIGINT) diff --git a/ramp-frontend/ramp_frontend/tests/test_general.py b/ramp-frontend/ramp_frontend/tests/test_general.py index bfe848bf3..53efd6a76 100644 --- a/ramp-frontend/ramp_frontend/tests/test_general.py +++ b/ramp-frontend/ramp_frontend/tests/test_general.py @@ -15,7 +15,7 @@ from ramp_frontend import create_app -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def client_session(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() @@ -23,81 +23,79 @@ def client_session(database_connection): deployment_dir = create_toy_db(database_config, ramp_config) flask_config = generate_flask_config(database_config) app = create_app(flask_config) - app.config['TESTING'] = True - app.config['WTF_CSRF_ENABLED'] = False - with session_scope(database_config['sqlalchemy']) as session: + app.config["TESTING"] = True + app.config["WTF_CSRF_ENABLED"] = False + with session_scope(database_config["sqlalchemy"]) as session: yield app.test_client(), session, app finally: shutil.rmtree(deployment_dir, ignore_errors=True) try: # In case of failure we should close the global flask engine from ramp_frontend import db as db_flask + db_flask.session.close() except RuntimeError: pass - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) def test_index(client_session): client, _, _ = client_session - rv = client.get('/') + rv = client.get("/") assert rv.status_code == 200 - assert (b'RAMP: collaborative data science challenges' in - rv.data) + assert b"RAMP: collaborative data science challenges" in rv.data def test_ramp(client_session): client, _, _ = client_session - rv = client.get('/description') + rv = client.get("/description") assert rv.status_code == 200 - assert (b'The RAMP software packages were developed by the' in - rv.data) + assert b"The RAMP software packages were developed by the" in rv.data def test_domain(client_session): client, session, _ = client_session - rv = client.get('/data_domains') + rv = client.get("/data_domains") assert rv.status_code == 200 - assert b'Scientific data domains' in rv.data - assert b'boston_housing' in rv.data - assert b'Boston housing price regression' in rv.data + assert b"Scientific data domains" in rv.data + assert b"boston_housing" in rv.data + assert b"Boston housing price regression" in rv.data def test_teaching(client_session): client, _, _ = client_session - rv = client.get('/teaching') + rv = client.get("/teaching") assert rv.status_code == 200 - assert b'RAMP challenges begin with an interesting supervised prediction' \ - in rv.data + assert b"RAMP challenges begin with an interesting supervised prediction" in rv.data def test_data_science_themes(client_session): client, _, _ = client_session - rv = client.get('/data_science_themes') + rv = client.get("/data_science_themes") assert rv.status_code == 200 - assert b'boston_housing_theme' in rv.data - assert b'iris_theme' in rv.data + assert b"boston_housing_theme" in rv.data + assert b"iris_theme" in rv.data def test_keywords(client_session): client, _, _ = client_session - rv = client.get('/keywords/boston_housing') + rv = client.get("/keywords/boston_housing") assert rv.status_code == 200 - assert b'Related problems' in rv.data - assert b'boston_housing' in rv.data - assert b'Boston housing price regression' in rv.data + assert b"Related problems" in rv.data + assert b"boston_housing" in rv.data + assert b"Boston housing price regression" in rv.data def test_privacy_policy(client_session, monkeypatch): client, _, app = client_session - rv = client.get('/privacy_policy') + rv = client.get("/privacy_policy") # By default privacy policy is not defined in the config. # i.e. config['privacy_policy'] = None assert rv.status_code == 404 - msg = 'Some HTML code' - monkeypatch.setitem(app.config, 'PRIVACY_POLICY_PAGE', msg) - rv = client.get('/privacy_policy') + msg = "Some HTML code" + monkeypatch.setitem(app.config, "PRIVACY_POLICY_PAGE", msg) + rv = client.get("/privacy_policy") assert rv.status_code == 200 - assert msg in rv.data.decode('utf-8') + assert msg in rv.data.decode("utf-8") diff --git a/ramp-frontend/ramp_frontend/tests/test_ramp.py b/ramp-frontend/ramp_frontend/tests/test_ramp.py index 79aa2d948..cc558a392 100644 --- a/ramp-frontend/ramp_frontend/tests/test_ramp.py +++ b/ramp-frontend/ramp_frontend/tests/test_ramp.py @@ -33,7 +33,7 @@ from ramp_frontend.testing import _fail_no_smtp_server -@pytest.fixture(scope='module') +@pytest.fixture(scope="module") def client_session(database_connection): database_config = read_config(database_config_template()) ramp_config = ramp_config_template() @@ -41,128 +41,141 @@ def client_session(database_connection): deployment_dir = create_toy_db(database_config, ramp_config) flask_config = generate_flask_config(database_config) app = create_app(flask_config) - app.config['TESTING'] = True - app.config['WTF_CSRF_ENABLED'] = False - with session_scope(database_config['sqlalchemy']) as session: + app.config["TESTING"] = True + app.config["WTF_CSRF_ENABLED"] = False + with session_scope(database_config["sqlalchemy"]) as session: yield app.test_client(), session finally: shutil.rmtree(deployment_dir, ignore_errors=True) try: # In case of failure we should close the global flask engine from ramp_frontend import db as db_flask + db_flask.session.close() except RuntimeError: pass - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) -@pytest.fixture(scope='function') +@pytest.fixture(scope="function") def makedrop_event(client_session): _, session = client_session - add_event(session, 'iris', 'iris_test_4event', 'iris_test_4event', - 'starting_kit', '/tmp/databoard_test/submissions', - is_public=True) + add_event( + session, + "iris", + "iris_test_4event", + "iris_test_4event", + "starting_kit", + "/tmp/databoard_test/submissions", + is_public=True, + ) yield - delete_event(session, 'iris_test_4event') + delete_event(session, "iris_test_4event") @pytest.mark.parametrize( "page", - ["/events/iris_test", - "/events/iris_test/sign_up", - "/events/iris_test/sandbox", - "problems/iris/ask_for_event", - "/credit/xxx", - "/event_plots/iris_test"] + [ + "/events/iris_test", + "/events/iris_test/sign_up", + "/events/iris_test/sandbox", + "problems/iris/ask_for_event", + "/credit/xxx", + "/event_plots/iris_test", + ], ) def test_check_login_required(client_session, page): client, _ = client_session rv = client.get(page) assert rv.status_code == 302 - assert 'http://localhost/login' in rv.location + assert "http://localhost/login" in rv.location rv = client.get(page, follow_redirects=True) assert rv.status_code == 200 @pytest.mark.parametrize( "page", - ["/events/xxx", - "/events/xxx/sign_up", - "/events/xxx/sandbox", - "/event_plots/xxx"] + [ + "/events/xxx", + "/events/xxx/sign_up", + "/events/xxx/sandbox", + "/event_plots/xxx", + ], ) def test_check_unknown_events(client_session, page): client, _ = client_session # trigger that the event does not exist - with login_scope(client, 'test_user', 'test') as client: + with login_scope(client, "test_user", "test") as client: rv = client.get(page) assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' + assert rv.location == "http://localhost/problems" with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert "no event named" in flash_message['message'] + flash_message = dict(cs["_flashes"]) + assert "no event named" in flash_message["message"] def test_problems(client_session): client, _ = client_session # GET: access the problems page without login - rv = client.get('/problems') + rv = client.get("/problems") assert rv.status_code == 200 - assert b'Hi User!' not in rv.data - assert b'participants' in rv.data - assert b'Iris classification' in rv.data - assert b'Boston housing price regression' in rv.data + assert b"Hi User!" not in rv.data + assert b"participants" in rv.data + assert b"Iris classification" in rv.data + assert b"Boston housing price regression" in rv.data # GET: access the problems when logged-in - with login_scope(client, 'test_user', 'test') as client: - rv = client.get('/problems') + with login_scope(client, "test_user", "test") as client: + rv = client.get("/problems") assert rv.status_code == 200 - assert b'Hi User!' in rv.data - assert b'participants' in rv.data - assert b'Iris classification' in rv.data - assert b'Boston housing price regression' in rv.data + assert b"Hi User!" in rv.data + assert b"participants" in rv.data + assert b"Iris classification" in rv.data + assert b"Boston housing price regression" in rv.data def test_problem(client_session): client, session = client_session # Access a problem that does not exist - rv = client.get('/problems/xxx') + rv = client.get("/problems/xxx") assert rv.status_code == 302 - assert rv.location == 'http://localhost/problems' + assert rv.location == "http://localhost/problems" with client.session_transaction() as cs: - flash_message = dict(cs['_flashes']) - assert flash_message['message'] == "Problem xxx does not exist" - rv = client.get('/problems/xxx', follow_redirects=True) + flash_message = dict(cs["_flashes"]) + assert flash_message["message"] == "Problem xxx does not exist" + rv = client.get("/problems/xxx", follow_redirects=True) assert rv.status_code == 200 # GET: looking at the problem without being logged-in - rv = client.get('problems/iris') + rv = client.get("problems/iris") assert rv.status_code == 200 - assert b'Iris classification' in rv.data - assert b'Registered events' in rv.data + assert b"Iris classification" in rv.data + assert b"Registered events" in rv.data # GET: looking at the problem being logged-in - with login_scope(client, 'test_user', 'test') as client: - rv = client.get('problems/iris') + with login_scope(client, "test_user", "test") as client: + rv = client.get("problems/iris") assert rv.status_code == 200 - assert b'Iris classification' in rv.data - assert b'Registered events' in rv.data + assert b"Iris classification" in rv.data + assert b"Registered events" in rv.data @pytest.mark.parametrize( "event_name, correct", - [("iri_aaa", False), - ("irisaaa", False), - ("test_iris", False), - ("iris_", True), - ("iris_aaa_aaa_test", True), - ("iris", False), - ("iris_t", True)] + [ + ("iri_aaa", False), + ("irisaaa", False), + ("test_iris", False), + ("iris_", True), + ("iris_aaa_aaa_test", True), + ("iris", False), + ("iris_t", True), + ], ) def test_event_name_correct(client_session, event_name, correct): client, session = client_session @@ -170,82 +183,128 @@ def test_event_name_correct(client_session, event_name, correct): err_msg = "The event name should start with the problem name" with pytest.raises(ValueError, match=err_msg): add_event( - session, 'iris', event_name, 'new_event', 'starting_kit', - '/tmp/databoard_test/submissions', is_public=True + session, + "iris", + event_name, + "new_event", + "starting_kit", + "/tmp/databoard_test/submissions", + is_public=True, ) else: - assert add_event(session, 'iris', event_name, 'new_event', - 'starting_kit', '/tmp/databoard_test/submissions', - is_public=True) + assert add_event( + session, + "iris", + event_name, + "new_event", + "starting_kit", + "/tmp/databoard_test/submissions", + is_public=True, + ) def test_user_event_status(client_session): client, session = client_session - add_user(session, 'new_user', 'new_user', 'new_user', - 'new_user', 'new_user', access_level='user') - add_event(session, 'iris', 'iris_new_event', 'new_event', 'starting_kit', - '/tmp/databoard_test/submissions', is_public=True) + add_user( + session, + "new_user", + "new_user", + "new_user", + "new_user", + "new_user", + access_level="user", + ) + add_event( + session, + "iris", + "iris_new_event", + "new_event", + "starting_kit", + "/tmp/databoard_test/submissions", + is_public=True, + ) # user signed up, not approved for the event - ask_sign_up_team(session, 'iris_new_event', 'new_user') - with login_scope(client, 'new_user', 'new_user') as client: - rv = client.get('/problems') + ask_sign_up_team(session, "iris_new_event", "new_user") + with login_scope(client, "new_user", "new_user") as client: + rv = client.get("/problems") assert rv.status_code == 200 - assert b'user-waiting' in rv.data - assert b'user-signed' not in rv.data + assert b"user-waiting" in rv.data + assert b"user-signed" not in rv.data # user signed up and approved for the event - sign_up_team(session, 'iris_new_event', 'new_user') - with login_scope(client, 'new_user', 'new_user') as client: - rv = client.get('/problems') + sign_up_team(session, "iris_new_event", "new_user") + with login_scope(client, "new_user", "new_user") as client: + rv = client.get("/problems") assert rv.status_code == 200 - assert b'user-signed' in rv.data - assert b'user-waiting' not in rv.data + assert b"user-signed" in rv.data + assert b"user-waiting" not in rv.data NOW = datetime.datetime.now() testtimestamps = [ - (NOW.replace(year=NOW.year+1), NOW.replace(year=NOW.year+2), - NOW.replace(year=NOW.year+3), b'event-close'), - (NOW.replace(year=NOW.year-1), NOW.replace(year=NOW.year+1), - NOW.replace(year=NOW.year+2), b'event-comp'), - (NOW.replace(year=NOW.year-2), NOW.replace(year=NOW.year-1), - NOW.replace(year=NOW.year+1), b'event-collab'), - (NOW.replace(year=NOW.year-3), NOW.replace(year=NOW.year-2), - NOW.replace(year=NOW.year-1), b'event-close'), + ( + NOW.replace(year=NOW.year + 1), + NOW.replace(year=NOW.year + 2), + NOW.replace(year=NOW.year + 3), + b"event-close", + ), + ( + NOW.replace(year=NOW.year - 1), + NOW.replace(year=NOW.year + 1), + NOW.replace(year=NOW.year + 2), + b"event-comp", + ), + ( + NOW.replace(year=NOW.year - 2), + NOW.replace(year=NOW.year - 1), + NOW.replace(year=NOW.year + 1), + b"event-collab", + ), + ( + NOW.replace(year=NOW.year - 3), + NOW.replace(year=NOW.year - 2), + NOW.replace(year=NOW.year - 1), + b"event-close", + ), ] @pytest.mark.parametrize( "opening_date,public_date,closing_date,expected", testtimestamps ) -def test_event_status(client_session, makedrop_event, - opening_date, public_date, - closing_date, expected): +def test_event_status( + client_session, + makedrop_event, + opening_date, + public_date, + closing_date, + expected, +): # checks if the event status is displayed correctly client, session = client_session # change the datetime stamps for the event - event = get_event(session, 'iris_test_4event') + event = get_event(session, "iris_test_4event") event.opening_timestamp = opening_date event.public_opening_timestamp = public_date event.closing_timestamp = closing_date session.commit() # GET: access the problems page without login - rv = client.get('/problems') + rv = client.get("/problems") assert rv.status_code == 200 - event_idx = rv.data.index(b'iris_test_4event') - event_class_idx = rv.data[:event_idx].rfind(b'") @@ -144,21 +142,21 @@ def manage_users(): def approve_single_user(user_name): """Approve a single user. This is usually used to approve user through email.""" - if not flask_login.current_user.access_level == 'admin': + if not flask_login.current_user.access_level == "admin": return redirect_to_user( - 'Sorry {}, you do not have admin rights' - .format(flask_login.current_user.firstname), - is_error=True + "Sorry {}, you do not have admin rights".format( + flask_login.current_user.firstname + ), + is_error=True, ) user = User.query.filter_by(name=user_name).one_or_none() if not user: - return redirect_to_user( - 'No user {}'.format(user_name), is_error=True - ) + return redirect_to_user("No user {}".format(user_name), is_error=True) approve_user(db.session, user.name) return redirect_to_user( - '{} is signed up'.format(user), is_error=False, - category='Successful sign-up' + "{} is signed up".format(user), + is_error=False, + category="Successful sign-up", ) @@ -180,26 +178,35 @@ def approve_sign_up_for_event(event_name, user_name): event = get_event(db.session, event_name) user = User.query.filter_by(name=user_name).one_or_none() if not is_admin(db.session, event_name, flask_login.current_user.name): - return redirect_to_user('Sorry {}, you do not have admin rights' - .format(flask_login.current_user.firstname), - is_error=True) + return redirect_to_user( + "Sorry {}, you do not have admin rights".format( + flask_login.current_user.firstname + ), + is_error=True, + ) if not event or not user: - return redirect_to_user('No event {} or no user {}' - .format(event_name, user_name), is_error=True) + return redirect_to_user( + "No event {} or no user {}".format(event_name, user_name), + is_error=True, + ) sign_up_team(db.session, event.name, user.name) - subject = ('Signed up for the RAMP event {}' - .format(event.name)) - body = ('{}, you have been registered to the RAMP event {}. ' - 'You can now proceed to your sandbox and make submissions.' - '\nHave fun!!!'.format(user.name, event.name)) + subject = "Signed up for the RAMP event {}".format(event.name) + body = ( + "{}, you have been registered to the RAMP event {}. " + "You can now proceed to your sandbox and make submissions." + "\nHave fun!!!".format(user.name, event.name) + ) send_mail(to=user.email, subject=subject, body=body) - return redirect_to_user('{} is signed up for {}.'.format(user, event), - is_error=False, category='Successful sign-up') + return redirect_to_user( + "{} is signed up for {}.".format(user, event), + is_error=False, + category="Successful sign-up", + ) -@mod.route("/events//update", methods=['GET', 'POST']) +@mod.route("/events//update", methods=["GET", "POST"]) @flask_login.login_required def update_event(event_name): """Update the parameters of an event. @@ -211,19 +218,21 @@ def update_event(event_name): """ if not is_admin(db.session, event_name, flask_login.current_user.name): return redirect_to_user( - 'Sorry {}, you do not have admin rights' - .format(flask_login.current_user.firstname), - is_error=True + "Sorry {}, you do not have admin rights".format( + flask_login.current_user.firstname + ), + is_error=True, ) event = get_event(db.session, event_name) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): return redirect_to_user( - '{}: no event named "{}"' - .format(flask_login.current_user.firstname, event_name) + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) ) - logger.info('{} is updating event {}' - .format(flask_login.current_user.name, event.name)) + logger.info( + "{} is updating event {}".format(flask_login.current_user.name, event.name) + ) admin = is_admin(db.session, event_name, flask_login.current_user.name) # We assume here that event name has the syntax _ @@ -253,9 +262,10 @@ def update_event(event_name): event.is_controled_signup = form.is_controled_signup.data event.is_competitive = form.is_competitive.data event.min_duration_between_submissions = ( - form.min_duration_between_submissions_hour.data * 3600 + - form.min_duration_between_submissions_minute.data * 60 + - form.min_duration_between_submissions_second.data) + form.min_duration_between_submissions_hour.data * 3600 + + form.min_duration_between_submissions_minute.data * 60 + + form.min_duration_between_submissions_second.data + ) event.opening_timestamp = form.opening_timestamp.data event.closing_timestamp = form.closing_timestamp.data event.public_opening_timestamp = form.public_opening_timestamp.data @@ -263,10 +273,10 @@ def update_event(event_name): except IntegrityError as e: db.session.rollback() - message = '' + message = "" existing_event = get_event(db.session, event.name) if existing_event is not None: - message += 'event name is already in use' + message += "event name is already in use" # # try: # # User.query.filter_by(email=email).one() # # if len(message) > 0: @@ -276,24 +286,22 @@ def update_event(event_name): # pass if message: e = NameClashError(message) - flash('{}'.format(e), category='Update event error') - return redirect(url_for('update_event', event_name=event.name)) + flash("{}".format(e), category="Update event error") + return redirect(url_for("update_event", event_name=event.name)) - return redirect(url_for('ramp.problems')) + return redirect(url_for("ramp.problems")) - approved = is_user_signed_up( - db.session, event_name, flask_login.current_user.name - ) + approved = is_user_signed_up(db.session, event_name, flask_login.current_user.name) asked = is_user_sign_up_requested( db.session, event_name, flask_login.current_user.name ) return render_template( - 'update_event.html', + "update_event.html", form=form, event=event, admin=admin, asked=asked, - approved=approved + approved=approved, ) @@ -301,19 +309,20 @@ def update_event(event_name): @flask_login.login_required def user_interactions(): """Show the user interactions recorded on the website.""" - if flask_login.current_user.access_level != 'admin': + if flask_login.current_user.access_level != "admin": return redirect_to_user( - 'Sorry {}, you do not have admin rights' - .format(flask_login.current_user.firstname), - is_error=True + "Sorry {}, you do not have admin rights".format( + flask_login.current_user.firstname + ), + is_error=True, ) user_interactions_html = get_user_interactions_by_name( - db.session, output_format='html' + db.session, output_format="html" ) return render_template( - 'user_interactions.html', - user_interactions_title='User interactions', - user_interactions=user_interactions_html + "user_interactions.html", + user_interactions_title="User interactions", + user_interactions=user_interactions_html, ) @@ -329,50 +338,51 @@ def dashboard_submissions(event_name): """ if not is_admin(db.session, event_name, flask_login.current_user.name): return redirect_to_user( - 'Sorry {}, you do not have admin rights' - .format(flask_login.current_user.firstname), - is_error=True + "Sorry {}, you do not have admin rights".format( + flask_login.current_user.firstname + ), + is_error=True, ) event = get_event(db.session, event_name) # Get dates and number of submissions - submissions = \ - (Submission.query - .filter(Event.name == event.name) - .filter(Event.id == EventTeam.event_id) - .filter(EventTeam.id == Submission.event_team_id) - .order_by(Submission.submission_timestamp) - .all()) + submissions = ( + Submission.query.filter(Event.name == event.name) + .filter(Event.id == EventTeam.event_id) + .filter(EventTeam.id == Submission.event_team_id) + .order_by(Submission.submission_timestamp) + .all() + ) submissions = [sub for sub in submissions if sub.is_not_sandbox] timestamp_submissions = [ - sub.submission_timestamp.strftime('%Y-%m-%d %H:%M:%S') - for sub in submissions] + sub.submission_timestamp.strftime("%Y-%m-%d %H:%M:%S") for sub in submissions + ] name_submissions = [sub.name for sub in submissions] cumulated_submissions = list(range(1, 1 + len(submissions))) training_sec = [ - ( - sub.training_timestamp - sub.submission_timestamp - ).total_seconds() / 60. - if sub.training_timestamp is not None else 0 + (sub.training_timestamp - sub.submission_timestamp).total_seconds() / 60.0 + if sub.training_timestamp is not None + else 0 for sub in submissions ] - dashboard_kwargs = {'event': event, - 'timestamp_submissions': timestamp_submissions, - 'training_sec': training_sec, - 'cumulated_submissions': cumulated_submissions, - 'name_submissions': name_submissions} + dashboard_kwargs = { + "event": event, + "timestamp_submissions": timestamp_submissions, + "training_sec": training_sec, + "cumulated_submissions": cumulated_submissions, + "name_submissions": name_submissions, + } failed_leaderboard_html = event.failed_leaderboard_html new_leaderboard_html = event.new_leaderboard_html - approved = is_user_signed_up( - db.session, event_name, flask_login.current_user.name - ) + approved = is_user_signed_up(db.session, event_name, flask_login.current_user.name) asked = is_user_sign_up_requested( db.session, event_name, flask_login.current_user.name ) return render_template( - 'dashboard_submissions.html', + "dashboard_submissions.html", failed_leaderboard=failed_leaderboard_html, new_leaderboard=new_leaderboard_html, admin=True, approved=approved, asked=asked, - **dashboard_kwargs) + **dashboard_kwargs, + ) diff --git a/ramp-frontend/ramp_frontend/views/auth.py b/ramp-frontend/ramp_frontend/views/auth.py index aee64f141..29aee9c4f 100644 --- a/ramp-frontend/ramp_frontend/views/auth.py +++ b/ramp-frontend/ramp_frontend/views/auth.py @@ -40,8 +40,8 @@ from ..utils import body_formatter_user from ..utils import send_mail -logger = logging.getLogger('RAMP-FRONTEND') -mod = Blueprint('auth', __name__) +logger = logging.getLogger("RAMP-FRONTEND") +mod = Blueprint("auth", __name__) ts = URLSafeTimedSerializer(app.config["SECRET_KEY"]) @@ -59,48 +59,45 @@ def load_user(id): return User.query.get(id) -@mod.route("/login", methods=['GET', 'POST']) +@mod.route("/login", methods=["GET", "POST"]) def login(): """Login request.""" - if app.config['TRACK_USER_INTERACTION']: - add_user_interaction(db.session, interaction='landing') + if app.config["TRACK_USER_INTERACTION"]: + add_user_interaction(db.session, interaction="landing") if flask_login.current_user.is_authenticated: - logger.info('User already logged-in') - session['logged_in'] = True - return redirect(url_for('ramp.problems')) + logger.info("User already logged-in") + session["logged_in"] = True + return redirect(url_for("ramp.problems")) form = LoginForm() if form.validate_on_submit(): - user = get_user_by_name_or_email(db.session, - name=form.user_name.data) + user = get_user_by_name_or_email(db.session, name=form.user_name.data) if user is None: msg = 'User "{}" does not exist'.format(form.user_name.data) flash(msg) logger.info(msg) - return redirect(url_for('auth.login')) - if not check_password(form.password.data, - user.hashed_password): - msg = 'Wrong password' + return redirect(url_for("auth.login")) + if not check_password(form.password.data, user.hashed_password): + msg = "Wrong password" flash(msg) logger.info(msg) - return redirect(url_for('auth.login')) + return redirect(url_for("auth.login")) flask_login.login_user(user, remember=True) - session['logged_in'] = True + session["logged_in"] = True user.is_authenticated = True db.session.commit() - logger.info('User "{}" is logged in' - .format(flask_login.current_user.name)) - if app.config['TRACK_USER_INTERACTION']: + logger.info('User "{}" is logged in'.format(flask_login.current_user.name)) + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( - db.session, interaction='login', user=flask_login.current_user + db.session, interaction="login", user=flask_login.current_user ) - next_ = request.args.get('next') + next_ = request.args.get("next") if next_ is None: - next_ = url_for('ramp.problems') + next_ = url_for("ramp.problems") return redirect(next_) - return render_template('login.html', form=form) + return render_template("login.html", form=form) @mod.route("/logout") @@ -108,23 +105,23 @@ def login(): def logout(): """Logout request.""" user = flask_login.current_user - if app.config['TRACK_USER_INTERACTION']: - add_user_interaction(db.session, interaction='logout', user=user) - session['logged_in'] = False + if app.config["TRACK_USER_INTERACTION"]: + add_user_interaction(db.session, interaction="logout", user=user) + session["logged_in"] = False user.is_authenticated = False db.session.commit() - logger.info('{} is logged out'.format(user)) + logger.info("{} is logged out".format(user)) flask_login.logout_user() - return redirect(url_for('auth.login')) + return redirect(url_for("auth.login")) -@mod.route("/sign_up", methods=['GET', 'POST']) +@mod.route("/sign_up", methods=["GET", "POST"]) def sign_up(): """Sign-up request.""" if flask_login.current_user.is_authenticated: - session['logged_in'] = True - return redirect(url_for('ramp.problems')) + session["logged_in"] = True + return redirect(url_for("ramp.problems")) form = UserCreateProfileForm() if form.validate_on_submit(): @@ -144,38 +141,35 @@ def sign_up(): website_url=form.website_url.data, bio=form.bio.data, is_want_news=form.is_want_news.data, - access_level='not_confirmed' + access_level="not_confirmed", ) except NameClashError as e: flash(str(e)) logger.info(str(e)) - return redirect(url_for('auth.sign_up')) + return redirect(url_for("auth.sign_up")) # send an email to the participant such that he can confirm his email token = ts.dumps(user.email) - recover_url = url_for( - 'auth.user_confirm_email', token=token, _external=True - ) + recover_url = url_for("auth.user_confirm_email", token=token, _external=True) subject = "Confirm your email for signing-up to RAMP" - body = ('Hi {}, \n\n Click on the following link to confirm your email' - ' address and finalize your sign-up to RAMP.\n\n Note that ' - 'your account still needs to be approved by a RAMP ' - 'administrator.\n\n' - .format(user.firstname)) + body = ( + "Hi {}, \n\n Click on the following link to confirm your email" + " address and finalize your sign-up to RAMP.\n\n Note that " + "your account still needs to be approved by a RAMP " + "administrator.\n\n".format(user.firstname) + ) body += recover_url - body += '\n\nSee you on the RAMP website!' + body += "\n\nSee you on the RAMP website!" send_mail(user.email, subject, body) - logger.info( - '{} has signed-up to RAMP'.format(user.name) - ) + logger.info("{} has signed-up to RAMP".format(user.name)) flash( "We sent a confirmation email. Go read your email and click on " "the confirmation link" ) - return redirect(url_for('auth.login')) - return render_template('sign_up.html', form=form) + return redirect(url_for("auth.login")) + return render_template("sign_up.html", form=form) -@mod.route("/update_profile", methods=['GET', 'POST']) +@mod.route("/update_profile", methods=["GET", "POST"]) @flask_login.login_required def update_profile(): """User profile update.""" @@ -194,10 +188,10 @@ def update_profile(): google_url=form.google_url.data, github_url=form.github_url.data, website_url=form.website_url.data, - is_want_news=form.is_want_news.data + is_want_news=form.is_want_news.data, ) # send_register_request_mail(user) - return redirect(url_for('ramp.problems')) + return redirect(url_for("ramp.problems")) form.lastname.data = flask_login.current_user.lastname form.firstname.data = flask_login.current_user.firstname form.email.data = flask_login.current_user.email @@ -209,16 +203,16 @@ def update_profile(): form.website_url.data = flask_login.current_user.website_url form.bio.data = flask_login.current_user.bio form.is_want_news.data = flask_login.current_user.is_want_news - return render_template('update_profile.html', form=form) + return render_template("update_profile.html", form=form) -@mod.route("/delete_profile", methods=['GET']) +@mod.route("/delete_profile", methods=["GET"]) @flask_login.login_required def delete_profile(): user = flask_login.current_user user_id = user.id user_name = user.name - session['logged_in'] = False + session["logged_in"] = False user.name = f"deleted_{user_id}" user.is_authenticated = False user.hashed_password = uuid.uuid4().hex @@ -235,47 +229,48 @@ def delete_profile(): lastname="deleted", firstname="deleted", ) - logger.info(f'User {user_name} profile is deleted.') + logger.info(f"User {user_name} profile is deleted.") flask_login.logout_user() - return redirect('/') + return redirect("/") -@mod.route('/reset_password', methods=["GET", "POST"]) +@mod.route("/reset_password", methods=["GET", "POST"]) def reset_password(): """Reset password of a RAMP user.""" form = EmailForm() - error = '' + error = "" if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).one_or_none() - if user and user.access_level != 'asked': + if user and user.access_level != "asked": token = ts.dumps(user.email) - recover_url = url_for( - 'auth.reset_with_token', token=token, _external=True - ) + recover_url = url_for("auth.reset_with_token", token=token, _external=True) subject = "Password reset requested - RAMP website" - body = ('Hi {}, \n\nclick on the link to reset your password:\n' - .format(user.firstname)) + body = "Hi {}, \n\nclick on the link to reset your password:\n".format( + user.firstname + ) body += recover_url - body += '\n\nSee you on the RAMP website!' + body += "\n\nSee you on the RAMP website!" send_mail(user.email, subject, body) - logger.info( - 'Password reset requested for user {}'.format(user.name) - ) + logger.info("Password reset requested for user {}".format(user.name)) logger.info(recover_url) - flash('An email to reset your password has been sent') - return redirect(url_for('auth.login')) + flash("An email to reset your password has been sent") + return redirect(url_for("auth.login")) elif user is None: - error = ('The email address is not linked to any user. You can ' - 'sign-up instead.') + error = ( + "The email address is not linked to any user. You can " + "sign-up instead." + ) else: - error = ('Your account has not been yet approved. You cannot ' - 'change the password already.') - return render_template('reset_password.html', form=form, error=error) + error = ( + "Your account has not been yet approved. You cannot " + "change the password already." + ) + return render_template("reset_password.html", form=form, error=error) -@mod.route('/reset/', methods=["GET", "POST"]) +@mod.route("/reset/", methods=["GET", "POST"]) def reset_with_token(token): """Reset password by passing a token (email). @@ -294,20 +289,20 @@ def reset_with_token(token): if form.validate_on_submit(): user = User.query.filter_by(email=email).one_or_none() if user is None: - logger.error('The error was deleted before resetting his/her ' - 'password') + logger.error("The error was deleted before resetting his/her " "password") abort(404) - (User.query.filter_by(email=email) - .update({ - "hashed_password": - hash_password(form.password.data).decode()})) + ( + User.query.filter_by(email=email).update( + {"hashed_password": hash_password(form.password.data).decode()} + ) + ) db.session.commit() - return redirect(url_for('auth.login')) + return redirect(url_for("auth.login")) - return render_template('reset_with_token.html', form=form, token=token) + return render_template("reset_with_token.html", form=form, token=token) -@mod.route('/confirm_email/', methods=["GET", "POST"]) +@mod.route("/confirm_email/", methods=["GET", "POST"]) def user_confirm_email(token): """Confirm a user account using his email address and a token to approve. @@ -325,37 +320,38 @@ def user_confirm_email(token): user = User.query.filter_by(email=email).one_or_none() if user is None: flash( - 'You did not sign-up yet to RAMP. Please sign-up first.', - category='error' + "You did not sign-up yet to RAMP. Please sign-up first.", + category="error", ) - return redirect(url_for('auth.sign_up')) - elif user.access_level in ('user', 'admin'): + return redirect(url_for("auth.sign_up")) + elif user.access_level in ("user", "admin"): flash( "Your account is already approved. You don't need to confirm your " - "email address", category='error' + "email address", + category="error", ) - return redirect(url_for('auth.login')) - elif user.access_level == 'asked': + return redirect(url_for("auth.login")) + elif user.access_level == "asked": flash( "Your email address already has been confirmed. You need to wait " - "for an approval from a RAMP administrator", category='error' + "for an approval from a RAMP administrator", + category="error", ) - return redirect(url_for('general.index')) - User.query.filter_by(email=email).update({'access_level': 'asked'}) + return redirect(url_for("general.index")) + User.query.filter_by(email=email).update({"access_level": "asked"}) db.session.commit() - admin_users = User.query.filter_by(access_level='admin') + admin_users = User.query.filter_by(access_level="admin") for admin in admin_users: - subject = 'Approve registration of {}'.format( - user.name - ) + subject = "Approve registration of {}".format(user.name) body = body_formatter_user(user) - url_approve = ('http://{}/sign_up/{}' - .format(app.config['DOMAIN_NAME'], user.name)) - body += 'Click on the link to approve the registration ' - body += 'of this user: {}'.format(url_approve) + url_approve = "http://{}/sign_up/{}".format( + app.config["DOMAIN_NAME"], user.name + ) + body += "Click on the link to approve the registration " + body += "of this user: {}".format(url_approve) send_mail(admin.email, subject, body) flash( "An email has been sent to the RAMP administrator(s) who will " "approve your account" ) - return redirect(url_for('auth.login')) + return redirect(url_for("auth.login")) diff --git a/ramp-frontend/ramp_frontend/views/general.py b/ramp-frontend/ramp_frontend/views/general.py index 37bb40332..e9a606684 100644 --- a/ramp-frontend/ramp_frontend/views/general.py +++ b/ramp-frontend/ramp_frontend/views/general.py @@ -11,31 +11,31 @@ from .redirect import redirect_to_user from .._version import __version__ -mod = Blueprint('general', __name__) +mod = Blueprint("general", __name__) -@mod.route('/') +@mod.route("/") def index(): """Default landing page.""" - img_ext = ('.png', '.jpg', '.jpeg', '.gif', '.svg') + img_ext = (".png", ".jpg", ".jpeg", ".gif", ".svg") current_dir = os.path.dirname(__file__) img_folder = os.path.join(current_dir, "..", "static", "img", "powered_by") context = {} if os.path.isdir(img_folder): - images = [f for f in os.listdir(img_folder) - if f.endswith(img_ext)] + images = [f for f in os.listdir(img_folder) if f.endswith(img_ext)] context["images"] = images context["version"] = __version__ - return render_template('index.html', **context) + return render_template("index.html", **context) @mod.route("/description") def ramp(): """RAMP description request.""" - user = (flask_login.current_user - if flask_login.current_user.is_authenticated else None) - admin = user.access_level == 'admin' if user is not None else False - return render_template('ramp_description.html', admin=admin) + user = ( + flask_login.current_user if flask_login.current_user.is_authenticated else None + ) + admin = user.access_level == "admin" if user is not None else False + return render_template("ramp_description.html", admin=admin) @mod.route("/data_domains") @@ -44,23 +44,24 @@ def data_domains(): problems.""" current_keywords = Keyword.query.order_by(Keyword.name) current_problems = Problem.query.order_by(Problem.id) - return render_template('data_domains.html', - keywords=current_keywords, - problems=current_problems) + return render_template( + "data_domains.html", + keywords=current_keywords, + problems=current_problems, + ) @mod.route("/teaching") def teaching(): """Page related to RAMP offers for teaching classes.""" - return render_template('teaching.html') + return render_template("teaching.html") @mod.route("/data_science_themes") def data_science_themes(): """Page reviewing problems organized by ML themes.""" current_keywords = Keyword.query.order_by(Keyword.name) - return render_template('data_science_themes.html', - keywords=current_keywords) + return render_template("data_science_themes.html", keywords=current_keywords) @mod.route("/keywords/") @@ -68,14 +69,15 @@ def keywords(keyword_name): """Page which give details about a keyword.""" keyword = Keyword.query.filter_by(name=keyword_name).one_or_none() if keyword: - return render_template('keyword.html', keyword=keyword) - return redirect_to_user('Keyword {} does not exist.' - .format(keyword_name), is_error=True) + return render_template("keyword.html", keyword=keyword) + return redirect_to_user( + "Keyword {} does not exist.".format(keyword_name), is_error=True + ) @mod.route("/privacy_policy") def privacy_policy(): - if not app.config['PRIVACY_POLICY_PAGE']: + if not app.config["PRIVACY_POLICY_PAGE"]: flask.abort(404) - return render_template('privacy_policy.html') + return render_template("privacy_policy.html") diff --git a/ramp-frontend/ramp_frontend/views/leaderboard.py b/ramp-frontend/ramp_frontend/views/leaderboard.py index e3cba2e43..d35f15371 100644 --- a/ramp-frontend/ramp_frontend/views/leaderboard.py +++ b/ramp-frontend/ramp_frontend/views/leaderboard.py @@ -23,8 +23,8 @@ from .redirect import redirect_to_user -mod = Blueprint('leaderboard', __name__) -logger = logging.getLogger('RAMP-FRONTEND') +mod = Blueprint("leaderboard", __name__) +logger = logging.getLogger("RAMP-FRONTEND") SORTING_COLUMN_INDEX = 2 @@ -40,45 +40,50 @@ def my_submissions(event_name): The name of the event. """ event = get_event(db.session, event_name) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): return redirect_to_user( - '{}: no event named "{}"' - .format(flask_login.current_user.firstname, event_name) + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) ) - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( - db.session, interaction='looking at my_submissions', - user=flask_login.current_user, event=event + db.session, + interaction="looking at my_submissions", + user=flask_login.current_user, + event=event, + ) + if not is_accessible_code(db.session, event_name, flask_login.current_user.name): + error_str = ( + "No access to my submissions for event {}. If you have " + "already signed up, please wait for approval.".format(event.name) ) - if not is_accessible_code(db.session, event_name, - flask_login.current_user.name): - error_str = ('No access to my submissions for event {}. If you have ' - 'already signed up, please wait for approval.' - .format(event.name)) return redirect_to_user(error_str) # Doesn't work if team mergers are allowed - event_team = get_event_team_by_name(db.session, event_name, - flask_login.current_user.name) + event_team = get_event_team_by_name( + db.session, event_name, flask_login.current_user.name + ) leaderboard_html = event_team.leaderboard_html failed_leaderboard_html = event_team.failed_leaderboard_html new_leaderboard_html = event_team.new_leaderboard_html admin = is_admin(db.session, event_name, flask_login.current_user.name) if event.official_score_type.is_lower_the_better: - sorting_direction = 'asc' + sorting_direction = "asc" else: - sorting_direction = 'desc' + sorting_direction = "desc" - return render_template('leaderboard.html', - leaderboard_title='Trained submissions', - leaderboard=leaderboard_html, - failed_leaderboard=failed_leaderboard_html, - new_leaderboard=new_leaderboard_html, - sorting_column_index=SORTING_COLUMN_INDEX, - sorting_direction=sorting_direction, - event=event, - admin=admin) + return render_template( + "leaderboard.html", + leaderboard_title="Trained submissions", + leaderboard=leaderboard_html, + failed_leaderboard=failed_leaderboard_html, + new_leaderboard=new_leaderboard_html, + sorting_column_index=SORTING_COLUMN_INDEX, + sorting_direction=sorting_direction, + event=event, + admin=admin, + ) @mod.route("/events//leaderboard") @@ -92,51 +97,49 @@ def leaderboard(event_name): The name of the event. """ event = get_event(db.session, event_name) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): return redirect_to_user( - '{}: no event named "{}"' - .format(flask_login.current_user.firstname, event_name)) - if app.config['TRACK_USER_INTERACTION']: + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) + ) + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='looking at leaderboard', + interaction="looking at leaderboard", user=flask_login.current_user, - event=event + event=event, ) - if is_accessible_leaderboard(db.session, event_name, - flask_login.current_user.name): + if is_accessible_leaderboard(db.session, event_name, flask_login.current_user.name): leaderboard_html = event.public_leaderboard_html_with_links else: leaderboard_html = event.public_leaderboard_html_no_links if event.official_score_type.is_lower_the_better: - sorting_direction = 'asc' + sorting_direction = "asc" else: - sorting_direction = 'desc' + sorting_direction = "desc" leaderboard_kwargs = dict( leaderboard=leaderboard_html, - leaderboard_title='Leaderboard', + leaderboard_title="Leaderboard", sorting_column_index=SORTING_COLUMN_INDEX, sorting_direction=sorting_direction, - event=event + event=event, ) if is_admin(db.session, event_name, flask_login.current_user.name): failed_leaderboard_html = event.failed_leaderboard_html new_leaderboard_html = event.new_leaderboard_html template = render_template( - 'leaderboard.html', + "leaderboard.html", failed_leaderboard=failed_leaderboard_html, new_leaderboard=new_leaderboard_html, admin=True, - **leaderboard_kwargs + **leaderboard_kwargs, ) else: - template = render_template( - 'leaderboard.html', **leaderboard_kwargs - ) + template = render_template("leaderboard.html", **leaderboard_kwargs) return template @@ -152,37 +155,35 @@ def competition_leaderboard(event_name): The event name. """ event = get_event(db.session, event_name) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): return redirect_to_user( - '{}: no event named "{}"' - .format(flask_login.current_user.firstname, event_name) + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) ) - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='looking at leaderboard', + interaction="looking at leaderboard", user=flask_login.current_user, - event=event + event=event, ) admin = is_admin(db.session, event_name, flask_login.current_user.name) - approved = is_user_signed_up( - db.session, event_name, flask_login.current_user.name - ) + approved = is_user_signed_up(db.session, event_name, flask_login.current_user.name) asked = approved leaderboard_html = event.public_competition_leaderboard_html leaderboard_kwargs = dict( leaderboard=leaderboard_html, - leaderboard_title='Leaderboard', + leaderboard_title="Leaderboard", sorting_column_index=0, - sorting_direction='asc', + sorting_direction="asc", event=event, admin=admin, asked=asked, - approved=approved + approved=approved, ) - return render_template('leaderboard.html', **leaderboard_kwargs) + return render_template("leaderboard.html", **leaderboard_kwargs) @mod.route("/events//private_leaderboard") @@ -196,48 +197,47 @@ def private_leaderboard(event_name): The event name. """ if not flask_login.current_user.is_authenticated: - return redirect(url_for('auth.login')) + return redirect(url_for("auth.login")) event = get_event(db.session, event_name) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): return redirect_to_user( - '{}: no event named "{}"' - .format(flask_login.current_user.firstname, event_name) + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) ) - if (not is_admin(db.session, event_name, flask_login.current_user.name) and - (event.closing_timestamp is None or - event.closing_timestamp > datetime.datetime.utcnow())): - return redirect(url_for('ramp.problems')) + if not is_admin(db.session, event_name, flask_login.current_user.name) and ( + event.closing_timestamp is None + or event.closing_timestamp > datetime.datetime.utcnow() + ): + return redirect(url_for("ramp.problems")) - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='looking at private leaderboard', + interaction="looking at private leaderboard", user=flask_login.current_user, - event=event + event=event, ) leaderboard_html = event.private_leaderboard_html admin = is_admin(db.session, event_name, flask_login.current_user.name) if event.official_score_type.is_lower_the_better: - sorting_direction = 'asc' + sorting_direction = "asc" else: - sorting_direction = 'desc' + sorting_direction = "desc" - approved = is_user_signed_up( - db.session, event_name, flask_login.current_user.name - ) + approved = is_user_signed_up(db.session, event_name, flask_login.current_user.name) asked = approved template = render_template( - 'leaderboard.html', - leaderboard_title='Leaderboard', + "leaderboard.html", + leaderboard_title="Leaderboard", leaderboard=leaderboard_html, - sorting_column_index=SORTING_COLUMN_INDEX+1, + sorting_column_index=SORTING_COLUMN_INDEX + 1, sorting_direction=sorting_direction, event=event, private=True, admin=admin, asked=asked, - approved=approved + approved=approved, ) return template @@ -254,43 +254,42 @@ def private_competition_leaderboard(event_name): The event name. """ if not flask_login.current_user.is_authenticated: - return redirect(url_for('auth.login')) + return redirect(url_for("auth.login")) event = get_event(db.session, event_name) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): return redirect_to_user( - '{}: no event named "{}"' - .format(flask_login.current_user.firstname, event_name) + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) ) - if (not is_admin(db.session, event_name, flask_login.current_user.name) and - (event.closing_timestamp is None or - event.closing_timestamp > datetime.datetime.utcnow())): - return redirect(url_for('ramp.problems')) + if not is_admin(db.session, event_name, flask_login.current_user.name) and ( + event.closing_timestamp is None + or event.closing_timestamp > datetime.datetime.utcnow() + ): + return redirect(url_for("ramp.problems")) - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='looking at private leaderboard', + interaction="looking at private leaderboard", user=flask_login.current_user, - event=event + event=event, ) admin = is_admin(db.session, event_name, flask_login.current_user.name) - approved = is_user_signed_up( - db.session, event_name, flask_login.current_user.name - ) + approved = is_user_signed_up(db.session, event_name, flask_login.current_user.name) asked = approved leaderboard_html = event.private_competition_leaderboard_html leaderboard_kwargs = dict( leaderboard=leaderboard_html, - leaderboard_title='Leaderboard', + leaderboard_title="Leaderboard", sorting_column_index=0, - sorting_direction='asc', + sorting_direction="asc", event=event, admin=admin, asked=asked, - approved=approved + approved=approved, ) - return render_template('leaderboard.html', **leaderboard_kwargs) + return render_template("leaderboard.html", **leaderboard_kwargs) diff --git a/ramp-frontend/ramp_frontend/views/ramp.py b/ramp-frontend/ramp_frontend/views/ramp.py index 0bbf90ce1..5b87e744b 100644 --- a/ramp-frontend/ramp_frontend/views/ramp.py +++ b/ramp-frontend/ramp_frontend/views/ramp.py @@ -72,20 +72,19 @@ from .visualization import score_plot -mod = Blueprint('ramp', __name__) -logger = logging.getLogger('RAMP-FRONTEND') +mod = Blueprint("ramp", __name__) +logger = logging.getLogger("RAMP-FRONTEND") @mod.route("/problems") def problems(): """Landing page showing all the RAMP problems.""" - user = (flask_login.current_user - if flask_login.current_user.is_authenticated else None) - admin = user.access_level == 'admin' if user is not None else False - if app.config['TRACK_USER_INTERACTION']: - add_user_interaction( - db.session, interaction='looking at problems', user=user - ) + user = ( + flask_login.current_user if flask_login.current_user.is_authenticated else None + ) + admin = user.access_level == "admin" if user is not None else False + if app.config["TRACK_USER_INTERACTION"]: + add_user_interaction(db.session, interaction="looking at problems", user=user) problems = get_problem(db.session, None) for problem in problems: @@ -96,28 +95,26 @@ def problems(): start_collab = event.public_opening_timestamp end = event.closing_timestamp if now < start or now >= end: - event.state = 'close' + event.state = "close" elif now >= start and now < start_collab: - event.state = 'competitive' + event.state = "competitive" elif now >= start and now >= start_collab and now < end: - event.state = 'collab' + event.state = "collab" if user: signed = get_event_team_by_name( - db.session, event.name, - flask_login.current_user.name) + db.session, event.name, flask_login.current_user.name + ) if not signed: - event.state_user = 'not_signed' + event.state_user = "not_signed" elif signed.approved: - event.state_user = 'signed' + event.state_user = "signed" elif signed: - event.state_user = 'waiting' + event.state_user = "waiting" else: - event.state_user = 'not_signed' + event.state_user = "not_signed" # problems = Problem.query.order_by(Problem.id.desc()) - return render_template('problems.html', - problems=problems, - admin=admin) + return render_template("problems.html", problems=problems, admin=admin) @mod.route("/problems/") @@ -130,44 +127,48 @@ def problem(problem_name): The name of a problem. """ current_problem = get_problem(db.session, problem_name) - user = (flask_login.current_user - if flask_login.current_user.is_authenticated else None) - admin = user.access_level == 'admin' if user is not None else False + user = ( + flask_login.current_user if flask_login.current_user.is_authenticated else None + ) + admin = user.access_level == "admin" if user is not None else False if current_problem: - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: if flask_login.current_user.is_authenticated: add_user_interaction( db.session, - interaction='looking at problem', + interaction="looking at problem", user=flask_login.current_user, - problem=current_problem + problem=current_problem, ) else: add_user_interaction( - db.session, interaction='looking at problem', - problem=current_problem + db.session, + interaction="looking at problem", + problem=current_problem, ) description_f_name = os.path.join( current_problem.path_ramp_kit, - '{}_starting_kit.html'.format(current_problem.name) + "{}_starting_kit.html".format(current_problem.name), ) # check which event ramp-kit archive is the latest - archive_dir = os.path.join( - current_problem.path_ramp_kit, "events_archived" - ) + archive_dir = os.path.join(current_problem.path_ramp_kit, "events_archived") latest_event_zip = max( [f for f in os.scandir(archive_dir) if f.name.endswith(".zip")], - key=lambda x: x.stat().st_mtime + key=lambda x: x.stat().st_mtime, ) latest_event = os.path.splitext(latest_event_zip.name)[0] return render_template( - 'problem.html', problem=current_problem, admin=admin, - notebook_filename=description_f_name, latest_event=latest_event + "problem.html", + problem=current_problem, + admin=admin, + notebook_filename=description_f_name, + latest_event=latest_event, ) else: - return redirect_to_user('Problem {} does not exist' - .format(problem_name), is_error=True) + return redirect_to_user( + "Problem {} does not exist".format(problem_name), is_error=True + ) @mod.route("/download_starting_kit/") @@ -175,7 +176,7 @@ def download_starting_kit(event_name): event = db.session.query(Event).filter_by(name=event_name).one() return send_from_directory( os.path.join(event.problem.path_ramp_kit, "events_archived"), - event_name + ".zip" + event_name + ".zip", ) @@ -184,14 +185,14 @@ def notebook(problem_name): current_problem = get_problem(db.session, problem_name) return send_from_directory( current_problem.path_ramp_kit, - '{}_starting_kit.html'.format(current_problem.name) + "{}_starting_kit.html".format(current_problem.name), ) @mod.route("/rules/") def rules(event_name): event = get_event(db.session, event_name) - return render_template('rules.html', event=event) + return render_template("rules.html", event=event) @mod.route("/events/") @@ -204,20 +205,25 @@ def user_event(event_name): event_name : str The event name. """ - if flask_login.current_user.access_level == 'asked': - msg = 'Your account has not been approved yet by the administrator' + if flask_login.current_user.access_level == "asked": + msg = "Your account has not been approved yet by the administrator" logger.error(msg) return redirect_to_user(msg) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): - return redirect_to_user('{}: no event named "{}"' - .format(flask_login.current_user.firstname, - event_name)) + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): + return redirect_to_user( + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) + ) event = get_event(db.session, event_name) if event: - if app.config['TRACK_USER_INTERACTION']: - add_user_interaction(db.session, interaction='looking at event', - event=event, user=flask_login.current_user) + if app.config["TRACK_USER_INTERACTION"]: + add_user_interaction( + db.session, + interaction="looking at event", + event=event, + user=flask_login.current_user, + ) admin = is_admin(db.session, event_name, flask_login.current_user.name) approved = is_user_signed_up( db.session, event_name, flask_login.current_user.name @@ -225,13 +231,16 @@ def user_event(event_name): asked = is_user_sign_up_requested( db.session, event_name, flask_login.current_user.name ) - return render_template('event.html', - event=event, - admin=admin, - approved=approved, - asked=asked) - return redirect_to_user('Event {} does not exist.' - .format(event_name), is_error=True) + return render_template( + "event.html", + event=event, + admin=admin, + approved=approved, + asked=asked, + ) + return redirect_to_user( + "Event {} does not exist.".format(event_name), is_error=True + ) @mod.route("/events//sign_up") @@ -245,43 +254,52 @@ def sign_up_for_event(event_name): The name of the event. """ event = get_event(db.session, event_name) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): - return redirect_to_user('{}: no event named "{}"' - .format(flask_login.current_user.firstname, - event_name)) - if app.config['TRACK_USER_INTERACTION']: - add_user_interaction(db.session, interaction='signing up at event', - user=flask_login.current_user, event=event) + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): + return redirect_to_user( + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) + ) + if app.config["TRACK_USER_INTERACTION"]: + add_user_interaction( + db.session, + interaction="signing up at event", + user=flask_login.current_user, + event=event, + ) ask_sign_up_team(db.session, event.name, flask_login.current_user.name) if event.is_controled_signup: - admin_users = User.query.filter_by(access_level='admin') + admin_users = User.query.filter_by(access_level="admin") for admin in admin_users: - subject = ('Request to sign-up {} to RAMP event {}' - .format(event.name, flask_login.current_user.name)) + subject = "Request to sign-up {} to RAMP event {}".format( + event.name, flask_login.current_user.name + ) body = body_formatter_user(flask_login.current_user) - url_approve = ('http://{}/events/{}/sign_up/{}' - .format( - app.config['DOMAIN_NAME'], event.name, - flask_login.current_user.name - )) - body += ('Click on this link to approve the sign-up request: {}' - .format(url_approve)) + url_approve = "http://{}/events/{}/sign_up/{}".format( + app.config["DOMAIN_NAME"], + event.name, + flask_login.current_user.name, + ) + body += "Click on this link to approve the sign-up request: {}".format( + url_approve + ) send_mail(admin.email, subject, body) - return redirect_to_user("Sign-up request is sent to event admins.", - is_error=False, category='Request sent') + return redirect_to_user( + "Sign-up request is sent to event admins.", + is_error=False, + category="Request sent", + ) sign_up_team(db.session, event.name, flask_login.current_user.name) return redirect_to_sandbox( event, - '{} is signed up for {}.' - .format(flask_login.current_user.firstname, event), + "{} is signed up for {}.".format(flask_login.current_user.firstname, event), is_error=False, - category='Successful sign-up' + category="Successful sign-up", ) -@mod.route("/events//sandbox", methods=['GET', 'POST']) +@mod.route("/events//sandbox", methods=["GET", "POST"]) @flask_login.login_required def sandbox(event_name): """Landing page for the user's sandbox. @@ -292,23 +310,25 @@ def sandbox(event_name): The event name. """ event = get_event(db.session, event_name) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): return redirect_to_user( - '{}: no event named "{}"' - .format(flask_login.current_user.firstname, event_name) + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) + ) + if not is_accessible_code(db.session, event_name, flask_login.current_user.name): + error_str = ( + "No access to sandbox for event {}. If you have " + "already signed up, please wait for approval.".format(event.name) ) - if not is_accessible_code(db.session, event_name, - flask_login.current_user.name): - error_str = ('No access to sandbox for event {}. If you have ' - 'already signed up, please wait for approval.' - .format(event.name)) return redirect_to_user(error_str) # setup the webpage when loading # we use the code store in the sandbox to show to the user sandbox_submission = get_submission_by_name( - db.session, event_name, flask_login.current_user.name, - event.ramp_sandbox_name + db.session, + event_name, + flask_login.current_user.name, + event.ramp_sandbox_name, ) event_team = get_event_team_by_name( db.session, event_name, flask_login.current_user.name @@ -332,10 +352,9 @@ def sandbox(event_name): for submission_file in sandbox_submission.files: if submission_file.is_editable: f_field = submission_file.name - setattr(CodeForm, - f_field, StringField('Text', widget=TextArea())) + setattr(CodeForm, f_field, StringField("Text", widget=TextArea())) code_form_kwargs[f_field] = submission_file.get_code() - code_form_kwargs['prefix'] = 'code' + code_form_kwargs["prefix"] = "code" code_form = CodeForm(**code_form_kwargs) # Then, to be able to iterate over the files in the sandbox.html # template, we also fill a separate table of pairs (file name, code). @@ -343,21 +362,21 @@ def sandbox(event_name): for submission_file in sandbox_submission.files: if submission_file.is_editable: code_form.names_codes.append( - (submission_file.name, submission_file.get_code())) + (submission_file.name, submission_file.get_code()) + ) # initialize the submission field and the the uploading form submit_form = SubmitForm( - submission_name=event_team.last_submission_name, prefix='submit' + submission_name=event_team.last_submission_name, prefix="submit" ) - upload_form = UploadForm(prefix='upload') + upload_form = UploadForm(prefix="upload") # check if the event is before, during or after open state now = datetime.datetime.now() start = event.opening_timestamp end = event.closing_timestamp - event_status = {"msg": "", - "state": "not_yet"} + event_status = {"msg": "", "state": "not_yet"} start_str = start.strftime("%d of %B %Y at %H:%M") end_str = end.strftime("%d of %B %Y, %H:%M") if now < start: @@ -371,80 +390,89 @@ def sandbox(event_name): event_status["state"] = "close" admin = is_admin(db.session, event_name, flask_login.current_user.name) - if request.method == 'GET': + if request.method == "GET": return render_template( - 'sandbox.html', + "sandbox.html", submission_names=sandbox_submission.f_names, code_form=code_form, - submit_form=submit_form, upload_form=upload_form, + submit_form=submit_form, + upload_form=upload_form, event=event, admin=admin, - event_status=event_status + event_status=event_status, ) - if request.method == 'POST': - if ('code-csrf_token' in request.form and - code_form.validate_on_submit()): + if request.method == "POST": + if "code-csrf_token" in request.form and code_form.validate_on_submit(): try: for submission_file in sandbox_submission.files: if submission_file.is_editable: old_code = submission_file.get_code() - submission_file.set_code( - request.form[submission_file.name]) + submission_file.set_code(request.form[submission_file.name]) new_code = submission_file.get_code() - diff = '\n'.join(difflib.unified_diff( - old_code.splitlines(), new_code.splitlines())) + diff = "\n".join( + difflib.unified_diff( + old_code.splitlines(), new_code.splitlines() + ) + ) similarity = difflib.SequenceMatcher( - a=old_code, b=new_code).ratio() - if app.config['TRACK_USER_INTERACTION']: + a=old_code, b=new_code + ).ratio() + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='save', + interaction="save", user=flask_login.current_user, event=event, submission_file=submission_file, - diff=diff, similarity=similarity + diff=diff, + similarity=similarity, ) except Exception as e: - return redirect_to_sandbox(event, 'Error: {}'.format(e)) + return redirect_to_sandbox(event, "Error: {}".format(e)) # if we required to only save the file, redirect now if "saving" in request.form: return redirect_to_sandbox( event, - 'Your submission has been saved. You can safely comeback ' - 'to your sandbox later.', - is_error=False, category='File saved' + "Your submission has been saved. You can safely comeback " + "to your sandbox later.", + is_error=False, + category="File saved", ) elif request.files: - upload_f_name = secure_filename( - request.files['file'].filename) - upload_name = upload_f_name.split('.')[0] + upload_f_name = secure_filename(request.files["file"].filename) + upload_name = upload_f_name.split(".")[0] # TODO: create a get_function upload_workflow_element = WorkflowElement.query.filter_by( - name=upload_name, workflow=event.workflow).one_or_none() + name=upload_name, workflow=event.workflow + ).one_or_none() if upload_workflow_element is None: - return redirect_to_sandbox(event, - '{} is not in the file list.' - .format(upload_f_name)) + return redirect_to_sandbox( + event, "{} is not in the file list.".format(upload_f_name) + ) # TODO: create a get_function submission_file = SubmissionFile.query.filter_by( submission=sandbox_submission, - workflow_element=upload_workflow_element).one() + workflow_element=upload_workflow_element, + ).one() if submission_file.is_editable: old_code = submission_file.get_code() tmp_f_name = os.path.join(tempfile.gettempdir(), upload_f_name) - request.files['file'].save(tmp_f_name) + request.files["file"].save(tmp_f_name) file_length = os.stat(tmp_f_name).st_size - if (upload_workflow_element.max_size is not None and - file_length > upload_workflow_element.max_size): + if ( + upload_workflow_element.max_size is not None + and file_length > upload_workflow_element.max_size + ): return redirect_to_sandbox( event, - 'File is too big: {} exceeds max size {}' - .format(file_length, upload_workflow_element.max_size) + "File is too big: {} exceeds max size {}".format( + file_length, upload_workflow_element.max_size + ), ) if submission_file.is_editable: try: @@ -452,39 +480,41 @@ def sandbox(event_name): code = f.read() submission_file.set_code(code) except Exception as e: - return redirect_to_sandbox(event, 'Error: {}'.format(e)) + return redirect_to_sandbox(event, "Error: {}".format(e)) else: # non-editable files are not verified for now dst = os.path.join(sandbox_submission.path, upload_f_name) shutil.copy2(tmp_f_name, dst) - logger.info('{} uploaded {} in {}' - .format(flask_login.current_user.name, upload_f_name, - event)) + logger.info( + "{} uploaded {} in {}".format( + flask_login.current_user.name, upload_f_name, event + ) + ) if submission_file.is_editable: new_code = submission_file.get_code() - diff = '\n'.join(difflib.unified_diff( - old_code.splitlines(), new_code.splitlines())) - similarity = difflib.SequenceMatcher( - a=old_code, b=new_code).ratio() - if app.config['TRACK_USER_INTERACTION']: + diff = "\n".join( + difflib.unified_diff(old_code.splitlines(), new_code.splitlines()) + ) + similarity = difflib.SequenceMatcher(a=old_code, b=new_code).ratio() + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='upload', + interaction="upload", user=flask_login.current_user, event=event, submission_file=submission_file, diff=diff, - similarity=similarity + similarity=similarity, ) else: - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='upload', + interaction="upload", user=flask_login.current_user, event=event, - submission_file=submission_file + submission_file=submission_file, ) return redirect(request.referrer) @@ -492,49 +522,54 @@ def sandbox(event_name): # ie: now we let upload eg external_data.bla, and only fail at # submission, without giving a message - if 'submission' in request.form: + if "submission" in request.form: if not submit_form.validate_on_submit(): return redirect_to_sandbox( - event, - 'Submission name should not contain any spaces' + event, "Submission name should not contain any spaces" ) - new_submission_name = request.form['submit-submission_name'] + new_submission_name = request.form["submit-submission_name"] if not 4 < len(new_submission_name) < 20: return redirect_to_sandbox( event, - 'Submission name should have length between 4 and ' - '20 characters.' + "Submission name should have length between 4 and " + "20 characters.", ) try: - new_submission_name.encode('ascii') + new_submission_name.encode("ascii") except Exception as e: - return redirect_to_sandbox(event, 'Error: {}'.format(e)) + return redirect_to_sandbox(event, "Error: {}".format(e)) try: - new_submission = add_submission(db.session, event_name, - event_team.team.name, - new_submission_name, - sandbox_submission.path) + new_submission = add_submission( + db.session, + event_name, + event_team.team.name, + new_submission_name, + sandbox_submission.path, + ) except DuplicateSubmissionError: return redirect_to_sandbox( event, - 'Submission {} already exists. Please change the name.' - .format(new_submission_name) + "Submission {} already exists. Please change the name.".format( + new_submission_name + ), ) except MissingExtensionError: - return redirect_to_sandbox( - event, 'Missing extension' - ) + return redirect_to_sandbox(event, "Missing extension") except TooEarlySubmissionError as e: return redirect_to_sandbox(event, str(e)) - logger.info('{} submitted {} for {}.' - .format(flask_login.current_user.name, - new_submission.name, event_team)) + logger.info( + "{} submitted {} for {}.".format( + flask_login.current_user.name, + new_submission.name, + event_team, + ) + ) if event.is_send_submitted_mails: - admin_users = User.query.filter_by(access_level='admin') + admin_users = User.query.filter_by(access_level="admin") for admin in admin_users: - subject = 'Submission {} sent for training'.format( + subject = "Submission {} sent for training".format( new_submission.name ) body = """A new submission have been submitted: @@ -542,56 +577,69 @@ def sandbox(event_name): user: {} submission: {} submission path: {} - """.format(event_team.event.name, - flask_login.current_user.name, - new_submission.name, new_submission.path) + """.format( + event_team.event.name, + flask_login.current_user.name, + new_submission.name, + new_submission.path, + ) send_mail(admin.email, subject, body) - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='submit', + interaction="submit", user=flask_login.current_user, event=event, - submission=new_submission + submission=new_submission, ) - if app.config['TRACK_CREDITS']: + if app.config["TRACK_CREDITS"]: return redirect_to_credit( submission_hash=new_submission.hash_, - message_str='{}'.format( - 'Successful submission, please provide credits.'), - is_error=False) + message_str="{}".format( + "Successful submission, please provide credits." + ), + is_error=False, + ) else: return redirect_to_sandbox( event, - '{} submitted {} for {}' - .format(flask_login.current_user.firstname, - new_submission.name, event_team), - is_error=False, category='Submission' + "{} submitted {} for {}".format( + flask_login.current_user.firstname, + new_submission.name, + event_team, + ), + is_error=False, + category="Submission", ) admin = is_admin(db.session, event_name, flask_login.current_user.name) return render_template( - 'sandbox.html', + "sandbox.html", submission_names=sandbox_submission.f_names, code_form=code_form, - submit_form=submit_form, upload_form=upload_form, + submit_form=submit_form, + upload_form=upload_form, event=event, admin=admin, - event_status=event_status + event_status=event_status, ) -@mod.route("/problems//ask_for_event", methods=['GET', 'POST']) +@mod.route("/problems//ask_for_event", methods=["GET", "POST"]) @flask_login.login_required def ask_for_event(problem_name): problem = Problem.query.filter_by(name=problem_name).one_or_none() if problem is None: return redirect_to_user( - '{}: no problem named "{}"' - .format(flask_login.current_user.firstname, problem_name) + '{}: no problem named "{}"'.format( + flask_login.current_user.firstname, problem_name + ) + ) + logger.info( + "{} is asking for event on {}".format( + flask_login.current_user.name, problem.name ) - logger.info('{} is asking for event on {}' - .format(flask_login.current_user.name, problem.name)) + ) # We assume here that event name has the syntax _ form = AskForEventForm( min_duration_between_submissions_hour=8, @@ -599,9 +647,9 @@ def ask_for_event(problem_name): min_duration_between_submissions_second=0, ) if form.validate_on_submit(): - admin_users = User.query.filter_by(access_level='admin') + admin_users = User.query.filter_by(access_level="admin") for admin in admin_users: - subject = 'Request to add a new event' + subject = "Request to add a new event" body = """User {} asked to add a new event: event name: {} event title: {} @@ -611,25 +659,26 @@ def ask_for_event(problem_name): closing data: {} """.format( flask_login.current_user.name, - problem.name + '_' + form.suffix.data, + problem.name + "_" + form.suffix.data, form.title.data, form.n_students.data, form.min_duration_between_submissions_hour.data, form.min_duration_between_submissions_minute.data, form.min_duration_between_submissions_second.data, form.opening_date.data, - form.closing_date.data + form.closing_date.data, ) send_mail(admin.email, subject, body) return redirect_to_user( - 'Thank you. Your request has been sent to RAMP administrators.', - category='Event request', is_error=False + "Thank you. Your request has been sent to RAMP administrators.", + category="Event request", + is_error=False, ) - return render_template('ask_for_event.html', form=form, problem=problem) + return render_template("ask_for_event.html", form=form, problem=problem) -@mod.route("/credit/", methods=['GET', 'POST']) +@mod.route("/credit/", methods=["GET", "POST"]) @flask_login.login_required def credit(submission_hash): """The landing page to credit other submission when a user submit is own. @@ -639,54 +688,51 @@ def credit(submission_hash): submission_hash : str The submission hash of the current submission. """ - submission = (Submission.query.filter_by(hash_=submission_hash) - .one_or_none()) + submission = Submission.query.filter_by(hash_=submission_hash).one_or_none() access_code = is_accessible_code( - db.session, submission.event_team.event.name, - flask_login.current_user.name, submission.id + db.session, + submission.event_team.event.name, + flask_login.current_user.name, + submission.id, ) if submission is None or not access_code: - error_str = 'Missing submission: {}'.format(submission_hash) + error_str = "Missing submission: {}".format(submission_hash) return redirect_to_user(error_str) event_team = submission.event_team event = event_team.event source_submissions = get_source_submissions(db.session, submission.id) def get_s_field(source_submission): - return '{}/{}/{}'.format( + return "{}/{}/{}".format( source_submission.event_team.event.name, source_submission.event_team.team.name, - source_submission.name) + source_submission.name, + ) # Make sure that CreditForm is empty CreditForm.name_credits = [] credit_form_kwargs = {} for source_submission in source_submissions: s_field = get_s_field(source_submission) - setattr(CreditForm, s_field, StringField('Text')) + setattr(CreditForm, s_field, StringField("Text")) credit_form = CreditForm(**credit_form_kwargs) sum_credit = 0 # new = True for source_submission in source_submissions: s_field = get_s_field(source_submission) - submission_similaritys = \ - (SubmissionSimilarity.query - .filter_by( - type='target_credit', - user=flask_login.current_user, - source_submission=source_submission, - target_submission=submission) - .all()) + submission_similaritys = SubmissionSimilarity.query.filter_by( + type="target_credit", + user=flask_login.current_user, + source_submission=source_submission, + target_submission=submission, + ).all() if not submission_similaritys: submission_credit = 0 else: # new = False # find the last credit (in case crediter changes her mind) - submission_similaritys.sort( - key=lambda x: x.timestamp, reverse=True) - submission_credit = int( - round(100 * submission_similaritys[0].similarity) - ) + submission_similaritys.sort(key=lambda x: x.timestamp, reverse=True) + submission_credit = int(round(100 * submission_similaritys[0].similarity)) sum_credit += submission_credit credit_form.name_credits.append( (s_field, str(submission_credit), source_submission.link) @@ -704,50 +750,52 @@ def get_s_field(source_submission): if sum_credit != 100: return redirect_to_credit( submission_hash, - 'Error: The total credit should add up to 100' + "Error: The total credit should add up to 100", ) except Exception as e: - return redirect_to_credit(submission_hash, 'Error: {}'.format(e)) + return redirect_to_credit(submission_hash, "Error: {}".format(e)) for source_submission in source_submissions: s_field = get_s_field(source_submission) - similarity = int(getattr(credit_form, s_field).data) / 100. - submission_similarity = \ - (SubmissionSimilarity.query - .filter_by( - type='target_credit', - user=flask_login.current_user, - source_submission=source_submission, - target_submission=submission) - .all()) + similarity = int(getattr(credit_form, s_field).data) / 100.0 + submission_similarity = SubmissionSimilarity.query.filter_by( + type="target_credit", + user=flask_login.current_user, + source_submission=source_submission, + target_submission=submission, + ).all() # if submission_similarity is not empty, we need to # add zero to cancel previous credits explicitly if similarity > 0 or submission_similarity: add_submission_similarity( db.session, - credit_type='target_credit', + credit_type="target_credit", user=flask_login.current_user, source_submission=source_submission, target_submission=submission, similarity=similarity, - timestamp=datetime.datetime.utcnow() + timestamp=datetime.datetime.utcnow(), ) - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='giving credit', + interaction="giving credit", user=flask_login.current_user, event=event, - submission=submission + submission=submission, ) - return redirect('/events/{}/sandbox'.format(event.name)) + return redirect("/events/{}/sandbox".format(event.name)) admin = is_admin(db.session, event.name, flask_login.current_user.name) return render_template( - 'credit.html', submission=submission, - source_submissions=source_submissions, credit_form=credit_form, - event=event, admin=admin) + "credit.html", + submission=submission, + source_submissions=source_submissions, + credit_form=credit_form, + event=event, + admin=admin, + ) @mod.route("/event_plots/") @@ -762,24 +810,22 @@ def event_plots(event_name): The name of the event. """ event = get_event(db.session, event_name) - if not is_accessible_event(db.session, event_name, - flask_login.current_user.name): - return redirect_to_user('{}: no event named "{}"' - .format(flask_login.current_user.firstname, - event_name)) + if not is_accessible_event(db.session, event_name, flask_login.current_user.name): + return redirect_to_user( + '{}: no event named "{}"'.format( + flask_login.current_user.firstname, event_name + ) + ) if event: p = score_plot(db.session, event) script, div = components(p) - return render_template('event_plots.html', - script=script, - div=div, - event=event) - return redirect_to_user('Event {} does not exist.' - .format(event_name), - is_error=True) + return render_template("event_plots.html", script=script, div=div, event=event) + return redirect_to_user( + "Event {} does not exist.".format(event_name), is_error=True + ) -@mod.route("//", methods=['GET', 'POST']) +@mod.route("//", methods=["GET", "POST"]) @flask_login.login_required def view_model(submission_hash, f_name): """Rendering submission codes using templates/submission.html. @@ -797,35 +843,34 @@ def view_model(submission_hash, f_name): f_name : tr The name of the submission file. """ - submission = (Submission.query.filter_by(hash_=submission_hash) - .one_or_none()) - if (submission is None or - not is_accessible_code(db.session, submission.event.name, - flask_login.current_user.name, - submission.id)): - error_str = 'Missing submission: {}'.format(submission_hash) + submission = Submission.query.filter_by(hash_=submission_hash).one_or_none() + if submission is None or not is_accessible_code( + db.session, + submission.event.name, + flask_login.current_user.name, + submission.id, + ): + error_str = "Missing submission: {}".format(submission_hash) return redirect_to_user(error_str) event = submission.event_team.event team = submission.event_team.team - workflow_element_name = f_name.split('.')[0] - workflow_element = \ - (WorkflowElement.query.filter_by(name=workflow_element_name, - workflow=event.workflow) - .one_or_none()) + workflow_element_name = f_name.split(".")[0] + workflow_element = WorkflowElement.query.filter_by( + name=workflow_element_name, workflow=event.workflow + ).one_or_none() if workflow_element is None: - error_str = ('{} is not a valid workflow element by {} ' - .format(workflow_element_name, - flask_login.current_user.name)) - error_str += 'in {}/{}/{}/{}'.format(event, team, submission, f_name) + error_str = "{} is not a valid workflow element by {} ".format( + workflow_element_name, flask_login.current_user.name + ) + error_str += "in {}/{}/{}/{}".format(event, team, submission, f_name) return redirect_to_user(error_str) - submission_file = \ - (SubmissionFile.query.filter_by(submission=submission, - workflow_element=workflow_element) - .one_or_none()) + submission_file = SubmissionFile.query.filter_by( + submission=submission, workflow_element=workflow_element + ).one_or_none() if submission_file is None: - error_str = ('No submission file by {} in {}/{}/{}/{}' - .format(flask_login.current_user.name, - event, team, submission, f_name)) + error_str = "No submission file by {} in {}/{}/{}/{}".format( + flask_login.current_user.name, event, team, submission, f_name + ) return redirect_to_user(error_str) # superfluous, perhaps when we'll have different extensions? @@ -833,24 +878,31 @@ def view_model(submission_hash, f_name): submission_abspath = os.path.abspath(submission.path) if not os.path.exists(submission_abspath): - error_str = ('{} does not exist by {} in {}/{}/{}/{}' - .format(submission_abspath, flask_login.current_user.name, - event, team, submission, f_name)) + error_str = "{} does not exist by {} in {}/{}/{}/{}".format( + submission_abspath, + flask_login.current_user.name, + event, + team, + submission, + f_name, + ) return redirect_to_user(error_str) - if app.config['TRACK_USER_INTERACTION'] or app.config['TRACK_CREDITS']: + if app.config["TRACK_USER_INTERACTION"] or app.config["TRACK_CREDITS"]: add_user_interaction( db.session, - interaction='looking at submission', + interaction="looking at submission", user=flask_login.current_user, event=event, submission=submission, - submission_file=submission_file + submission_file=submission_file, ) - logger.info('{} is looking at {}/{}/{}/{}' - .format(flask_login.current_user.name, event, team, submission, - f_name)) + logger.info( + "{} is looking at {}/{}/{}/{}".format( + flask_login.current_user.name, event, team, submission, f_name + ) + ) # Downloading file if it is not editable (e.g., external_data.csv) if not workflow_element.is_editable: @@ -858,20 +910,22 @@ def view_model(submission_hash, f_name): # with changedir(submission_abspath): # with ZipFile(archive_filename, 'w') as archive: # archive.write(f_name) - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='download', + interaction="download", user=flask_login.current_user, event=event, submission=submission, - submission_file=submission_file + submission_file=submission_file, ) return send_from_directory( - submission_abspath, f_name, as_attachment=True, - attachment_filename='{}_{}'.format(submission.hash_[:6], f_name), - mimetype='application/octet-stream' + submission_abspath, + f_name, + as_attachment=True, + attachment_filename="{}_{}".format(submission.hash_[:6], f_name), + mimetype="application/octet-stream", ) # Importing selected files into sandbox @@ -880,51 +934,59 @@ def view_model(submission_hash, f_name): import_form.selected_f_names.choices = choices if import_form.validate_on_submit(): sandbox_submission = get_submission_by_name( - db.session, event.name, flask_login.current_user.name, - event.ramp_sandbox_name + db.session, + event.name, + flask_login.current_user.name, + event.ramp_sandbox_name, ) for filename in import_form.selected_f_names.data: logger.info( - '{} is importing {}/{}/{}/{}' - .format(flask_login.current_user.name, event, team, - submission, filename) + "{} is importing {}/{}/{}/{}".format( + flask_login.current_user.name, + event, + team, + submission, + filename, + ) ) workflow_element = WorkflowElement.query.filter_by( - name=filename.split('.')[0], workflow=event.workflow).one() + name=filename.split(".")[0], workflow=event.workflow + ).one() # TODO: deal with different extensions of the same file src = os.path.join(submission.path, filename) dst = os.path.join(sandbox_submission.path, filename) shutil.copy2(src, dst) # copying also metadata - logger.info('Copying {} to {}'.format(src, dst)) + logger.info("Copying {} to {}".format(src, dst)) submission_file = SubmissionFile.query.filter_by( - submission=submission, - workflow_element=workflow_element).one() - if app.config['TRACK_USER_INTERACTION']: + submission=submission, workflow_element=workflow_element + ).one() + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='copy', + interaction="copy", user=flask_login.current_user, event=event, submission=submission, - submission_file=submission_file + submission_file=submission_file, ) - return redirect('/events/{}/sandbox'.format(event.name)) + return redirect("/events/{}/sandbox".format(event.name)) with open(os.path.join(submission.path, f_name)) as f: code = f.read() admin = is_admin(db.session, event.name, flask_login.current_user.name) return render_template( - 'submission.html', + "submission.html", event=event, code=code, submission=submission, f_name=f_name, import_form=import_form, - admin=admin) + admin=admin, + ) @mod.route("//error.txt") @@ -943,27 +1005,27 @@ def view_submission_error(submission_hash): submission_hash : str The hash of the submission. """ - submission = (Submission.query.filter_by(hash_=submission_hash) - .one_or_none()) + submission = Submission.query.filter_by(hash_=submission_hash).one_or_none() if submission is None: - error_str = ('Missing submission {}: {}' - .format(flask_login.current_user.name, submission_hash)) + error_str = "Missing submission {}: {}".format( + flask_login.current_user.name, submission_hash + ) return redirect_to_user(error_str) event = submission.event_team.event team = submission.event_team.team # TODO: check if event == submission.event_team.event - if app.config['TRACK_USER_INTERACTION']: + if app.config["TRACK_USER_INTERACTION"]: add_user_interaction( db.session, - interaction='looking at error', + interaction="looking at error", user=flask_login.current_user, event=event, - submission=submission + submission=submission, ) return render_template( - 'submission_error.html', submission=submission, team=team, event=event + "submission_error.html", submission=submission, team=team, event=event ) @@ -977,26 +1039,25 @@ def toggle_competition(submission_hash): submission_hash : str The submission hash of the current submission. """ - submission = (Submission.query.filter_by(hash_=submission_hash) - .one_or_none()) + submission = Submission.query.filter_by(hash_=submission_hash).one_or_none() if submission is None: - error_str = 'Missing submission: {}'.format(submission_hash) + error_str = "Missing submission: {}".format(submission_hash) return redirect_to_user(error_str) access_code = is_accessible_code( - db.session, submission.event_team.event.name, - flask_login.current_user.name, submission.id + db.session, + submission.event_team.event.name, + flask_login.current_user.name, + submission.id, ) if not access_code: - error_str = 'Missing submission: {}'.format(submission_hash) + error_str = "Missing submission: {}".format(submission_hash) return redirect_to_user(error_str) submission.is_in_competition = not submission.is_in_competition db.session.commit() update_leaderboards(db.session, submission.event_team.event.name) - return redirect( - '/{}/{}'.format(submission_hash, submission.files[0].f_name) - ) + return redirect("/{}/{}".format(submission_hash, submission.files[0].f_name)) @mod.route("/download/") @@ -1009,22 +1070,23 @@ def download_submission(submission_hash): submission_hash : str The submission hash of the current submission. """ - submission = (Submission.query.filter_by(hash_=submission_hash) - .one_or_none()) + submission = Submission.query.filter_by(hash_=submission_hash).one_or_none() if submission is None: - error_str = 'Missing submission: {}'.format(submission_hash) + error_str = "Missing submission: {}".format(submission_hash) return redirect_to_user(error_str) access_code = is_accessible_code( - db.session, submission.event_team.event.name, - flask_login.current_user.name, submission.id + db.session, + submission.event_team.event.name, + flask_login.current_user.name, + submission.id, ) if not access_code: - error_str = 'Unauthorized access: {}'.format(submission_hash) + error_str = "Unauthorized access: {}".format(submission_hash) return redirect_to_user(error_str) file_in_memory = io.BytesIO() - with zipfile.ZipFile(file_in_memory, 'w') as zf: + with zipfile.ZipFile(file_in_memory, "w") as zf: for ff in submission.files: data = zipfile.ZipInfo(ff.f_name) data.date_time = time.localtime(time.time())[:6] diff --git a/ramp-frontend/ramp_frontend/views/redirect.py b/ramp-frontend/ramp_frontend/views/redirect.py index 91b733ca9..2f9ca4563 100644 --- a/ramp-frontend/ramp_frontend/views/redirect.py +++ b/ramp-frontend/ramp_frontend/views/redirect.py @@ -4,10 +4,10 @@ from flask import redirect from flask import url_for -logger = logging.getLogger('RAMP-FRONTEND') +logger = logging.getLogger("RAMP-FRONTEND") -def redirect_to_user(message_str, is_error=True, category='message'): +def redirect_to_user(message_str, is_error=True, category="message"): """Redirect the page to the problem landing page. Parameters @@ -25,7 +25,7 @@ def redirect_to_user(message_str, is_error=True, category='message'): logger.error(message_str) else: logger.info(message_str) - return redirect(url_for('ramp.problems')) + return redirect(url_for("ramp.problems")) def redirect_to_sandbox(event, message_str, is_error=True, category=None): @@ -45,11 +45,10 @@ def redirect_to_sandbox(event, message_str, is_error=True, category=None): logger.error(message_str) else: logger.info(message_str) - return redirect('/events/{}/sandbox'.format(event.name)) + return redirect("/events/{}/sandbox".format(event.name)) -def redirect_to_credit(submission_hash, message_str, is_error=True, - category=None): +def redirect_to_credit(submission_hash, message_str, is_error=True, category=None): """Redirect the page to the credit landing page. Parameters @@ -68,4 +67,4 @@ def redirect_to_credit(submission_hash, message_str, is_error=True, logger.error(message_str) else: logger.info(message_str) - return redirect('/credit/{}'.format(submission_hash)) + return redirect("/credit/{}".format(submission_hash)) diff --git a/ramp-frontend/ramp_frontend/views/visualization.py b/ramp-frontend/ramp_frontend/views/visualization.py index 021cf5c87..3f19ed8c4 100644 --- a/ramp-frontend/ramp_frontend/views/visualization.py +++ b/ramp-frontend/ramp_frontend/views/visualization.py @@ -22,25 +22,28 @@ def make_step_df(pareto_df, is_lower_the_better): pareto_df = pareto_df.set_index(1 + 2 * np.arange(n_pareto)) for i in range(2, 2 * n_pareto, 2): pareto_df.loc[i] = pareto_df.loc[i - 1] - pareto_df.loc[i, 'x'] = pareto_df.loc[i + 1, 'x'] + pareto_df.loc[i, "x"] = pareto_df.loc[i + 1, "x"] pareto_df.loc[2 * n_pareto] = pareto_df.loc[2 * n_pareto - 1] - pareto_df.loc[2 * n_pareto, 'x'] = max(pareto_df['x']) + pareto_df.loc[2 * n_pareto, "x"] = max(pareto_df["x"]) pareto_df.loc[0] = pareto_df.loc[1] if is_lower_the_better: - pareto_df.loc[0, 'y'] = max(pareto_df['y']) + pareto_df.loc[0, "y"] = max(pareto_df["y"]) else: - pareto_df.loc[0, 'y'] = min(pareto_df['y']) + pareto_df.loc[0, "y"] = min(pareto_df["y"]) return pareto_df.sort_index() def color_gradient(rgb, factor_array): """Rescale rgb by factor_array.""" from skimage.color import gray2rgb, rgb2gray + colors = np.array( - [(255 - rgb[0], 255 - rgb[2], 255 - rgb[2]) for _ in factor_array]) + [(255 - rgb[0], 255 - rgb[2], 255 - rgb[2]) for _ in factor_array] + ) colors = rgb2gray(colors) - colors = gray2rgb(255 - np.array([color * factor for color, factor - in zip(colors, factor_array)]))[:, :, 0] + colors = gray2rgb( + 255 - np.array([color * factor for color, factor in zip(colors, factor_array)]) + )[:, :, 0] return colors @@ -66,21 +69,20 @@ def add_pareto(df, col, worst, is_lower_the_better): The dataframe amended with the new column col + ' pareto' """ df_ = df.copy() - df_.loc[:, col + ' pareto'] = pd.Series(np.zeros(df.shape[0]), - index=df_.index) + df_.loc[:, col + " pareto"] = pd.Series(np.zeros(df.shape[0]), index=df_.index) best_score = worst if is_lower_the_better: for i, row in df.iterrows(): score = row[col] if score < best_score: best_score = score - df_.loc[i, col + ' pareto'] = 1 + df_.loc[i, col + " pareto"] = 1 else: for i, row in df.iterrows(): score = row[col] if score > best_score: best_score = score - df_.loc[i, col + ' pareto'] = 1 + df_.loc[i, col + " pareto"] = 1 return df_ @@ -93,22 +95,30 @@ def score_plot(session, event): submissions = [ get_submission_by_id(session, sub_id) for sub_id, _, _ in submissions - if get_submission_by_id(session, sub_id).is_public_leaderboard and - get_submission_by_id(session, sub_id).is_valid] + if get_submission_by_id(session, sub_id).is_public_leaderboard + and get_submission_by_id(session, sub_id).is_valid + ] score_names = [score_type.name for score_type in event.score_types] - scoress = np.array([ - [score.valid_score_cv_bag - for score in submission.ordered_scores(score_names)] - for submission in submissions - ]).T + scoress = np.array( + [ + [ + score.valid_score_cv_bag + for score in submission.ordered_scores(score_names) + ] + for submission in submissions + ] + ).T score_plot_df = pd.DataFrame() - score_plot_df['submitted at (UTC)'] = [ - submission.submission_timestamp for submission in submissions] - score_plot_df['contributivity'] = [ - submission.contributivity for submission in submissions] - score_plot_df['historical contributivity'] = [ - submission.historical_contributivity for submission in submissions] + score_plot_df["submitted at (UTC)"] = [ + submission.submission_timestamp for submission in submissions + ] + score_plot_df["contributivity"] = [ + submission.contributivity for submission in submissions + ] + score_plot_df["historical contributivity"] = [ + submission.historical_contributivity for submission in submissions + ] for score_name in score_names: # to make sure the column is created score_plot_df[score_name] = 0 for score_name, scores in zip(score_names, scoress): @@ -116,86 +126,101 @@ def score_plot(session, event): score_name = event.official_score_name score_plot_df = score_plot_df[ - score_plot_df['submitted at (UTC)'] > event.opening_timestamp] - score_plot_df = score_plot_df.sort_values('submitted at (UTC)') + score_plot_df["submitted at (UTC)"] > event.opening_timestamp + ] + score_plot_df = score_plot_df.sort_values("submitted at (UTC)") score_plot_df = add_pareto( - score_plot_df, score_name, event.official_score_type.worst, - event.official_score_type.is_lower_the_better) + score_plot_df, + score_name, + event.official_score_type.worst, + event.official_score_type.is_lower_the_better, + ) - is_open = (score_plot_df['submitted at (UTC)'] > - event.public_opening_timestamp).values + is_open = ( + score_plot_df["submitted at (UTC)"] > event.public_opening_timestamp + ).values - max_contributivity = max( - 0.0000001, max(score_plot_df['contributivity'].values)) - max_historical_contributivity = max(0.0000001, max( - score_plot_df['historical contributivity'].values)) + max_contributivity = max(0.0000001, max(score_plot_df["contributivity"].values)) + max_historical_contributivity = max( + 0.0000001, max(score_plot_df["historical contributivity"].values) + ) fill_color_1 = (176, 23, 31) fill_color_2 = (16, 78, 139) fill_colors_1 = color_gradient( - fill_color_1, score_plot_df['contributivity'].values / - max_contributivity) + fill_color_1, + score_plot_df["contributivity"].values / max_contributivity, + ) fill_colors_2 = color_gradient( - fill_color_2, score_plot_df['historical contributivity'].values / - max_historical_contributivity) + fill_color_2, + score_plot_df["historical contributivity"].values + / max_historical_contributivity, + ) fill_colors = np.minimum(fill_colors_1, fill_colors_2).astype(int) fill_colors = ["#%02x%02x%02x" % (c[0], c[1], c[2]) for c in fill_colors] - score_plot_df['x'] = score_plot_df['submitted at (UTC)'] - score_plot_df['y'] = score_plot_df[score_name] - score_plot_df['line_color'] = 'royalblue' - score_plot_df['circle_size'] = 8 - score_plot_df['line_color'] = 'royalblue' - score_plot_df.loc[is_open, 'line_color'] = 'coral' - score_plot_df['fill_color'] = fill_colors - score_plot_df['fill_alpha'] = 0.5 - score_plot_df['line_width'] = 0 - score_plot_df['label'] = 'closed phase' - score_plot_df.loc[is_open, 'label'] = 'open phase' + score_plot_df["x"] = score_plot_df["submitted at (UTC)"] + score_plot_df["y"] = score_plot_df[score_name] + score_plot_df["line_color"] = "royalblue" + score_plot_df["circle_size"] = 8 + score_plot_df["line_color"] = "royalblue" + score_plot_df.loc[is_open, "line_color"] = "coral" + score_plot_df["fill_color"] = fill_colors + score_plot_df["fill_alpha"] = 0.5 + score_plot_df["line_width"] = 0 + score_plot_df["label"] = "closed phase" + score_plot_df.loc[is_open, "label"] = "open phase" source = ColumnDataSource(score_plot_df) - pareto_df = score_plot_df[ - score_plot_df[score_name + ' pareto'] == 1].copy() + pareto_df = score_plot_df[score_plot_df[score_name + " pareto"] == 1].copy() pareto_df = pareto_df.append(pareto_df.iloc[-1]) - pareto_df.iloc[-1, pareto_df.columns.get_loc('x')] = ( - max(score_plot_df['x']) - ) - pareto_df = make_step_df( - pareto_df, event.official_score_type.is_lower_the_better) + pareto_df.iloc[-1, pareto_df.columns.get_loc("x")] = max(score_plot_df["x"]) + pareto_df = make_step_df(pareto_df, event.official_score_type.is_lower_the_better) source_pareto = ColumnDataSource(pareto_df) - tools = ['pan,wheel_zoom,box_zoom,reset,save,tap'] - p = figure(plot_width=900, plot_height=600, tools=tools, title='Scores') + tools = ["pan,wheel_zoom,box_zoom,reset,save,tap"] + p = figure(plot_width=900, plot_height=600, tools=tools, title="Scores") p.circle( - 'x', 'y', size='circle_size', line_color='line_color', - fill_color='fill_color', fill_alpha='fill_alpha', line_width=1, - source=source, legend='label' + "x", + "y", + size="circle_size", + line_color="line_color", + fill_color="fill_color", + fill_alpha="fill_alpha", + line_width=1, + source=source, + legend="label", ) p.line( - 'x', 'y', line_width=3, line_color='goldenrod', source=source_pareto, - legend='best score', alpha=0.9 + "x", + "y", + line_width=3, + line_color="goldenrod", + source=source_pareto, + legend="best score", + alpha=0.9, ) p.xaxis.formatter = DatetimeTickFormatter( - hours=['%d %B %Y'], - days=['%d %B %Y'], - months=['%d %B %Y'], - years=['%d %B %Y'], + hours=["%d %B %Y"], + days=["%d %B %Y"], + months=["%d %B %Y"], + years=["%d %B %Y"], ) p.xaxis.major_label_orientation = np.pi / 4 if event.official_score_type.is_lower_the_better: - p.yaxis.axis_label = score_name + ' (the lower the better)' - p.legend.location = 'top_right' + p.yaxis.axis_label = score_name + " (the lower the better)" + p.legend.location = "top_right" else: - p.yaxis.axis_label = score_name + ' (the greater the better)' - p.legend.location = 'bottom_right' - p.xaxis.axis_label = 'submission timestamp (UTC)' - p.xaxis.axis_label_text_font_size = '14pt' - p.yaxis.axis_label_text_font_size = '14pt' - p.legend.label_text_font_size = '14pt' - p.title.text_font_size = '16pt' - p.xaxis.major_label_text_font_size = '10pt' - p.yaxis.major_label_text_font_size = '10pt' + p.yaxis.axis_label = score_name + " (the greater the better)" + p.legend.location = "bottom_right" + p.xaxis.axis_label = "submission timestamp (UTC)" + p.xaxis.axis_label_text_font_size = "14pt" + p.yaxis.axis_label_text_font_size = "14pt" + p.legend.label_text_font_size = "14pt" + p.title.text_font_size = "16pt" + p.xaxis.major_label_text_font_size = "10pt" + p.yaxis.major_label_text_font_size = "10pt" return p diff --git a/ramp-frontend/setup.py b/ramp-frontend/setup.py index 753bd4663..7ce872ed2 100755 --- a/ramp-frontend/setup.py +++ b/ramp-frontend/setup.py @@ -5,54 +5,65 @@ from setuptools import find_packages, setup # get __version__ from _version.py -ver_file = os.path.join('ramp_frontend', '_version.py') +ver_file = os.path.join("ramp_frontend", "_version.py") with open(ver_file) as f: exec(f.read()) -DISTNAME = 'ramp-frontend' +DISTNAME = "ramp-frontend" DESCRIPTION = "Website for RAMP" -with codecs.open('README.rst', encoding='utf-8-sig') as f: +with codecs.open("README.rst", encoding="utf-8-sig") as f: LONG_DESCRIPTION = f.read() -MAINTAINER = 'A. Boucaud, B. Kegl, G. Lemaitre, J. Van den Bossche' -MAINTAINER_EMAIL = 'boucaud.alexandre@gmail.com, guillaume.lemaitre@inria.fr' -URL = 'https://github.com/paris-saclay-cds/ramp-board' -LICENSE = 'BSD (3-clause)' -DOWNLOAD_URL = 'https://github.com/paris-saclay-cds/ramp-board' +MAINTAINER = "A. Boucaud, B. Kegl, G. Lemaitre, J. Van den Bossche" +MAINTAINER_EMAIL = "boucaud.alexandre@gmail.com, guillaume.lemaitre@inria.fr" +URL = "https://github.com/paris-saclay-cds/ramp-board" +LICENSE = "BSD (3-clause)" +DOWNLOAD_URL = "https://github.com/paris-saclay-cds/ramp-board" VERSION = __version__ # noqa -CLASSIFIERS = ['Intended Audience :: Science/Research', - 'Intended Audience :: Developers', - 'License :: OSI Approved', - 'Programming Language :: Python', - 'Topic :: Software Development', - 'Topic :: Scientific/Engineering', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Operating System :: Unix', - 'Operating System :: MacOS', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8'] -INSTALL_REQUIRES = ['bokeh', 'click', 'Flask', 'Flask-Login', 'Flask-Mail', - 'Flask-SQLAlchemy', 'Flask-WTF', 'itsdangerous', 'numpy', - 'pandas'] +CLASSIFIERS = [ + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "License :: OSI Approved", + "Programming Language :: Python", + "Topic :: Software Development", + "Topic :: Scientific/Engineering", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", +] +INSTALL_REQUIRES = [ + "bokeh", + "click", + "Flask", + "Flask-Login", + "Flask-Mail", + "Flask-SQLAlchemy", + "Flask-WTF", + "itsdangerous", + "numpy", + "pandas", +] EXTRAS_REQUIRE = { - 'tests': ['pytest', 'pytest-cov'], - 'docs': ['sphinx', 'sphinx_rtd_theme', 'numpydoc'] + "tests": ["pytest", "pytest-cov"], + "docs": ["sphinx", "sphinx_rtd_theme", "numpydoc"], } PACKAGE_DATA = { - 'ramp_frontend': [ - os.path.join('templates', '*'), - os.path.join('static', 'css', 'style.css'), - os.path.join('static', 'css', 'themes', 'flat-blue.css'), - os.path.join('static', 'img', '*'), - os.path.join('static', 'img', 'backdrop', '*'), - os.path.join('static', 'img', 'partners', '*'), - os.path.join('static', 'img', 'powered_by', '*'), - os.path.join('static', 'js', '*'), - os.path.join('static', 'lib', 'css', '*'), - os.path.join('static', 'lib', 'fonts', '*'), - os.path.join('static', 'lib', 'img', '*'), - os.path.join('static', 'lib', 'js', '*'), + "ramp_frontend": [ + os.path.join("templates", "*"), + os.path.join("static", "css", "style.css"), + os.path.join("static", "css", "themes", "flat-blue.css"), + os.path.join("static", "img", "*"), + os.path.join("static", "img", "backdrop", "*"), + os.path.join("static", "img", "partners", "*"), + os.path.join("static", "img", "powered_by", "*"), + os.path.join("static", "js", "*"), + os.path.join("static", "lib", "css", "*"), + os.path.join("static", "lib", "fonts", "*"), + os.path.join("static", "lib", "img", "*"), + os.path.join("static", "lib", "js", "*"), ] } @@ -73,7 +84,5 @@ install_requires=INSTALL_REQUIRES, extras_require=EXTRAS_REQUIRE, python_requires=">=3.7", - entry_points={ - 'console_scripts': ['ramp-frontend = ramp_frontend.cli:start'] - } + entry_points={"console_scripts": ["ramp-frontend = ramp_frontend.cli:start"]}, ) diff --git a/ramp-utils/ramp_utils/__init__.py b/ramp-utils/ramp_utils/__init__.py index 998fdc545..84edfbfc8 100644 --- a/ramp-utils/ramp_utils/__init__.py +++ b/ramp-utils/ramp_utils/__init__.py @@ -7,10 +7,10 @@ from ._version import __version__ __all__ = [ - 'generate_flask_config', - 'generate_ramp_config', - 'generate_worker_config', - 'import_module_from_source', - 'read_config', - '__version__' + "generate_flask_config", + "generate_ramp_config", + "generate_worker_config", + "import_module_from_source", + "read_config", + "__version__", ] diff --git a/ramp-utils/ramp_utils/_version.py b/ramp-utils/ramp_utils/_version.py index 3fdc7fd91..dcd7d05fa 100644 --- a/ramp-utils/ramp_utils/_version.py +++ b/ramp-utils/ramp_utils/_version.py @@ -21,4 +21,4 @@ # 'X.Y.dev0' is the canonical version of 'X.Y.dev' # -__version__ = '0.9.0.dev0' +__version__ = "0.9.0.dev0" diff --git a/ramp-utils/ramp_utils/cli.py b/ramp-utils/ramp_utils/cli.py index 184d8ee10..832410184 100644 --- a/ramp-utils/ramp_utils/cli.py +++ b/ramp-utils/ramp_utils/cli.py @@ -14,7 +14,7 @@ HERE = os.path.dirname(__file__) -CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) +CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @click.group(context_settings=CONTEXT_SETTINGS) @@ -29,18 +29,24 @@ def main(): @main.command() -@click.option("--deployment-dir", default='.', show_default=True, - help='The directory where to create a config file.') -@click.option('--force', is_flag=True, - help='Whether or not to potentially overwrite the ' - 'repositories, problem and event in the database.') +@click.option( + "--deployment-dir", + default=".", + show_default=True, + help="The directory where to create a config file.", +) +@click.option( + "--force", + is_flag=True, + help="Whether or not to potentially overwrite the " + "repositories, problem and event in the database.", +) def init(deployment_dir, force): """Initialize the deployment directory with a template config.yml""" - template = os.path.join(HERE, 'template', 'database_config_template.yml') - destination = os.path.join(deployment_dir, 'config.yml') + template = os.path.join(HERE, "template", "database_config_template.yml") + destination = os.path.join(deployment_dir, "config.yml") if os.path.isfile(destination) and not force: - click.echo( - "Config file already exists. Specify --force to overwrite it.") + click.echo("Config file already exists. Specify --force to overwrite it.") return shutil.copy(template, destination) click.echo("Created {}".format(destination)) @@ -48,17 +54,24 @@ def init(deployment_dir, force): @main.command() -@click.option("--name", help='The name of the event.', required=True) -@click.option("--deployment-dir", default='.', show_default=True, - help='The directory where to create a config file.') -@click.option('--force', is_flag=True, - help='Whether or not to potentially overwrite the ' - 'repositories, problem and event in the database.') +@click.option("--name", help="The name of the event.", required=True) +@click.option( + "--deployment-dir", + default=".", + show_default=True, + help="The directory where to create a config file.", +) +@click.option( + "--force", + is_flag=True, + help="Whether or not to potentially overwrite the " + "repositories, problem and event in the database.", +) def init_event(name, deployment_dir, force): """Initialize the event directory with a template config.yml""" # create directories - events_dir = os.path.join(deployment_dir, 'events') + events_dir = os.path.join(deployment_dir, "events") if not os.path.isdir(events_dir): os.mkdir(events_dir) @@ -68,18 +81,18 @@ def init_event(name, deployment_dir, force): shutil.rmtree(event_dir) else: click.echo( - "{} already exists. Specify --force to overwrite it.".format( - event_dir)) + "{} already exists. Specify --force to overwrite it.".format(event_dir) + ) return os.mkdir(event_dir) # copy + edit config template - template = os.path.join(HERE, 'template', 'ramp_config_template.yml') - destination = os.path.join(event_dir, 'config.yml') - with open(destination, 'w') as dest: - with open(template, 'r') as src: + template = os.path.join(HERE, "template", "ramp_config_template.yml") + destination = os.path.join(event_dir, "config.yml") + with open(destination, "w") as dest: + with open(template, "r") as src: content = src.read() - content = content.replace('', name) + content = content.replace("", name) dest.write(content) click.echo("Created {}".format(destination)) @@ -87,18 +100,30 @@ def init_event(name, deployment_dir, force): @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file in YAML format containing the database ' - 'information') -@click.option("--event-config", default='config.yml', show_default=True, - help='Configuration file in YAML format containing the RAMP ' - 'information') -@click.option("--cloning/--no-cloning", default=True, show_default=True, - help='Whether or not to clone the RAMP kit and data ' - 'repositories.') -@click.option('--force', is_flag=True, - help='Whether or not to potentially overwrite the ' - 'repositories, problem and event in the database.') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file in YAML format containing the database " "information", +) +@click.option( + "--event-config", + default="config.yml", + show_default=True, + help="Configuration file in YAML format containing the RAMP " "information", +) +@click.option( + "--cloning/--no-cloning", + default=True, + show_default=True, + help="Whether or not to clone the RAMP kit and data " "repositories.", +) +@click.option( + "--force", + is_flag=True, + help="Whether or not to potentially overwrite the " + "repositories, problem and event in the database.", +) def deploy_event(config, event_config, cloning, force): """Deploy event (add problem and event to the database, optionally clone kit and data) @@ -107,33 +132,44 @@ def deploy_event(config, event_config, cloning, force): @main.command() -@click.option("--config", default='config.yml', show_default=True, - help='Configuration file in YAML format containing the database ' - 'information') -@click.option("--event-config", default='config.yml', show_default=True, - help='Configuration file in YAML format containing the RAMP ' - 'information') +@click.option( + "--config", + default="config.yml", + show_default=True, + help="Configuration file in YAML format containing the database " "information", +) +@click.option( + "--event-config", + default="config.yml", + show_default=True, + help="Configuration file in YAML format containing the RAMP " "information", +) def create_conda_env(config, event_config): """Create the conda environment for a specific event""" conda_env_name = read_config(event_config)["worker"]["conda_env"] ramp_config = generate_ramp_config(event_config, database_config=config) - path_environment_file = os.path.join( - ramp_config["ramp_kit_dir"], "environment.yml" - ) - subprocess.run( - ["conda", "create", "--name", conda_env_name, "--yes"] - ) + path_environment_file = os.path.join(ramp_config["ramp_kit_dir"], "environment.yml") + subprocess.run(["conda", "create", "--name", conda_env_name, "--yes"]) subprocess.run( - ["conda", "env", "update", - "--name", conda_env_name, - "--file", path_environment_file] + [ + "conda", + "env", + "update", + "--name", + conda_env_name, + "--file", + path_environment_file, + ] ) @main.command() -@click.option("--event-config", default='config.yml', show_default=True, - help='Configuration file in YAML format containing the RAMP ' - 'information') +@click.option( + "--event-config", + default="config.yml", + show_default=True, + help="Configuration file in YAML format containing the RAMP " "information", +) def update_conda_env(event_config): """Update the conda environment for a specific event""" conda_env = read_config(event_config, filter_section="worker")["conda_env"] @@ -148,24 +184,27 @@ def update_conda_env(event_config): raise ValueError(stderr.decode("utf-8")) conda_info = json.loads(stdout) - if conda_env == 'base': - python_bin_path = os.path.join(conda_info['envs'][0], 'bin') + if conda_env == "base": + python_bin_path = os.path.join(conda_info["envs"][0], "bin") else: - envs_path = conda_info['envs'][1:] + envs_path = conda_info["envs"][1:] if not envs_path: - raise ValueError('Only the conda base environment exist. You ' - 'need to create the "{}" conda environment ' - 'to use it.'.format(conda_env)) + raise ValueError( + "Only the conda base environment exist. You " + 'need to create the "{}" conda environment ' + "to use it.".format(conda_env) + ) is_env_found = False for env in envs_path: if conda_env == os.path.split(env)[-1]: is_env_found = True - python_bin_path = os.path.join(env, 'bin') + python_bin_path = os.path.join(env, "bin") break if not is_env_found: - raise ValueError('The specified conda environment {} does not ' - 'exist. You need to create it.' - .format(conda_env)) + raise ValueError( + "The specified conda environment {} does not " + "exist. You need to create it.".format(conda_env) + ) # update the conda packages subprocess.run(["conda", "update", "--name", conda_env, "--all", "--yes"]) @@ -187,7 +226,7 @@ def update_conda_env(event_config): # update the pip packages subprocess.run( - [os.path.join(python_bin_path, 'pip'), "install", "-U"] + pip_packages + [os.path.join(python_bin_path, "pip"), "install", "-U"] + pip_packages ) @@ -195,5 +234,5 @@ def start(): main() -if __name__ == '__main__': +if __name__ == "__main__": start() diff --git a/ramp-utils/ramp_utils/config_parser.py b/ramp-utils/ramp_utils/config_parser.py index 52f17fd52..cc0f2b082 100644 --- a/ramp-utils/ramp_utils/config_parser.py +++ b/ramp-utils/ramp_utils/config_parser.py @@ -2,10 +2,16 @@ import yaml REQUIRED_KEYS = { - 'sqlalchemy': {'drivername', 'username', 'password', 'host', 'port', - 'database'}, - 'ramp': {'problem_name', 'event_name', 'event_title', 'event_is_public'}, - 'worker': {'worker_type'} + "sqlalchemy": { + "drivername", + "username", + "password", + "host", + "port", + "database", + }, + "ramp": {"problem_name", "event_name", "event_title", "event_is_public"}, + "worker": {"worker_type"}, } @@ -31,7 +37,7 @@ def read_config(config_file, filter_section=None, check_requirements=True): config : dict Configuration parsed as a dictionary. """ - with open(config_file, 'r') as f: + with open(config_file, "r") as f: config = yaml.safe_load(f) # if a single string is given, we will later unpack remove the first layer @@ -45,25 +51,28 @@ def read_config(config_file, filter_section=None, check_requirements=True): if sec not in config: raise ValueError( 'The section "{}" is not in the "{}" file. Got these ' - 'sections instead {}.' - .format(sec, os.path.basename(config_file), - list(config.keys())) + "sections instead {}.".format( + sec, os.path.basename(config_file), list(config.keys()) + ) ) - config = {key: value - for key, value in config.items() - if filter_section is None or key in filter_section} + config = { + key: value + for key, value in config.items() + if filter_section is None or key in filter_section + } if check_requirements: for section_name, required_field in REQUIRED_KEYS.items(): if section_name in config: - missing_parameters = required_field.difference( - config[section_name]) + missing_parameters = required_field.difference(config[section_name]) if missing_parameters: raise ValueError( 'The section "{}" in the "{}" file is missing the ' - 'required parameters {}.' - .format(section_name, os.path.basename(config_file), - missing_parameters) + "required parameters {}.".format( + section_name, + os.path.basename(config_file), + missing_parameters, + ) ) if unpack: diff --git a/ramp-utils/ramp_utils/datasets.py b/ramp-utils/ramp_utils/datasets.py index e0bfa3621..193c8423f 100644 --- a/ramp-utils/ramp_utils/datasets.py +++ b/ramp-utils/ramp_utils/datasets.py @@ -4,10 +4,7 @@ import os from urllib import request -OSFRemoteMetaData = namedtuple( - "OSFRemoteMetaData", - ["filename", "id", "revision"] -) +OSFRemoteMetaData = namedtuple("OSFRemoteMetaData", ["filename", "id", "revision"]) def _sha256(path): @@ -42,16 +39,13 @@ def fetch_from_osf(path_data, metadata, token=None): if not os.path.exists(path_data): os.makedirs(path_data) for file_info in metadata: - file_info_url = ( - f"https://api.osf.io/v2/files/{file_info.id}/" - ) + file_info_url = f"https://api.osf.io/v2/files/{file_info.id}/" req = request.Request(file_info_url) if token is not None: req.add_header("Authorization", f"Bearer {token}") response = request.urlopen(req) info = json.loads(response.read()) - original_checksum = \ - info["data"]["attributes"]["extra"]["hashes"]["sha256"] + original_checksum = info["data"]["attributes"]["extra"]["hashes"]["sha256"] filename = os.path.join(path_data, file_info.filename) if os.path.exists(filename): if _sha256(filename) == original_checksum: @@ -59,8 +53,7 @@ def fetch_from_osf(path_data, metadata, token=None): continue osf_url = ( - f"https://osf.io/download/" - f"{file_info.id}/?revision={file_info.revision}" + f"https://osf.io/download/" f"{file_info.id}/?revision={file_info.revision}" ) req = request.Request(osf_url) if token is not None: @@ -70,5 +63,6 @@ def fetch_from_osf(path_data, metadata, token=None): raise RuntimeError(response.read()) with open(filename, "wb") as fid: fid.write(response.read()) - assert _sha256(filename) == original_checksum, \ - f"{filename} was corrupted during download" + assert ( + _sha256(filename) == original_checksum + ), f"{filename} was corrupted during download" diff --git a/ramp-utils/ramp_utils/deploy.py b/ramp-utils/ramp_utils/deploy.py index 9a135ce3b..8c7aab8cb 100644 --- a/ramp-utils/ramp_utils/deploy.py +++ b/ramp-utils/ramp_utils/deploy.py @@ -32,70 +32,79 @@ def deploy_ramp_event(config, event_config, setup_ramp_repo=True, force=False): Whether or not to potentially overwrite the repositories, problem and event in the database. """ - database_config = read_config(config, filter_section='sqlalchemy') + database_config = read_config(config, filter_section="sqlalchemy") ramp_config = generate_ramp_config(event_config, config) with session_scope(database_config) as session: setup_files_extension_type(session) if setup_ramp_repo: - setup_ramp_kit_ramp_data( - ramp_config, ramp_config['problem_name'], force - ) + setup_ramp_kit_ramp_data(ramp_config, ramp_config["problem_name"], force) else: # we do not clone the repository but we need to convert the # notebook to html current_directory = os.getcwd() - problem_kit_path = ramp_config['ramp_kit_dir'] + problem_kit_path = ramp_config["ramp_kit_dir"] os.chdir(problem_kit_path) - subprocess.check_output(["jupyter", "nbconvert", "--to", "html", - "{}_starting_kit.ipynb" - .format(ramp_config['problem_name'])]) + subprocess.check_output( + [ + "jupyter", + "nbconvert", + "--to", + "html", + "{}_starting_kit.ipynb".format(ramp_config["problem_name"]), + ] + ) # delete this line since it trigger in the front-end # (try to open execute "custom.css".) _delete_line_from_file( - "{}_starting_kit.html".format(ramp_config['problem_name']), - '\n' + "{}_starting_kit.html".format(ramp_config["problem_name"]), + '\n', ) os.chdir(current_directory) # check if the repository exists - problem = get_problem(session, ramp_config['problem_name']) + problem = get_problem(session, ramp_config["problem_name"]) if problem is None: - add_problem(session, ramp_config['problem_name'], - ramp_config['ramp_kit_dir'], - ramp_config['ramp_data_dir']) + add_problem( + session, + ramp_config["problem_name"], + ramp_config["ramp_kit_dir"], + ramp_config["ramp_data_dir"], + ) else: - if ((ramp_config['ramp_kit_dir'] != problem.path_ramp_kit or - ramp_config['ramp_data_dir'] != problem.path_ramp_data) and - not force): + if ( + ramp_config["ramp_kit_dir"] != problem.path_ramp_kit + or ramp_config["ramp_data_dir"] != problem.path_ramp_data + ) and not force: raise ValueError( - 'The RAMP problem already exists in the database. The path' - ' to the kit or to the data is different. You need to set' + "The RAMP problem already exists in the database. The path" + " to the kit or to the data is different. You need to set" ' "force=True" if you want to overwrite these parameters.' ) if setup_ramp_repo: setup_ramp_kit_ramp_data( - ramp_config, ramp_config['problem_name'], force + ramp_config, ramp_config["problem_name"], force ) if force: - add_problem(session, ramp_config['problem_name'], - ramp_config['ramp_kit_dir'], - ramp_config['ramp_data_dir'], - force) + add_problem( + session, + ramp_config["problem_name"], + ramp_config["ramp_kit_dir"], + ramp_config["ramp_data_dir"], + force, + ) - if not os.path.exists(ramp_config['ramp_submissions_dir']): - os.makedirs(ramp_config['ramp_submissions_dir']) + if not os.path.exists(ramp_config["ramp_submissions_dir"]): + os.makedirs(ramp_config["ramp_submissions_dir"]) # create a folder in the ramp-kit directory to store the archive - archive_dir = os.path.abspath(os.path.join( - ramp_config['ramp_kit_dir'], 'events_archived' - )) + archive_dir = os.path.abspath( + os.path.join(ramp_config["ramp_kit_dir"], "events_archived") + ) if not os.path.exists(archive_dir): os.makedirs(archive_dir) - zip_filename = os.path.join( - archive_dir, ramp_config["event_name"] + ".zip" - ) - with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf: - for root, dirs, files in os.walk(ramp_config['ramp_kit_dir']): + zip_filename = os.path.join(archive_dir, ramp_config["event_name"] + ".zip") + with zipfile.ZipFile(zip_filename, "w", zipfile.ZIP_DEFLATED) as zipf: + for root, dirs, files in os.walk(ramp_config["ramp_kit_dir"]): if archive_dir not in os.path.abspath(root): for f in files: path_file = os.path.join(root, f) @@ -103,13 +112,16 @@ def deploy_ramp_event(config, event_config, setup_ramp_repo=True, force=False): path_file, os.path.relpath( path_file, start=ramp_config["ramp_kit_dir"] - ) + ), ) - add_event(session, ramp_config['problem_name'], - ramp_config['event_name'], - ramp_config['event_title'], - ramp_config['sandbox_name'], - ramp_config['ramp_submissions_dir'], - ramp_config['event_is_public'], - force) + add_event( + session, + ramp_config["problem_name"], + ramp_config["event_name"], + ramp_config["event_title"], + ramp_config["sandbox_name"], + ramp_config["ramp_submissions_dir"], + ramp_config["event_is_public"], + force, + ) diff --git a/ramp-utils/ramp_utils/frontend.py b/ramp-utils/ramp_utils/frontend.py index 999397ae7..17fae2f7e 100644 --- a/ramp-utils/ramp_utils/frontend.py +++ b/ramp-utils/ramp_utils/frontend.py @@ -2,17 +2,17 @@ DEFAULT_CONFIG = { - 'WTF_CSRF_ENABLED': True, - 'LOG_FILENAME': 'None', - 'MAX_CONTENT_LENGTH': 1073741824, - 'SQLALCHEMY_TRACK_MODIFICATIONS': False, - 'TRACK_USER_INTERACTION': False, - 'TRACK_CREDITS': False, - 'DOMAIN_NAME': 'localhost', - 'LOGIN_INSTRUCTIONS': None, - 'SIGN_UP_INSTRUCTIONS': None, - 'SIGN_UP_ASK_SOCIAL_MEDIA': False, - 'PRIVACY_POLICY_PAGE': None + "WTF_CSRF_ENABLED": True, + "LOG_FILENAME": "None", + "MAX_CONTENT_LENGTH": 1073741824, + "SQLALCHEMY_TRACK_MODIFICATIONS": False, + "TRACK_USER_INTERACTION": False, + "TRACK_CREDITS": False, + "DOMAIN_NAME": "localhost", + "LOGIN_INSTRUCTIONS": None, + "SIGN_UP_INSTRUCTIONS": None, + "SIGN_UP_ASK_SOCIAL_MEDIA": False, + "PRIVACY_POLICY_PAGE": None, } @@ -22,8 +22,8 @@ def _read_if_html_path(txt: str) -> str: If the input is a path to a valid HTML file, read it. Otherwise return the input """ - if txt and txt.endswith('.html'): - with open(txt, 'rt') as fh: + if txt and txt.endswith(".html"): + with open(txt, "rt") as fh: txt = fh.read() return txt @@ -42,21 +42,26 @@ def generate_flask_config(config): The configuration for the RAMP worker. """ if isinstance(config, str): - config = read_config(config, filter_section=['flask', 'sqlalchemy']) + config = read_config(config, filter_section=["flask", "sqlalchemy"]) flask_config = DEFAULT_CONFIG.copy() - user_flask_config = { - key.upper(): value for key, value in config['flask'].items()} + user_flask_config = {key.upper(): value for key, value in config["flask"].items()} flask_config.update(user_flask_config) - for key in ['LOGIN_INSTRUCTIONS', 'SIGN_UP_INSTRUCTIONS', - 'PRIVACY_POLICY_PAGE']: + for key in [ + "LOGIN_INSTRUCTIONS", + "SIGN_UP_INSTRUCTIONS", + "PRIVACY_POLICY_PAGE", + ]: flask_config[key] = _read_if_html_path(flask_config[key]) - database_config = config['sqlalchemy'] - flask_config['SQLALCHEMY_DATABASE_URI'] = \ - ('{}://{}:{}@{}:{}/{}' - .format(database_config['drivername'], database_config['username'], - database_config['password'], database_config['host'], - database_config['port'], database_config['database'])) + database_config = config["sqlalchemy"] + flask_config["SQLALCHEMY_DATABASE_URI"] = "{}://{}:{}@{}:{}/{}".format( + database_config["drivername"], + database_config["username"], + database_config["password"], + database_config["host"], + database_config["port"], + database_config["database"], + ) return flask_config diff --git a/ramp-utils/ramp_utils/ramp.py b/ramp-utils/ramp_utils/ramp.py index 63aab27eb..344b804f3 100644 --- a/ramp-utils/ramp_utils/ramp.py +++ b/ramp-utils/ramp_utils/ramp.py @@ -2,8 +2,14 @@ from .config_parser import read_config -MANDATORY_DICT_PARAMS = ('kit_dir', 'data_dir', 'submissions_dir', - 'sandbox_dir', 'predictions_dir', 'logs_dir') +MANDATORY_DICT_PARAMS = ( + "kit_dir", + "data_dir", + "submissions_dir", + "sandbox_dir", + "predictions_dir", + "logs_dir", +) def _create_defaults(config, key, path_config): @@ -11,22 +17,16 @@ def _create_defaults(config, key, path_config): already. """ default_mapping = { - 'kit_dir': os.path.join( - path_config, 'ramp-kits', config['problem_name'] + "kit_dir": os.path.join(path_config, "ramp-kits", config["problem_name"]), + "data_dir": os.path.join(path_config, "ramp-data", config["problem_name"]), + "submissions_dir": os.path.join( + path_config, "events", config["event_name"], "submissions" ), - 'data_dir': os.path.join( - path_config, 'ramp-data', config['problem_name'] + "predictions_dir": os.path.join( + path_config, "events", config["event_name"], "predictions" ), - 'submissions_dir': os.path.join( - path_config, 'events', config['event_name'], 'submissions' - ), - 'predictions_dir': os.path.join( - path_config, 'events', config['event_name'], 'predictions' - ), - 'logs_dir': os.path.join( - path_config, 'events', config['event_name'], 'logs' - ), - 'sandbox_dir': 'starting_kit' + "logs_dir": os.path.join(path_config, "events", config["event_name"], "logs"), + "sandbox_dir": "starting_kit", } if key not in config: return default_mapping[key] @@ -52,70 +52,59 @@ def generate_ramp_config(event_config, database_config=None): The configuration for the RAMP worker. """ if isinstance(event_config, str): - if (database_config is None or - not isinstance(database_config, str)): + if database_config is None or not isinstance(database_config, str): raise ValueError( 'When "event_config" corresponds to the filename of the ' - 'configuration, you need to provide the filename of the ' + "configuration, you need to provide the filename of the " 'database as well, by assigning "database_config".' ) event_config = read_config(event_config) - config = event_config['ramp'] + config = event_config["ramp"] - path_config = os.path.dirname( - os.path.abspath(database_config) - ) + path_config = os.path.dirname(os.path.abspath(database_config)) else: - if 'ramp' in event_config.keys(): - config = event_config['ramp'] + if "ramp" in event_config.keys(): + config = event_config["ramp"] else: config = event_config if not all([key in config.keys() for key in MANDATORY_DICT_PARAMS]): raise ValueError( 'When "event_config" is a dictionary, you need to provide all ' - 'following keys: {}'.format(MANDATORY_DICT_PARAMS) + "following keys: {}".format(MANDATORY_DICT_PARAMS) ) - path_config = '' + path_config = "" ramp_config = {} # mandatory parameters - ramp_config['problem_name'] = config['problem_name'] - ramp_config['event_name'] = config['event_name'] - ramp_config['event_title'] = config['event_title'] - ramp_config['event_is_public'] = config['event_is_public'] + ramp_config["problem_name"] = config["problem_name"] + ramp_config["event_name"] = config["event_name"] + ramp_config["event_title"] = config["event_title"] + ramp_config["event_is_public"] = config["event_is_public"] # parameters which can be built by default if given a string - ramp_config['ramp_kit_dir'] = _create_defaults( - config, 'kit_dir', path_config - ) - ramp_config['ramp_data_dir'] = _create_defaults( - config, 'data_dir', path_config + ramp_config["ramp_kit_dir"] = _create_defaults(config, "kit_dir", path_config) + ramp_config["ramp_data_dir"] = _create_defaults(config, "data_dir", path_config) + ramp_config["ramp_submissions_dir"] = _create_defaults( + config, "submissions_dir", path_config ) - ramp_config['ramp_submissions_dir'] = _create_defaults( - config, 'submissions_dir', path_config - ) - ramp_config['sandbox_name'] = _create_defaults( - config, 'sandbox_dir', '' - ) - ramp_config['ramp_predictions_dir'] = _create_defaults( - config, 'predictions_dir', path_config - ) - ramp_config['ramp_logs_dir'] = _create_defaults( - config, 'logs_dir', path_config + ramp_config["sandbox_name"] = _create_defaults(config, "sandbox_dir", "") + ramp_config["ramp_predictions_dir"] = _create_defaults( + config, "predictions_dir", path_config ) + ramp_config["ramp_logs_dir"] = _create_defaults(config, "logs_dir", path_config) # parameters inferred from the previous one - ramp_config['ramp_sandbox_dir'] = os.path.join( - ramp_config['ramp_kit_dir'], 'submissions', ramp_config['sandbox_name'] + ramp_config["ramp_sandbox_dir"] = os.path.join( + ramp_config["ramp_kit_dir"], "submissions", ramp_config["sandbox_name"] ) - ramp_config['ramp_kit_submissions_dir'] = os.path.join( - ramp_config['ramp_kit_dir'], 'submissions' + ramp_config["ramp_kit_submissions_dir"] = os.path.join( + ramp_config["ramp_kit_dir"], "submissions" ) # parameters only used with DaskWorker - if event_config.get('worker', {}).get('worker_type', None) == 'dask': - ramp_config['dask_scheduler'] = ramp_config.get( - 'worker', {} - ).get('dask_scheduler', None) + if event_config.get("worker", {}).get("worker_type", None) == "dask": + ramp_config["dask_scheduler"] = ramp_config.get("worker", {}).get( + "dask_scheduler", None + ) return ramp_config diff --git a/ramp-utils/ramp_utils/ramp_cli.py b/ramp-utils/ramp_utils/ramp_cli.py index 4564a0f5d..acdbd15d5 100644 --- a/ramp-utils/ramp_utils/ramp_cli.py +++ b/ramp-utils/ramp_utils/ramp_cli.py @@ -11,15 +11,15 @@ class RAMPParser(argparse.ArgumentParser): - @property # type: ignore def epilog(self): """Add subcommands to epilog on request Avoids searching PATH for subcommands unless help output is requested. """ - return 'Available subcommands:\n - {}'.format( - '\n - '.join(list_subcommands())) + return "Available subcommands:\n - {}".format( + "\n - ".join(list_subcommands()) + ) @epilog.setter def epilog(self, x): @@ -31,13 +31,15 @@ def ramp_parser(): parser = RAMPParser( description="RAMP: collaborative data science challenges", # use raw formatting to preserver newlines in the epilog - formatter_class=argparse.RawDescriptionHelpFormatter) + formatter_class=argparse.RawDescriptionHelpFormatter, + ) group = parser.add_mutually_exclusive_group(required=True) # don't use argparse's version action because it prints to stderr on py2 # group.add_argument('--version', action='store_true', # help="show the ramp command's version and exit") - group.add_argument('subcommand', type=str, nargs='?', - help='the subcommand to launch') + group.add_argument( + "subcommand", type=str, nargs="?", help="the subcommand to launch" + ) return parser @@ -58,19 +60,18 @@ def list_subcommands(): except OSError: continue for name in names: - if name.startswith('ramp-'): - if sys.platform.startswith('win'): + if name.startswith("ramp-"): + if sys.platform.startswith("win"): # remove file-extension on Windows name = os.path.splitext(name)[0] - subcommand_tuples.add(tuple(name.split('-')[1:])) + subcommand_tuples.add(tuple(name.split("-")[1:])) # build a set of subcommand strings, excluding subcommands whose parents # are defined subcommands = set() # Only include `jupyter-foo-bar` if `jupyter-foo` is not already present for sub_tup in subcommand_tuples: - if not any(sub_tup[:i] in subcommand_tuples - for i in range(1, len(sub_tup))): - subcommands.add('-'.join(sub_tup)) + if not any(sub_tup[:i] in subcommand_tuples for i in range(1, len(sub_tup))): + subcommands.add("-".join(sub_tup)) return sorted(subcommands) @@ -80,7 +81,7 @@ def _execvp(cmd, argv): Python provides execvp on Windows, but its behavior is problematic (Python bug#9148). """ - if sys.platform.startswith('win'): + if sys.platform.startswith("win"): # PATH is ignored when shell=False, # so rely on shutil.which try: @@ -89,11 +90,12 @@ def _execvp(cmd, argv): from .utils.shutil_which import which cmd_path = which(cmd) if cmd_path is None: - raise OSError('%r not found' % cmd, errno.ENOENT) + raise OSError("%r not found" % cmd, errno.ENOENT) p = Popen([cmd_path] + argv[1:]) # Don't raise KeyboardInterrupt in the parent process. # Set this after spawning, to avoid subprocess inheriting handler. import signal + signal.signal(signal.SIGINT, signal.SIG_IGN) p.wait() sys.exit(p.returncode) @@ -113,21 +115,22 @@ def _path_with_self(): # include realpath, if `ramp` is a symlinkxecvp(command, sys.argv[1:]) scripts.append(os.path.realpath(scripts[0])) - path_list = (os.environ.get('PATH') or os.defpath).split(os.pathsep) + path_list = (os.environ.get("PATH") or os.defpath).split(os.pathsep) for script in scripts: bindir = os.path.dirname(script) - if (os.path.isdir(bindir) - and os.access(script, os.X_OK)): # only if it's a script + if os.path.isdir(bindir) and os.access( + script, os.X_OK + ): # only if it's a script # ensure executable's dir is on PATH # avoids missing subcommands when ramp is run via absolute path path_list.insert(0, bindir) - os.environ['PATH'] = os.pathsep.join(path_list) + os.environ["PATH"] = os.pathsep.join(path_list) return path_list def main(): _path_with_self() # ensure executable is on PATH - if len(sys.argv) > 1 and not sys.argv[1].startswith('-'): + if len(sys.argv) > 1 and not sys.argv[1].startswith("-"): # Don't parse if a subcommand is given # Avoids argparse gobbling up args passed to subcommand, such as `-h`. subcommand = sys.argv[1] @@ -144,12 +147,12 @@ def main(): parser.print_usage(file=sys.stderr) sys.exit("subcommand is required") - command = 'ramp-' + subcommand + command = "ramp-" + subcommand try: _execvp(command, sys.argv[1:]) except OSError as e: sys.exit("Error executing ramp command %r: %s" % (subcommand, e)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ramp-utils/ramp_utils/testing.py b/ramp-utils/ramp_utils/testing.py index 934cefd19..5182c5e90 100644 --- a/ramp-utils/ramp_utils/testing.py +++ b/ramp-utils/ramp_utils/testing.py @@ -11,7 +11,7 @@ def database_config_template(): filename : str The database configuration filename. """ - return os.path.join(HERE, 'template', 'database_config.yml') + return os.path.join(HERE, "template", "database_config.yml") def ramp_config_template(): @@ -22,7 +22,7 @@ def ramp_config_template(): filename : str The RAMP configuration filename. """ - return os.path.join(HERE, 'template', 'ramp_config.yml') + return os.path.join(HERE, "template", "ramp_config.yml") def ramp_aws_config_template(): @@ -33,4 +33,4 @@ def ramp_aws_config_template(): filename : str The RAMP configuration on AWS filename. """ - return os.path.join(HERE, 'template', 'ramp_config_aws.yml') + return os.path.join(HERE, "template", "ramp_config_aws.yml") diff --git a/ramp-utils/ramp_utils/tests/test_cli.py b/ramp-utils/ramp_utils/tests/test_cli.py index 076b56d58..45f56572d 100644 --- a/ramp-utils/ramp_utils/tests/test_cli.py +++ b/ramp-utils/ramp_utils/tests/test_cli.py @@ -20,14 +20,14 @@ def deployment_dir(database_connection): ramp_config = read_config(ramp_config_template()) return os.path.commonpath( - [ramp_config['ramp']['kit_dir'], ramp_config['ramp']['data_dir']] + [ramp_config["ramp"]["kit_dir"], ramp_config["ramp"]["data_dir"]] ) def setup_function(function): ramp_config = read_config(ramp_config_template()) function.deployment_dir = os.path.commonpath( - [ramp_config['ramp']['kit_dir'], ramp_config['ramp']['data_dir']] + [ramp_config["ramp"]["kit_dir"], ramp_config["ramp"]["data_dir"]] ) @@ -36,7 +36,7 @@ def teardown_function(function): # FIXME: we are recreating the deployment directory but it should be # replaced by an temporary creation of folder. shutil.rmtree(function.deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) @@ -44,11 +44,9 @@ def test_setup_init(deployment_dir): try: os.mkdir(deployment_dir) runner = CliRunner() - result = runner.invoke(main, ['init', - '--deployment-dir', deployment_dir]) + result = runner.invoke(main, ["init", "--deployment-dir", deployment_dir]) assert result.exit_code == 0, result.output - result = runner.invoke(main, ['init', - '--deployment-dir', deployment_dir]) + result = runner.invoke(main, ["init", "--deployment-dir", deployment_dir]) assert result.exit_code == 0, result.output finally: shutil.rmtree(deployment_dir, ignore_errors=True) @@ -58,18 +56,39 @@ def test_setup_init_event(deployment_dir): try: os.mkdir(deployment_dir) runner = CliRunner() - result = runner.invoke(main, ['init-event', - '--name', 'iris_test', - '--deployment-dir', deployment_dir]) + result = runner.invoke( + main, + [ + "init-event", + "--name", + "iris_test", + "--deployment-dir", + deployment_dir, + ], + ) assert result.exit_code == 0, result.output - result = runner.invoke(main, ['init-event', - '--name', 'iris_test', - '--deployment-dir', deployment_dir]) + result = runner.invoke( + main, + [ + "init-event", + "--name", + "iris_test", + "--deployment-dir", + deployment_dir, + ], + ) assert result.exit_code == 0, result.output - result = runner.invoke(main, ['init-event', - '--name', 'iris_test', - '--deployment-dir', deployment_dir, - '--force']) + result = runner.invoke( + main, + [ + "init-event", + "--name", + "iris_test", + "--deployment-dir", + deployment_dir, + "--force", + ], + ) assert result.exit_code == 0, result.output finally: shutil.rmtree(deployment_dir, ignore_errors=True) @@ -77,23 +96,37 @@ def test_setup_init_event(deployment_dir): def test_deploy_ramp_event(): runner = CliRunner() - result = runner.invoke(main, ['deploy-event', - '--config', database_config_template(), - '--event-config', ramp_config_template()]) + result = runner.invoke( + main, + [ + "deploy-event", + "--config", + database_config_template(), + "--event-config", + ramp_config_template(), + ], + ) assert result.exit_code == 0, result.output - result = runner.invoke(main, ['deploy-event', - '--config', database_config_template(), - '--event-config', ramp_config_template(), - '--force']) + result = runner.invoke( + main, + [ + "deploy-event", + "--config", + database_config_template(), + "--event-config", + ramp_config_template(), + "--force", + ], + ) assert result.exit_code == 0, result.output @pytest.mark.parametrize( - 'subcommand', [None, 'database', 'frontend', 'launch', 'setup'] + "subcommand", [None, "database", "frontend", "launch", "setup"] ) def test_ramp_cli(subcommand): - cmd = ['ramp'] + cmd = ["ramp"] if subcommand is not None: cmd += [subcommand] - cmd += ['-h'] + cmd += ["-h"] subprocess.check_output(cmd, env=os.environ.copy()) diff --git a/ramp-utils/ramp_utils/tests/test_config_parser.py b/ramp-utils/ramp_utils/tests/test_config_parser.py index 3e9a8609e..98d09f3c4 100644 --- a/ramp-utils/ramp_utils/tests/test_config_parser.py +++ b/ramp-utils/ramp_utils/tests/test_config_parser.py @@ -7,33 +7,47 @@ @pytest.fixture def simple_config(database_connection): - data = {'sqlalchemy': {'username': 'mrramp', 'password': 'mrramp'}, - 'ramp': {'event_name': 'iris_test'}} - with tempfile.NamedTemporaryFile(mode='w', suffix='.yml') as config_file: + data = { + "sqlalchemy": {"username": "mrramp", "password": "mrramp"}, + "ramp": {"event_name": "iris_test"}, + } + with tempfile.NamedTemporaryFile(mode="w", suffix=".yml") as config_file: yaml.dump(data, config_file, default_flow_style=False) yield config_file.name @pytest.mark.parametrize( "filter_section, expected_config", - [(None, {'sqlalchemy': {'username': 'mrramp', 'password': 'mrramp'}, - 'ramp': {'event_name': 'iris_test'}}), - (['ramp'], {'ramp': {'event_name': 'iris_test'}}), - ('ramp', {'event_name': 'iris_test'})] + [ + ( + None, + { + "sqlalchemy": {"username": "mrramp", "password": "mrramp"}, + "ramp": {"event_name": "iris_test"}, + }, + ), + (["ramp"], {"ramp": {"event_name": "iris_test"}}), + ("ramp", {"event_name": "iris_test"}), + ], ) def test_read_config_filtering(simple_config, filter_section, expected_config): - config = read_config(simple_config, filter_section=filter_section, - check_requirements=False) + config = read_config( + simple_config, filter_section=filter_section, check_requirements=False + ) assert config == expected_config @pytest.mark.parametrize( "filter_section, check_requirements, err_msg", - [('unknown', False, 'The section "unknown" is not in'), - (None, True, 'The section "sqlalchemy" in the')] + [ + ("unknown", False, 'The section "unknown" is not in'), + (None, True, 'The section "sqlalchemy" in the'), + ], ) -def test_read_config_error(simple_config, filter_section, check_requirements, - err_msg): +def test_read_config_error(simple_config, filter_section, check_requirements, err_msg): with pytest.raises(ValueError, match=err_msg): - read_config(simple_config, filter_section=filter_section, - check_requirements=check_requirements) + read_config( + simple_config, + filter_section=filter_section, + check_requirements=check_requirements, + ) diff --git a/ramp-utils/ramp_utils/tests/test_deploy.py b/ramp-utils/ramp_utils/tests/test_deploy.py index b87902fc9..04e609bbd 100644 --- a/ramp-utils/ramp_utils/tests/test_deploy.py +++ b/ramp-utils/ramp_utils/tests/test_deploy.py @@ -34,10 +34,10 @@ def session_scope_function(database_connection): # FIXME: we are recreating the deployment directory but it should be # replaced by an temporary creation of folder. deployment_dir = os.path.commonpath( - [ramp_config['ramp']['kit_dir'], ramp_config['ramp']['data_dir']] + [ramp_config["ramp"]["kit_dir"], ramp_config["ramp"]["data_dir"]] ) shutil.rmtree(deployment_dir, ignore_errors=True) - db, _ = setup_db(database_config['sqlalchemy']) + db, _ = setup_db(database_config["sqlalchemy"]) Model.metadata.drop_all(db) @@ -46,43 +46,47 @@ def test_deploy_ramp_event_options(session_scope_function): ramp_config = generate_ramp_config(read_config(ramp_config_template())) deploy_ramp_event(database_config_template(), ramp_config_template()) # deploy again by forcing the deployment - deploy_ramp_event( - database_config_template(), ramp_config_template(), force=True - ) + deploy_ramp_event(database_config_template(), ramp_config_template(), force=True) # do not deploy the kit to trigger the error in the problem with we don't # force the deployment - msg_err = 'The RAMP problem already exists in the database.' + msg_err = "The RAMP problem already exists in the database." with pytest.raises(ValueError, match=msg_err): - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: # if one of the ramp-kit or ramp-data folders changed - problem = get_problem(session, 'iris') - problem.path_ramp_kit = problem.path_ramp_kit + '_xxx' + problem = get_problem(session, "iris") + problem.path_ramp_kit = problem.path_ramp_kit + "_xxx" session.commit() deploy_ramp_event( - database_config_template(), ramp_config_template(), - setup_ramp_repo=False, force=False + database_config_template(), + ramp_config_template(), + setup_ramp_repo=False, + force=False, ) - problem = get_problem(session, 'iris') - problem.path_ramp_kit = ramp_config['ramp_kit_dir'] - problem.path_ramp_data = problem.path_ramp_data + '_xxx' + problem = get_problem(session, "iris") + problem.path_ramp_kit = ramp_config["ramp_kit_dir"] + problem.path_ramp_data = problem.path_ramp_data + "_xxx" session.commit() deploy_ramp_event( - database_config_template(), ramp_config_template(), - setup_ramp_repo=False, force=False + database_config_template(), + ramp_config_template(), + setup_ramp_repo=False, + force=False, ) - msg_err = 'Attempting to overwrite existing event' + msg_err = "Attempting to overwrite existing event" with pytest.raises(ValueError, match=msg_err): - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: # if the problem is the same, then the event should be overwritten - problem = get_problem(session, 'iris') - problem.path_ramp_kit = ramp_config['ramp_kit_dir'] - problem.path_ramp_data = ramp_config['ramp_data_dir'] + problem = get_problem(session, "iris") + problem.path_ramp_kit = ramp_config["ramp_kit_dir"] + problem.path_ramp_data = ramp_config["ramp_data_dir"] session.commit() deploy_ramp_event( - database_config_template(), ramp_config_template(), - setup_ramp_repo=False, force=False + database_config_template(), + ramp_config_template(), + setup_ramp_repo=False, + force=False, ) @@ -96,30 +100,37 @@ def test_deploy_ramp_event(session_scope_function): # check that we created the archive assert os.path.isfile( os.path.join( - ramp_config['ramp_kit_dir'], 'events_archived', - ramp_config['event_name'] + '.zip' + ramp_config["ramp_kit_dir"], + "events_archived", + ramp_config["event_name"] + ".zip", ) ) # simulate that we add users and sign-up for the event and that they # submitted the starting kit - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: add_users(session) - sign_up_team(session, ramp_config['event_name'], 'test_user') - submit_starting_kits(session, ramp_config['event_name'], 'test_user', - ramp_config['ramp_kit_submissions_dir']) + sign_up_team(session, ramp_config["event_name"], "test_user") + submit_starting_kits( + session, + ramp_config["event_name"], + "test_user", + ramp_config["ramp_kit_submissions_dir"], + ) # run the dispatcher on the event which are in the dataset - dispatcher = Dispatcher(config=database_config, - event_config=event_config, - worker=CondaEnvWorker, - n_workers=-1, - hunger_policy='exit') + dispatcher = Dispatcher( + config=database_config, + event_config=event_config, + worker=CondaEnvWorker, + n_workers=-1, + hunger_policy="exit", + ) dispatcher.launch() # the iris kit contain a submission which should fail for a user - with session_scope(database_config['sqlalchemy']) as session: + with session_scope(database_config["sqlalchemy"]) as session: submission = get_submissions( - session, event_config['ramp']['event_name'], 'training_error' + session, event_config["ramp"]["event_name"], "training_error" ) assert len(submission) == 1 diff --git a/ramp-utils/ramp_utils/tests/test_frontend.py b/ramp-utils/ramp_utils/tests/test_frontend.py index a0707656c..1f90ade3d 100644 --- a/ramp-utils/ramp_utils/tests/test_frontend.py +++ b/ramp-utils/ramp_utils/tests/test_frontend.py @@ -9,44 +9,44 @@ @pytest.mark.parametrize( "config", - [database_config_template(), - read_config(database_config_template())] + [database_config_template(), read_config(database_config_template())], ) def test_generate_flask_config(config): flask_config = generate_flask_config(config) expected_config = { - 'SECRET_KEY': 'abcdefghijkl', - 'WTF_CSRF_ENABLED': True, - 'LOGIN_INSTRUCTIONS': None, - 'LOG_FILENAME': 'None', - 'MAX_CONTENT_LENGTH': 1073741824, - 'PRIVACY_POLICY_PAGE': None, - 'DEBUG': True, - 'TESTING': False, - 'MAIL_SERVER': 'localhost', - 'MAIL_PORT': 8025, - 'MAIL_DEFAULT_SENDER': ['RAMP admin', 'rampmailer@localhost.com'], - 'SIGN_UP_ASK_SOCIAL_MEDIA': False, - 'SIGN_UP_INSTRUCTIONS': None, - 'SQLALCHEMY_TRACK_MODIFICATIONS': False, - 'SQLALCHEMY_DATABASE_URI': ('postgresql://mrramp:mrramp@localhost:5432' - '/databoard_test'), - 'TRACK_USER_INTERACTION': True, - 'TRACK_CREDITS': True, - 'DOMAIN_NAME': 'localhost' - } + "SECRET_KEY": "abcdefghijkl", + "WTF_CSRF_ENABLED": True, + "LOGIN_INSTRUCTIONS": None, + "LOG_FILENAME": "None", + "MAX_CONTENT_LENGTH": 1073741824, + "PRIVACY_POLICY_PAGE": None, + "DEBUG": True, + "TESTING": False, + "MAIL_SERVER": "localhost", + "MAIL_PORT": 8025, + "MAIL_DEFAULT_SENDER": ["RAMP admin", "rampmailer@localhost.com"], + "SIGN_UP_ASK_SOCIAL_MEDIA": False, + "SIGN_UP_INSTRUCTIONS": None, + "SQLALCHEMY_TRACK_MODIFICATIONS": False, + "SQLALCHEMY_DATABASE_URI": ( + "postgresql://mrramp:mrramp@localhost:5432" "/databoard_test" + ), + "TRACK_USER_INTERACTION": True, + "TRACK_CREDITS": True, + "DOMAIN_NAME": "localhost", + } assert flask_config == expected_config def test_read_if_html_path(tmpdir): - msg = 'some arbitrary text' + msg = "some arbitrary text" assert _read_if_html_path(msg) == msg - msg = 'an_ivalid_path.html' + msg = "an_ivalid_path.html" with pytest.raises(FileNotFoundError): _read_if_html_path(msg) - msg = str(tmpdir / 'some_file.html') - with open(msg, 'wt') as fh: - fh.write('Privacy Policy') - assert _read_if_html_path(msg) == 'Privacy Policy' + msg = str(tmpdir / "some_file.html") + with open(msg, "wt") as fh: + fh.write("Privacy Policy") + assert _read_if_html_path(msg) == "Privacy Policy" diff --git a/ramp-utils/ramp_utils/tests/test_ramp.py b/ramp-utils/ramp_utils/tests/test_ramp.py index c5fe8031b..93daa8e82 100644 --- a/ramp-utils/ramp_utils/tests/test_ramp.py +++ b/ramp-utils/ramp_utils/tests/test_ramp.py @@ -12,17 +12,23 @@ def _get_event_config(version): - return os.path.join( - HERE, 'data', 'ramp_config_{}.yml'.format(version) - ) + return os.path.join(HERE, "data", "ramp_config_{}.yml".format(version)) @pytest.mark.parametrize( "event_config, database_config, err_msg", - [(_get_event_config('absolute'), None, - "you need to provide the filename of the database as well"), - (read_config(_get_event_config('missing')), None, - "you need to provide all following keys")] + [ + ( + _get_event_config("absolute"), + None, + "you need to provide the filename of the database as well", + ), + ( + read_config(_get_event_config("missing")), + None, + "you need to provide all following keys", + ), + ], ) def test_generate_ramp_config_error(event_config, database_config, err_msg): with pytest.raises(ValueError, match=err_msg): @@ -31,70 +37,63 @@ def test_generate_ramp_config_error(event_config, database_config, err_msg): @pytest.mark.parametrize( "event_config, database_config", - [(ramp_config_template(), database_config_template()), - (read_config(ramp_config_template()), None), - (read_config(ramp_config_template(), filter_section='ramp'), None)] + [ + (ramp_config_template(), database_config_template()), + (read_config(ramp_config_template()), None), + (read_config(ramp_config_template(), filter_section="ramp"), None), + ], ) def test_generate_ramp_config(event_config, database_config): ramp_config = generate_ramp_config(event_config, database_config) expected_config = { - 'problem_name': 'iris', - 'event_name': 'iris_test', - 'event_title': 'Iris event', - 'event_is_public': True, - 'sandbox_name': 'starting_kit', - 'ramp_kit_dir': os.path.join( - '/tmp/databoard_test', 'ramp-kits', 'iris' - ), - 'ramp_data_dir': os.path.join( - '/tmp/databoard_test', 'ramp-data', 'iris' - ), - 'ramp_kit_submissions_dir': os.path.join( - '/tmp/databoard_test', 'ramp-kits', 'iris', 'submissions' - ), - 'ramp_submissions_dir': os.path.join( - '/tmp/databoard_test', 'submissions' - ), - 'ramp_sandbox_dir': os.path.join( - '/tmp/databoard_test', 'ramp-kits', 'iris', 'submissions', - 'starting_kit' + "problem_name": "iris", + "event_name": "iris_test", + "event_title": "Iris event", + "event_is_public": True, + "sandbox_name": "starting_kit", + "ramp_kit_dir": os.path.join("/tmp/databoard_test", "ramp-kits", "iris"), + "ramp_data_dir": os.path.join("/tmp/databoard_test", "ramp-data", "iris"), + "ramp_kit_submissions_dir": os.path.join( + "/tmp/databoard_test", "ramp-kits", "iris", "submissions" ), - 'ramp_logs_dir': os.path.join( - '/tmp/databoard_test', 'log' + "ramp_submissions_dir": os.path.join("/tmp/databoard_test", "submissions"), + "ramp_sandbox_dir": os.path.join( + "/tmp/databoard_test", + "ramp-kits", + "iris", + "submissions", + "starting_kit", ), - 'ramp_predictions_dir': os.path.join( - '/tmp/databoard_test', 'preds' - ) + "ramp_logs_dir": os.path.join("/tmp/databoard_test", "log"), + "ramp_predictions_dir": os.path.join("/tmp/databoard_test", "preds"), } assert ramp_config == expected_config def test_generate_ramp_config_short(): ramp_config = generate_ramp_config( - _get_event_config('short'), database_config_template() + _get_event_config("short"), database_config_template() ) expected_config = { - 'problem_name': 'iris', - 'event_name': 'iris_test', - 'event_title': 'Iris event', - 'ramp_kit_dir': os.path.join('template', 'ramp-kits', 'iris'), - 'ramp_data_dir': os.path.join('template', 'ramp-data', 'iris'), - 'ramp_submissions_dir': os.path.join( - 'template', 'events', 'iris_test', 'submissions' + "problem_name": "iris", + "event_name": "iris_test", + "event_title": "Iris event", + "ramp_kit_dir": os.path.join("template", "ramp-kits", "iris"), + "ramp_data_dir": os.path.join("template", "ramp-data", "iris"), + "ramp_submissions_dir": os.path.join( + "template", "events", "iris_test", "submissions" ), - 'sandbox_name': 'starting_kit', - 'ramp_predictions_dir': os.path.join( - 'template', 'events', 'iris_test', 'predictions' + "sandbox_name": "starting_kit", + "ramp_predictions_dir": os.path.join( + "template", "events", "iris_test", "predictions" ), - 'ramp_logs_dir': os.path.join( - 'template', 'events', 'iris_test', 'logs' + "ramp_logs_dir": os.path.join("template", "events", "iris_test", "logs"), + "ramp_sandbox_dir": os.path.join( + "template", "ramp-kits", "iris", "submissions", "starting_kit" ), - 'ramp_sandbox_dir': os.path.join( - 'template', 'ramp-kits', 'iris', 'submissions', 'starting_kit' + "ramp_kit_submissions_dir": os.path.join( + "template", "ramp-kits", "iris", "submissions" ), - 'ramp_kit_submissions_dir': os.path.join( - 'template', 'ramp-kits', 'iris', 'submissions' - ) } for key in expected_config: assert expected_config[key] in ramp_config[key] diff --git a/ramp-utils/ramp_utils/tests/test_testing.py b/ramp-utils/ramp_utils/tests/test_testing.py index 829b3f232..c84242d87 100644 --- a/ramp-utils/ramp_utils/tests/test_testing.py +++ b/ramp-utils/ramp_utils/tests/test_testing.py @@ -10,8 +10,10 @@ @pytest.mark.parametrize( "config_func, partial_path", - [(database_config_template, join('template', 'database_config.yml')), - (ramp_config_template, join('template', 'ramp_config.yml'))] + [ + (database_config_template, join("template", "database_config.yml")), + (ramp_config_template, join("template", "ramp_config.yml")), + ], ) def test_path_configuration(config_func, partial_path): path = config_func() diff --git a/ramp-utils/ramp_utils/tests/test_utils.py b/ramp-utils/ramp_utils/tests/test_utils.py index d2f48baf6..06b735136 100644 --- a/ramp-utils/ramp_utils/tests/test_utils.py +++ b/ramp-utils/ramp_utils/tests/test_utils.py @@ -6,7 +6,5 @@ def test_import_module_from_source(): module_path = os.path.dirname(__file__) # import the local_module.py which consist of a single function. - mod = import_module_from_source( - os.path.join(module_path, 'local_module.py'), 'mod' - ) - assert hasattr(mod, 'func_local_module') + mod = import_module_from_source(os.path.join(module_path, "local_module.py"), "mod") + assert hasattr(mod, "func_local_module") diff --git a/ramp-utils/ramp_utils/tests/test_worker.py b/ramp-utils/ramp_utils/tests/test_worker.py index 4bcb6ae98..3e1fcefd1 100644 --- a/ramp-utils/ramp_utils/tests/test_worker.py +++ b/ramp-utils/ramp_utils/tests/test_worker.py @@ -14,13 +14,13 @@ def test_generate_worker_config(): ramp_config_template(), database_config_template() ) expected_config = { - 'worker_type': 'conda', - 'conda_env': 'ramp-iris', - 'kit_dir': os.path.join('/tmp/databoard_test', 'ramp-kits', 'iris'), - 'data_dir': os.path.join('/tmp/databoard_test', 'ramp-data', 'iris'), - 'submissions_dir': os.path.join('/tmp/databoard_test', 'submissions'), - 'predictions_dir': os.path.join('/tmp/databoard_test', 'preds'), - 'logs_dir': os.path.join('/tmp/databoard_test', 'log') + "worker_type": "conda", + "conda_env": "ramp-iris", + "kit_dir": os.path.join("/tmp/databoard_test", "ramp-kits", "iris"), + "data_dir": os.path.join("/tmp/databoard_test", "ramp-data", "iris"), + "submissions_dir": os.path.join("/tmp/databoard_test", "submissions"), + "predictions_dir": os.path.join("/tmp/databoard_test", "preds"), + "logs_dir": os.path.join("/tmp/databoard_test", "log"), } assert worker_config == expected_config @@ -28,8 +28,8 @@ def test_generate_worker_config(): def test_generate_worker_config_missing_params(): ramp_config = read_config(ramp_config_template()) # rename on of the key to make the generation failed - ramp_config['worker']['env'] = ramp_config['worker']['conda_env'] - del ramp_config['worker']['conda_env'] + ramp_config["worker"]["env"] = ramp_config["worker"]["conda_env"] + del ramp_config["worker"]["conda_env"] err_msg = "The conda worker is missing the parameter" with pytest.raises(ValueError, match=err_msg): generate_worker_config(ramp_config) diff --git a/ramp-utils/ramp_utils/worker.py b/ramp-utils/ramp_utils/worker.py index a8ee6230e..4308b1f38 100644 --- a/ramp-utils/ramp_utils/worker.py +++ b/ramp-utils/ramp_utils/worker.py @@ -2,11 +2,20 @@ from .ramp import generate_ramp_config REQUIRED_KEYS = { - 'conda': {'conda_env'}, - 'aws': {'access_key_id', 'secret_access_key', 'region_name', - 'ami_image_name', 'ami_user_name', 'instance_type', - 'key_name', 'security_group', 'key_path', 'remote_ramp_kit_folder', - 'memory_profiling'} + "conda": {"conda_env"}, + "aws": { + "access_key_id", + "secret_access_key", + "region_name", + "ami_image_name", + "ami_user_name", + "instance_type", + "key_name", + "security_group", + "key_path", + "remote_ramp_kit_folder", + "memory_profiling", + }, } @@ -31,31 +40,31 @@ def generate_worker_config(event_config, database_config=None): """ if isinstance(event_config, str): ramp_config = generate_ramp_config(event_config, database_config) - event_config = read_config( - event_config, filter_section=['ramp', 'worker']) + event_config = read_config(event_config, filter_section=["ramp", "worker"]) else: ramp_config = generate_ramp_config(event_config) # copy the specific information for the given worker configuration - worker_config = event_config['worker'].copy() + worker_config = event_config["worker"].copy() # define the directory of the ramp-kit for the event - worker_config['kit_dir'] = ramp_config['ramp_kit_dir'] + worker_config["kit_dir"] = ramp_config["ramp_kit_dir"] # define the directory of the ramp-data for the event - worker_config['data_dir'] = ramp_config['ramp_data_dir'] + worker_config["data_dir"] = ramp_config["ramp_data_dir"] # define the directory of the submissions - worker_config['submissions_dir'] = ramp_config['ramp_submissions_dir'] + worker_config["submissions_dir"] = ramp_config["ramp_submissions_dir"] # define the directory of the predictions - worker_config['predictions_dir'] = ramp_config['ramp_predictions_dir'] + worker_config["predictions_dir"] = ramp_config["ramp_predictions_dir"] # define the directory of the logs - worker_config['logs_dir'] = ramp_config['ramp_logs_dir'] + worker_config["logs_dir"] = ramp_config["ramp_logs_dir"] - if worker_config['worker_type'] in REQUIRED_KEYS.keys(): - required_fields = REQUIRED_KEYS[worker_config['worker_type']] + if worker_config["worker_type"] in REQUIRED_KEYS.keys(): + required_fields = REQUIRED_KEYS[worker_config["worker_type"]] missing_parameters = required_fields.difference(worker_config) if missing_parameters: raise ValueError( - 'The {} worker is missing the parameter(s): {}' - .format(worker_config['worker_type'], missing_parameters) + "The {} worker is missing the parameter(s): {}".format( + worker_config["worker_type"], missing_parameters + ) ) return worker_config diff --git a/ramp-utils/setup.py b/ramp-utils/setup.py index 4cf3d3d47..0b0fa843b 100755 --- a/ramp-utils/setup.py +++ b/ramp-utils/setup.py @@ -5,46 +5,50 @@ from setuptools import find_packages, setup # get __version__ from _version.py -ver_file = os.path.join('ramp_utils', '_version.py') +ver_file = os.path.join("ramp_utils", "_version.py") with open(ver_file) as f: exec(f.read()) -DISTNAME = 'ramp-utils' +DISTNAME = "ramp-utils" DESCRIPTION = "Utilities shared across the RAMP bundle" -with codecs.open('README.rst', encoding='utf-8-sig') as f: +with codecs.open("README.rst", encoding="utf-8-sig") as f: LONG_DESCRIPTION = f.read() -MAINTAINER = 'A. Boucaud, B. Kegl, G. Lemaitre, J. Van den Bossche' -MAINTAINER_EMAIL = 'boucaud.alexandre@gmail.com, guillaume.lemaitre@inria.fr' -URL = 'https://github.com/paris-saclay-cds/ramp-board' -LICENSE = 'BSD (3-clause)' -DOWNLOAD_URL = 'https://github.com/paris-saclay-cds/ramp-board' +MAINTAINER = "A. Boucaud, B. Kegl, G. Lemaitre, J. Van den Bossche" +MAINTAINER_EMAIL = "boucaud.alexandre@gmail.com, guillaume.lemaitre@inria.fr" +URL = "https://github.com/paris-saclay-cds/ramp-board" +LICENSE = "BSD (3-clause)" +DOWNLOAD_URL = "https://github.com/paris-saclay-cds/ramp-board" VERSION = __version__ # noqa -CLASSIFIERS = ['Intended Audience :: Science/Research', - 'Intended Audience :: Developers', - 'License :: OSI Approved', - 'Programming Language :: Python', - 'Topic :: Software Development', - 'Topic :: Scientific/Engineering', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Operating System :: Unix', - 'Operating System :: MacOS', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8'] -INSTALL_REQUIRES = ['click', 'pandas', 'pyyaml'] +CLASSIFIERS = [ + "Intended Audience :: Science/Research", + "Intended Audience :: Developers", + "License :: OSI Approved", + "Programming Language :: Python", + "Topic :: Software Development", + "Topic :: Scientific/Engineering", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Operating System :: Unix", + "Operating System :: MacOS", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", +] +INSTALL_REQUIRES = ["click", "pandas", "pyyaml"] EXTRAS_REQUIRE = { - 'tests': ['pytest', 'pytest-cov'], - 'docs': ['sphinx', 'sphinx_rtd_theme', 'numpydoc'] + "tests": ["pytest", "pytest-cov"], + "docs": ["sphinx", "sphinx_rtd_theme", "numpydoc"], } PACKAGE_DATA = { - 'ramp_utils': [os.path.join('tests', 'data', 'ramp_config_absolute.yml'), - os.path.join('tests', 'data', 'ramp_config_missing.yml'), - os.path.join('tests', 'data', 'ramp_config_short.yml'), - os.path.join('template', 'database_config.yml'), - os.path.join('template', 'ramp_config.yml'), - os.path.join('template', 'ramp_config_template.yml'), - os.path.join('template', 'database_config_template.yml')] + "ramp_utils": [ + os.path.join("tests", "data", "ramp_config_absolute.yml"), + os.path.join("tests", "data", "ramp_config_missing.yml"), + os.path.join("tests", "data", "ramp_config_short.yml"), + os.path.join("template", "database_config.yml"), + os.path.join("template", "ramp_config.yml"), + os.path.join("template", "ramp_config_template.yml"), + os.path.join("template", "database_config_template.yml"), + ] } setup( @@ -65,9 +69,9 @@ extras_require=EXTRAS_REQUIRE, python_requires=">=3.7", entry_points={ - 'console_scripts': [ - 'ramp = ramp_utils.ramp_cli:main', - 'ramp-setup = ramp_utils.cli:start', + "console_scripts": [ + "ramp = ramp_utils.ramp_cli:main", + "ramp-setup = ramp_utils.cli:start", ] - } + }, )