diff --git a/beat/web/experiments/models/experiment.py b/beat/web/experiments/models/experiment.py index d09485e832ccc399378f5f155037eb79ed10622a..b57c79aa039f34e356dcfa1fb0cc37ce51a0cb06 100755 --- a/beat/web/experiments/models/experiment.py +++ b/beat/web/experiments/models/experiment.py @@ -37,8 +37,6 @@ from django.contrib.sites.models import Site import beat.core.hash import beat.core.experiment -from beat.core.utils import NumpyJSONEncoder - from ...algorithms.models import Algorithm from ...toolchains.models import Toolchain @@ -50,7 +48,6 @@ from ...common.models import get_description from ...common.models import set_description from ...common.models import get_declaration from ...common.models import set_declaration -from ...common.models import get_declaration_string from ...common import storage from ...common.exceptions import ShareError @@ -60,7 +57,7 @@ from ...backend.models import Queue from ...backend.models import Environment from ...databases.models import DatabaseSet from ...databases.models import DatabaseSetOutput -from ...import __version__ +from ... import __version__ from .block import Block from .block_input import BlockInput @@ -72,123 +69,160 @@ import os import simplejson import logging + logger = logging.getLogger(__name__) -#---------------------------------------------------------- +# ---------------------------------------------------------- def validate_environments(experiment, user=None): """Validates the environments throughout the experiment""" def _valid(environment): - q = Environment.objects.for_user(user, True) if user is not None else Environment.objects + q = ( + Environment.objects.for_user(user, True) + if user is not None + else Environment.objects + ) - return bool(q.filter( - name=environment['name'], - version=environment['version'], - )) + return bool(q.filter(name=environment["name"], version=environment["version"])) def _valid_combination(queue, environment): - return bool(Queue.objects.filter(name=queue, - environments__name=environment['name'], - environments__version=environment['version'] - )) + return bool( + Queue.objects.filter( + name=queue, + environments__name=environment["name"], + environments__version=environment["version"], + ) + ) errors = [] - default_q = experiment.data['globals']['queue'] - default_env = experiment.data['globals']['environment'] + default_q = experiment.data["globals"]["queue"] + default_env = experiment.data["globals"]["environment"] if not _valid(default_env): - errors.append("The environment '%s (%s)' in the global experiment declaration does not exist" % (default_env['name'], default_env['version'])) + errors.append( + "The environment '%s (%s)' in the global experiment declaration does not exist" + % (default_env["name"], default_env["version"]) + ) elif not _valid_combination(default_q, default_env): - errors.append("The combination of queue '%s' with environment '%s (%s)' in the global experiment declaration does not exist" % (default_q, default_env['name'], default_env['version'])) + errors.append( + "The combination of queue '%s' with environment '%s (%s)' in the global experiment declaration does not exist" + % (default_q, default_env["name"], default_env["version"]) + ) for name, config in experiment.blocks.items(): - q = config.get('queue', default_q) - env = config.get('environment', default_env) + q = config.get("queue", default_q) + env = config.get("environment", default_env) if not _valid(env): - errors.append("The environment '%s (%s)' for block '%s' does not exist" % (env['name'], env['version'], name)) + errors.append( + "The environment '%s (%s)' for block '%s' does not exist" + % (env["name"], env["version"], name) + ) elif not _valid_combination(q, env): - errors.append("The combination of queue '%s' with environment '%s (%s)' for block '%s' does not exist" % (q, env['name'], env['version'], name)) + errors.append( + "The combination of queue '%s' with environment '%s (%s)' for block '%s' does not exist" + % (q, env["name"], env["version"], name) + ) for name, config in experiment.analyzers.items(): - q = config.get('queue', default_q) - env = config.get('environment', default_env) + q = config.get("queue", default_q) + env = config.get("environment", default_env) if not _valid(env): - errors.append("The environment '%s (%s)' for analyzer '%s' does not exist" % (env['name'], env['version'], name)) + errors.append( + "The environment '%s (%s)' for analyzer '%s' does not exist" + % (env["name"], env["version"], name) + ) elif not _valid_combination(q, env): - errors.append("The combination of queue '%s' with environment '%s (%s)' for analyzer '%s' does not exist" % (q, env['name'], env['version'], name)) + errors.append( + "The combination of queue '%s' with environment '%s (%s)' for analyzer '%s' does not exist" + % (q, env["name"], env["version"], name) + ) return errors -#---------------------------------------------------------- +# ---------------------------------------------------------- def validate_experiment(experiment_info, toolchain_info, user=None): """Makes sure the experiment can be run""" - xp = beat.core.experiment.Experiment(settings.PREFIX, - (experiment_info, toolchain_info)) + xp = beat.core.experiment.Experiment( + settings.PREFIX, (experiment_info, toolchain_info) + ) - if not xp.valid: return xp, xp.errors + if not xp.valid: + return xp, xp.errors return xp, xp.errors + validate_environments(xp, user) -#---------------------------------------------------------- +# ---------------------------------------------------------- class DeclarationStorage(OverwriteStorage): - def __init__(self, *args, **kwargs): - super(DeclarationStorage, self).__init__(*args, location=settings.EXPERIMENTS_ROOT, **kwargs) + super(DeclarationStorage, self).__init__( + *args, location=settings.EXPERIMENTS_ROOT, **kwargs + ) -#---------------------------------------------------------- +# ---------------------------------------------------------- class ExperimentManager(ContributionManager): - - def get_by_natural_key(self, author_username, toolchain_username, - toolchain_name, toolchain_version, name): + def get_by_natural_key( + self, + author_username, + toolchain_username, + toolchain_name, + toolchain_version, + name, + ): return self.get( author__username=author_username, toolchain__author__username=toolchain_username, toolchain__name=toolchain_name, toolchain__version=toolchain_version, - name=name + name=name, ) - def from_author(self, user, author_name, add_public=False): - return super(ExperimentManager, self).from_author(user, author_name, add_public).order_by('-creation_date', 'name') - + return ( + super(ExperimentManager, self) + .from_author(user, author_name, add_public) + .order_by("-creation_date", "name") + ) def from_author_and_public(self, user, author_name): - return super(ExperimentManager, self).from_author_and_public(user, author_name).order_by('-creation_date', 'name') - + return ( + super(ExperimentManager, self) + .from_author_and_public(user, author_name) + .order_by("-creation_date", "name") + ) - def create_experiment(self, author, toolchain, name, declaration, - short_description='', description=''): + def create_experiment( + self, author, toolchain, name, declaration, short_description="", description="" + ): """Creates a new experiment in pending state""" # Create the database representation of the experiment experiment = self.model( - author = author, - toolchain = toolchain, - name = name, - status = self.model.PENDING, - start_date = None, - end_date = None, + author=author, + toolchain=toolchain, + name=name, + status=self.model.PENDING, + start_date=None, + end_date=None, ) - if not(isinstance(declaration, dict)): + if not (isinstance(declaration, dict)): declaration = simplejson.loads(declaration) if len(short_description) > 0: - declaration['description'] = short_description + declaration["description"] = short_description experiment.declaration = declaration @@ -203,96 +237,101 @@ class ExperimentManager(ContributionManager): return (None, None, str(e)) except Exception: import traceback + return (None, None, traceback.format_exc()) experiment._loaded_status = experiment.status return experiment, experiment._toolchain_cache, None -#---------------------------------------------------------- +# ---------------------------------------------------------- class Experiment(Shareable): - #_____ Constants __________ + # _____ Constants __________ - PENDING = 'P' - SCHEDULED = 'S' - RUNNING = 'R' - DONE = 'D' - FAILED = 'F' - CANCELLING = 'C' + PENDING = "P" + SCHEDULED = "S" + RUNNING = "R" + DONE = "D" + FAILED = "F" + CANCELLING = "C" STATUS = ( - (PENDING, 'Pending'), - (SCHEDULED, 'Scheduled'), - (RUNNING, 'Running'), - (DONE, 'Done'), - (FAILED, 'Failed'), - (CANCELLING, 'Canceling'), + (PENDING, "Pending"), + (SCHEDULED, "Scheduled"), + (RUNNING, "Running"), + (DONE, "Done"), + (FAILED, "Failed"), + (CANCELLING, "Canceling"), ) + # _____ Fields __________ - #_____ Fields __________ - - author = models.ForeignKey(User, related_name='experiments', - on_delete=models.CASCADE) - toolchain = models.ForeignKey(Toolchain, related_name='experiments', - on_delete=models.CASCADE) - name = models.CharField(max_length=200) - short_description = models.CharField(max_length=100, default='', blank=True, help_text=Messages['short_description']) - status = models.CharField(max_length=1, choices=STATUS, default=PENDING) - creation_date = models.DateTimeField(null=True, blank=True, auto_now_add=True) - start_date = models.DateTimeField(null=True, blank=True) - end_date = models.DateTimeField(null=True, blank=True) + author = models.ForeignKey( + User, related_name="experiments", on_delete=models.CASCADE + ) + toolchain = models.ForeignKey( + Toolchain, related_name="experiments", on_delete=models.CASCADE + ) + name = models.CharField(max_length=200) + short_description = models.CharField( + max_length=100, default="", blank=True, help_text=Messages["short_description"] + ) + status = models.CharField(max_length=1, choices=STATUS, default=PENDING) + creation_date = models.DateTimeField(null=True, blank=True, auto_now_add=True) + start_date = models.DateTimeField(null=True, blank=True) + end_date = models.DateTimeField(null=True, blank=True) declaration_file = models.FileField( storage=DeclarationStorage(), upload_to=get_contribution_declaration_filename, - blank=True, null=True, + blank=True, + null=True, max_length=300, - db_column='declaration' + db_column="declaration", ) description_file = models.FileField( storage=DeclarationStorage(), upload_to=get_contribution_description_filename, - blank=True, null=True, + blank=True, + null=True, max_length=300, - db_column='description' + db_column="description", ) - # read-only parameters that are updated at every save(), if required hash = models.CharField(max_length=64) - referenced_datasets = models.ManyToManyField(DatabaseSet, related_name='experiments', blank=True) - referenced_algorithms = models.ManyToManyField(Algorithm, related_name='experiments', blank=True) + referenced_datasets = models.ManyToManyField( + DatabaseSet, related_name="experiments", blank=True + ) + referenced_algorithms = models.ManyToManyField( + Algorithm, related_name="experiments", blank=True + ) objects = ExperimentManager() - - #_____ Meta parameters __________ + # _____ Meta parameters __________ class Meta: - unique_together = ('author', 'toolchain', 'name') - ordering = ['-creation_date'] - + unique_together = ("author", "toolchain", "name") + ordering = ["-creation_date"] - #_____ Utilities __________ + # _____ Utilities __________ def natural_key(self): - return (self.author.username,) + \ - self.toolchain.natural_key() + (self.name,) - natural_key.dependencies = ['toolchains.toolchain'] + return (self.author.username,) + self.toolchain.natural_key() + (self.name,) + natural_key.dependencies = ["toolchains.toolchain"] - #_____ Methods __________ + # _____ Methods __________ def fullname(self): - return '%s/%s/%s' % (self.author.username, self.toolchain.fullname(), self.name) + return "%s/%s/%s" % (self.author.username, self.toolchain.fullname(), self.name) - - def hashed_path(self, extension=''): + def hashed_path(self, extension=""): """Relative path of a file belonging to the object on the respective storage""" @@ -302,18 +341,15 @@ class Experiment(Shareable): self.name + extension, ) - def declaration_filename(self): """Relative path of the declaration file on the storage""" - return self.hashed_path('.json') - + return self.hashed_path(".json") def description_filename(self): """Relative path of the description file on the storage""" - return self.hashed_path('.rst') - + return self.hashed_path(".rst") - #_____ Utilities __________ + # _____ Utilities __________ def _share_dataformats(self, users, teams): needed_formats = [] @@ -336,7 +372,6 @@ class Experiment(Shareable): for dataformats in own_needed_formats: dataformats.share(users=users, teams=teams) - def __share_algorithms(self, users, teams, algorithms_infos): # Retrieve and process the list of algorithms referenced by the declaration, and # the data formats they reference @@ -348,8 +383,12 @@ class Experiment(Shareable): # Only keep the referenced dataformats and algorithms owned by the author of the # experiment # Process the list of referenced algorithms - own_needed_algorithms = filter(lambda x: x.author == self.author, needed_algorithms) - other_needed_algorithms = filter(lambda x: x.author != self.author, needed_algorithms) + own_needed_algorithms = filter( + lambda x: x.author == self.author, needed_algorithms + ) + other_needed_algorithms = filter( + lambda x: x.author != self.author, needed_algorithms + ) # Ensure that all needed algorithms from other users have the necessary sharing # preferences @@ -362,14 +401,13 @@ class Experiment(Shareable): for algorithm in own_needed_algorithms: if algorithms_infos and algorithm.fullname() in algorithms_infos: infos = algorithms_infos[algorithm.fullname()] - opensource = infos.get('opensource', False) + opensource = infos.get("opensource", False) else: opensource = True algorithm.share(public=opensource, users=users, teams=teams) - - #_____ Overrides __________ + # _____ Overrides __________ def has_attestation(self): try: @@ -379,40 +417,43 @@ class Experiment(Shareable): else: return True - @classmethod def from_db(cls, db, field_names, values): instance = super(Experiment, cls).from_db(db, field_names, values) - instance._loaded_status = values[field_names.index('status')] + instance._loaded_status = values[field_names.index("status")] return instance - def save(self, *args, **kwargs): - new_experiment = (self.id is None) + new_experiment = self.id is None # if changing the experiment status, then make sure start_date is set - if (self.status not in [self.PENDING, self.SCHEDULED]) and \ - (self.start_date is None): + if (self.status not in [self.PENDING, self.SCHEDULED]) and ( + self.start_date is None + ): self.start_date = self.end_date or datetime.now() # Retrieve the experiment declaration declaration = self.declaration # Compute the hash of the content - content_hash = beat.core.hash.hashJSON(declaration, 'description') - content_modified = (content_hash != self.hash) + content_hash = beat.core.hash.hashJSON(declaration, "description") + content_modified = content_hash != self.hash if content_modified: # validates the experiment - xp, errors = validate_experiment(declaration, self.toolchain.declaration, self.author) + xp, errors = validate_experiment( + declaration, self.toolchain.declaration, self.author + ) if errors: - message = "The experiment isn't valid, due to the " \ + message = ( + "The experiment isn't valid, due to the " "following errors:\n * %s" - raise SyntaxError(message % '\n * '.join(errors)) + ) + raise SyntaxError(message % "\n * ".join(errors)) self.hash = content_hash - self.short_description = declaration.get('description', '') + self.short_description = declaration.get("description", "") # Ensures that the sharing informations are consistent if self.sharing == Shareable.USABLE: @@ -432,8 +473,8 @@ class Experiment(Shareable): # If the filename has changed, move all the files if self.declaration_filename() != self.declaration_file.name: - storage.rename_file(self, 'declaration_file', self.declaration_filename()) - storage.rename_file(self, 'description_file', self.description_filename()) + storage.rename_file(self, "declaration_file", self.declaration_filename()) + storage.rename_file(self, "description_file", self.description_filename()) if content_modified: # Creates experiment blocks and setup dependencies @@ -443,22 +484,25 @@ class Experiment(Shareable): self.referenced_datasets.clear() for dataset_declaration in xp.datasets.values(): try: - (db_name, db_version) = \ - dataset_declaration['database'].name.split('/') + (db_name, db_version) = dataset_declaration["database"].name.split( + "/" + ) dataset = DatabaseSet.objects.get( protocol__database__name=db_name, protocol__database__version=db_version, - protocol__name=dataset_declaration['protocol'], - name=dataset_declaration['set'], + protocol__name=dataset_declaration["protocol"], + name=dataset_declaration["set"], ) - except: - if new_experiment: self.delete() - raise SyntaxError("The dataset '%s.%s.%s' can't be found" \ - % ( - dataset_declaration['database'].name, - dataset_declaration['protocol'], - dataset_declaration['set'], - ) + except Exception: + if new_experiment: + self.delete() + raise SyntaxError( + "The dataset '%s.%s.%s' can't be found" + % ( + dataset_declaration["database"].name, + dataset_declaration["protocol"], + dataset_declaration["set"], + ) ) self.referenced_datasets.add(dataset) @@ -468,55 +512,87 @@ class Experiment(Shareable): for algorithm in xp.algorithms: try: - (username, name, version) = algorithm.split('/') + (username, name, version) = algorithm.split("/") algorithm_db = Algorithm.objects.get( - author__username=username, - name=name, - version=int(version), + author__username=username, name=name, version=int(version) ) - except: + except Exception: if new_experiment: self.delete() - raise SyntaxError("The algorithm '%s' can't be found" % \ - algorithm.fullname()) + raise SyntaxError( + "The algorithm '%s' can't be found" % algorithm.fullname() + ) self.referenced_algorithms.add(algorithm_db) - def email(self): - '''e-mails owners and shared parties about this experiment status''' + """e-mails owners and shared parties about this experiment status""" - user_email_list = [self.author.email] if self.author.accountsettings.experiment_mail_notifications_enabled else [] - user_email_list.extend([user.email for user in self.shared_with.all() if user.accountsettings.experiment_mail_notifications_enabled]) - all_team_members = [user for team in self.shared_with_team.all() for user in team.members.all()] - user_email_list.extend([user.email for user in all_team_members if user.email not in user_email_list and user.accountsettings.experiment_mail_notifications_enabled]) + user_email_list = ( + [self.author.email] + if self.author.accountsettings.experiment_mail_notifications_enabled + else [] + ) + user_email_list.extend( + [ + user.email + for user in self.shared_with.all() + if user.accountsettings.experiment_mail_notifications_enabled + ] + ) + all_team_members = [ + user for team in self.shared_with_team.all() for user in team.members.all() + ] + user_email_list.extend( + [ + user.email + for user in all_team_members + if user.email not in user_email_list + and user.accountsettings.experiment_mail_notifications_enabled + ] + ) if user_email_list: if self.status == Experiment.DONE: - subject = "Experiment %s finished successfully" % \ - self.fullname() - template_path = 'experiments/successful_experiment_email.txt' + subject = "Experiment %s finished successfully" % self.fullname() + template_path = "experiments/successful_experiment_email.txt" elif self.status == Experiment.FAILED: subject = "Experiment %s failed" % self.fullname() - template_path = 'experiments/failed_experiment_email.txt' + template_path = "experiments/failed_experiment_email.txt" try: - send_mail(subject, render_to_string(template_path, {'experiment': self, 'beat_version': __version__, 'site': Site.objects.get_current()}), settings.DEFAULT_FROM_EMAIL, user_email_list) + send_mail( + subject, + render_to_string( + template_path, + { + "experiment": self, + "beat_version": __version__, + "site": Site.objects.get_current(), + }, + ), + settings.DEFAULT_FROM_EMAIL, + user_email_list, + ) except Exception: import traceback - logger.warn("Could not send e-mail to `%s' about " \ - "`%s'. Exception caught: %s", user_email_list, - self, traceback.format_exc()) + logger.warn( + "Could not send e-mail to `%s' about " "`%s'. Exception caught: %s", + user_email_list, + self, + traceback.format_exc(), + ) def share(self, users=None, teams=None, algorithms_infos={}): self._share_dataformats(users=users, teams=teams) - self.__share_algorithms(users=users, teams=teams, algorithms_infos=algorithms_infos) + self.__share_algorithms( + users=users, teams=teams, algorithms_infos=algorithms_infos + ) self.toolchain.share(users=users, teams=teams) super(Experiment, self).share(users=users, teams=teams) - def update_blocks(self): """Updates internal block representation of an experiment""" @@ -527,41 +603,45 @@ class Experiment(Shareable): for order_0, (block_name, description) in enumerate(corexp.setup().items()): # Checks that the Queue/Environment exists - job_description = description['configuration'] + job_description = description["configuration"] env = Environment.objects.filter( - name=job_description['environment']['name'], - version=job_description['environment']['version'], + name=job_description["environment"]["name"], + version=job_description["environment"]["version"], ) if not env: - logger.warn("Cannot find environment `%s (%s)' - not setting", - job_description['environment']['name'], - job_description['environment']['version']) + logger.warn( + "Cannot find environment `%s (%s)' - not setting", + job_description["environment"]["name"], + job_description["environment"]["version"], + ) env = None else: env = env[0] # Search for queue that contains a specific environment if env: - queue = Queue.objects.filter(name=job_description['queue'], - environments__in=[env]) + queue = Queue.objects.filter( + name=job_description["queue"], environments__in=[env] + ) else: - queue = Queue.objects.filter(name=queue) + queue = Queue.objects.filter(name=queue) if not queue: - env_name = env.fullname() if env else 'NULL' - logger.warn("Cannot find queue `%s' which contains " \ - "environment `%s' - not setting", - job_description['queue'], env_name) + env_name = env.fullname() if env else "NULL" + logger.warn( + "Cannot find queue `%s' which contains " + "environment `%s' - not setting", + job_description["queue"], + env_name, + ) queue = None else: queue = queue[0] - parts = job_description['algorithm'].split('/') + parts = job_description["algorithm"].split("/") algorithm = Algorithm.objects.get( - author__username=parts[0], - name=parts[1], - version=parts[2], + author__username=parts[0], name=parts[1], version=parts[2] ) # Ties the block in @@ -577,113 +657,122 @@ class Experiment(Shareable): b.analyzer = algorithm.analysis() b.environment = env b.queue = queue - b.required_slots = job_description['nb_slots'] - b.channel = job_description['channel'] + b.required_slots = job_description["nb_slots"] + b.channel = job_description["channel"] b.save() # from this point: requires block to have an assigned id b.dependencies.clear() - b.dependencies.add(*[self.blocks.get(name=k) \ - for k in description['dependencies']]) + b.dependencies.add( + *[self.blocks.get(name=k) for k in description["dependencies"]] + ) # reset inputs - creates if necessary only b.inputs.clear() - for v in job_description['inputs'].values(): - if 'database' in v: #database input - db = DatabaseSetOutput.objects.get(set__hash=v['hash'], - template__name=v['output']) - BlockInput.objects.get_or_create(block=b, - channel=v['channel'], database=db) + for v in job_description["inputs"].values(): + if "database" in v: # database input + db = DatabaseSetOutput.objects.get( + set__hash=v["hash"], template__name=v["output"] + ) + BlockInput.objects.get_or_create( + block=b, channel=v["channel"], database=db + ) else: - cache = CachedFile.objects.get(hash=v['hash']) - BlockInput.objects.get_or_create(block=b, - channel=v['channel'], cache=cache) + cache, _ = CachedFile.objects.get_or_create(hash=v["hash"]) + BlockInput.objects.get_or_create( + block=b, channel=v["channel"], cache=cache + ) # reset outputs - creates if necessary only b.outputs.clear() - outputs = job_description.get('outputs', {'': job_description.get('result')}) + outputs = job_description.get( + "outputs", {"": job_description.get("result")} + ) for v in outputs.values(): - cache, cr = CachedFile.objects.get_or_create(hash=v['hash']) + cache, cr = CachedFile.objects.get_or_create(hash=v["hash"]) cache.blocks.add(b) - - #_____ Methods __________ + # _____ Methods __________ def is_busy(self): - return self.status in [Experiment.PENDING, Experiment.SCHEDULED, Experiment.CANCELLING] - + return self.status in [ + Experiment.PENDING, + Experiment.SCHEDULED, + Experiment.CANCELLING, + ] def is_done(self): return self.status in [Experiment.DONE, Experiment.FAILED] - def is_running(self): return self.status == Experiment.RUNNING - def modifiable(self): - return (self.reports.count() == 0) and not self.has_attestation() and super(Experiment, self).modifiable() - + return ( + (self.reports.count() == 0) + and not self.has_attestation() + and super(Experiment, self).modifiable() + ) def deletable(self): - return (self.reports.count() == 0) and not self.has_attestation() and super(Experiment, self).deletable() - + return ( + (self.reports.count() == 0) + and not self.has_attestation() + and super(Experiment, self).deletable() + ) def core(self): - return validate_experiment(self.declaration, self.toolchain.declaration, self.author)[0] - + return validate_experiment( + self.declaration, self.toolchain.declaration, self.author + )[0] def job_splits(self, status=None): from ...backend.models import JobSplit + retval = JobSplit.objects.filter(job__block__in=self.blocks.all()) if status is not None: retval = retval.filter(status=status) return retval - def get_absolute_url(self): return reverse( - 'experiments:view', - args=( - self.author.username, - self.toolchain.author.username, - self.toolchain.name, - self.toolchain.version, - self.name, - ), + "experiments:view", + args=( + self.author.username, + self.toolchain.author.username, + self.toolchain.name, + self.toolchain.version, + self.name, + ), ) - def get_api_share_url(self): return reverse( - 'api_experiments:share', - args=( - self.author.username, - self.toolchain.author.username, - self.toolchain.name, - self.toolchain.version, - self.name, - ), + "api_experiments:share", + args=( + self.author.username, + self.toolchain.author.username, + self.toolchain.name, + self.toolchain.version, + self.name, + ), ) - def get_api_update_url(self): return reverse( - 'api_experiments:object', - args=( - self.author.username, - self.toolchain.author.username, - self.toolchain.name, - self.toolchain.version, - self.name, - ), + "api_experiments:object", + args=( + self.author.username, + self.toolchain.author.username, + self.toolchain.name, + self.toolchain.version, + self.name, + ), ) - def get_admin_change_url(self): - return reverse('admin:experiments_experiment_change', args=(self.id,)) - + return reverse("admin:experiments_experiment_change", args=(self.id,)) def completion(self): if self.start_date is None: @@ -697,8 +786,11 @@ class Experiment(Shareable): if len(blocks) == 0: return 0 - return int(100 * float(len(filter(lambda x: x.status == Block.DONE, blocks))) / len(blocks)) - + return int( + 100 + * float(len(filter(lambda x: x.status == Block.DONE, blocks))) + / len(blocks) + ) def all_needed_dataformats(self): result = [] @@ -711,72 +803,72 @@ class Experiment(Shareable): return list(set(result)) - def reset(self): """Resets an experiment so it can be run again""" - if not self.is_done(): return #can only reset experiments which are done + if not self.is_done(): + return # can only reset experiments which are done - self.blocks.update( - status=Block.PENDING, - start_date=None, - end_date=None, - ) + self.blocks.update(status=Block.PENDING, start_date=None, end_date=None) self.start_date = None self.end_date = None self.status = self.PENDING # reset sharing state - self.sharing = Shareable.PRIVATE + self.sharing = Shareable.PRIVATE self.shared_with.clear() self.shared_with_team.clear() # remove associated attestations - if self.has_attestation(): self.attestation.all().delete() + if self.has_attestation(): + self.attestation.all().delete() self.save() - def databases_and_protocols(self): - '''A set of all used database/protocol combinations for all datasets''' - - return set(['%s' % k.protocol for k in self.referenced_datasets.all()]) + """A set of all used database/protocol combinations for all datasets""" + return set(["%s" % k.protocol for k in self.referenced_datasets.all()]) def analyzers(self): - '''A list of all used analyzers''' - - return set([k.fullname() for k in self.referenced_algorithms.all() if k.analysis()]) + """A list of all used analyzers""" + return set( + [k.fullname() for k in self.referenced_algorithms.all() if k.analysis()] + ) - #_____ Properties __________ + # _____ Properties __________ description = property(get_description, set_description) declaration = property(get_declaration, set_declaration) declaration_string = property(beat.web.common.models.get_declaration_string) - def schedule(self): - '''Schedules this experiment for execution at the backend''' + """Schedules this experiment for execution at the backend""" from ...backend.helpers import schedule_experiment - schedule_experiment(self) + schedule_experiment(self) def cancel(self): - '''Cancels the execution of this experiment on the backend.''' + """Cancels the execution of this experiment on the backend.""" from ...backend.helpers import cancel_experiment - cancel_experiment(self) + cancel_experiment(self) def fork(self, username=None, name=None): - '''Forks this experiment under a new username or name''' + """Forks this experiment under a new username or name""" author = username or self.author name = name or self.name - xp, _, __ = Experiment.objects.create_experiment(author, - self.toolchain, name, self.declaration, - self.short_description, self.description) + xp, _, __ = Experiment.objects.create_experiment( + author, + self.toolchain, + name, + self.declaration, + self.short_description, + self.description, + ) return xp diff --git a/common.cfg b/common.cfg index 09bd0c7ce1a42d5f8de5fcc0c2ed5d35ad93b180..faf2ab9c50a7aa47b1d1d9e2b7087819cdb5e0dd 100644 --- a/common.cfg +++ b/common.cfg @@ -16,6 +16,7 @@ django-guardian = >=1.4,<2.0 djangorestframework = >3.7 django-activity-stream = >= 0.6.5 django-jsonfield = >= 1.0.1 +jsonfield = <3.0 [scripts] recipe = bob.buildout:scripts diff --git a/dev.yml b/dev.yml index 23b4663c5903a00cc532a7cb7960c141aea2715b..5c4105699e0b64e91134e9e2be2694e13f5f3eb3 100644 --- a/dev.yml +++ b/dev.yml @@ -47,3 +47,4 @@ dependencies: - drf-yasg>=1.16 - djangorestframework>=3.9,<3.10 - sphinxcontrib-openapi>=0.5.0 + - jsonfield<3.0 diff --git a/release.cfg b/release.cfg index b6d96ffb49a3af05672f9128d5d02fe26de57062..8b2e6f7faca67d3dac60da29300334836f8b00f8 100644 --- a/release.cfg +++ b/release.cfg @@ -18,6 +18,7 @@ django-guardian = >=1.4,<2.0 djangorestframework = >3.7 django-activity-stream = >= 0.6.5 django-jsonfield = >= 1.0.1 +jsonfield = <3.0 [scripts] eggs = ${buildout:eggs}