From a81d70065d6f155c39c73721b60a3036d8f9aaf1 Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Wed, 14 Oct 2020 12:14:58 +0100 Subject: [PATCH 1/5] Initial write of rerun experiments enhancement --- weblab/entities/processing.py | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/weblab/entities/processing.py b/weblab/entities/processing.py index 6ae3576ea..efd8d67ab 100644 --- a/weblab/entities/processing.py +++ b/weblab/entities/processing.py @@ -9,8 +9,7 @@ from experiments.models import Experiment, PlannedExperiment from repocache.models import ProtocolInterface, ProtocolIoputs -from .models import AnalysisTask - +from .models import AnalysisTask, ModelEntity, ProtocolEntity logger = logging.getLogger(__name__) @@ -171,21 +170,24 @@ def record_experiments_to_run(user, entity, commit): entity.entity_type: entity, entity.entity_type + '_version': commit.sha, } - for parent in commit.parents: - # Find visible experiments involving this parent - parent_kwargs = { - entity.entity_type: entity, - entity.entity_type + '_version': entity.repocache.get_version(parent.sha), - } - for expt in Experiment.objects.filter(**parent_kwargs): - if expt.is_visible_to_user(user): + if entity.other_type == entity.ENTITY_TYPE_MODEL: + other_model = ModelEntity + else: + other_model = ProtocolEntity + search_args = { + entity.other_type + '_experiments__' + entity.entity_type: entity, + } + other_entities = other_model.objects.visible_to_user(user).filter(**search_args) + for other_entity in other_entities: + # Look for latest visible version + for other_version in other_entity.cachedentity.versions.reverse(): + if other_entity.is_version_visible_to_user(other_version.sha, user): # Record the new experiment to run kwargs = { 'submitter': user, - 'model_id': expt.model_id, - 'model_version': expt.model_version.sha, - 'protocol_id': expt.protocol_id, - 'protocol_version': expt.protocol_version.sha, + entity.other_type: other_entity, + other_entity + '_version': other_version.sha, } kwargs.update(new_version_kwargs) PlannedExperiment.objects.get_or_create(**kwargs) + break \ No newline at end of file From bef37ee53e402a80c047e498ff86e2b189e4f8b3 Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Fri, 16 Oct 2020 12:07:12 +0100 Subject: [PATCH 2/5] Create test for rerun experiments enhancement --- weblab/entities/processing.py | 11 ++--- weblab/entities/tests/test_views.py | 63 +++++++++++++++-------------- 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/weblab/entities/processing.py b/weblab/entities/processing.py index efd8d67ab..c8480925b 100644 --- a/weblab/entities/processing.py +++ b/weblab/entities/processing.py @@ -6,11 +6,12 @@ from django.core.urlresolvers import reverse from django.db import IntegrityError -from experiments.models import Experiment, PlannedExperiment +from experiments.models import PlannedExperiment from repocache.models import ProtocolInterface, ProtocolIoputs from .models import AnalysisTask, ModelEntity, ProtocolEntity + logger = logging.getLogger(__name__) @@ -159,7 +160,7 @@ def process_check_protocol_callback(data): def record_experiments_to_run(user, entity, commit): """Record what experiments to run automatically on a new entity version. - Any experiments that were run with the parent version(s) (that are visible to the user) + The latest experiments that were run with the parent version(s) (that are visible to the user) should be repeated with the new version. :param user: the user that created the new version @@ -180,14 +181,14 @@ def record_experiments_to_run(user, entity, commit): other_entities = other_model.objects.visible_to_user(user).filter(**search_args) for other_entity in other_entities: # Look for latest visible version - for other_version in other_entity.cachedentity.versions.reverse(): + for other_version in other_entity.cachedentity.versions.all(): if other_entity.is_version_visible_to_user(other_version.sha, user): # Record the new experiment to run kwargs = { 'submitter': user, entity.other_type: other_entity, - other_entity + '_version': other_version.sha, + entity.other_type + '_version': other_version.sha, } kwargs.update(new_version_kwargs) PlannedExperiment.objects.get_or_create(**kwargs) - break \ No newline at end of file + break diff --git a/weblab/entities/tests/test_views.py b/weblab/entities/tests/test_views.py index 4cbde5827..ce6fe0b63 100644 --- a/weblab/entities/tests/test_views.py +++ b/weblab/entities/tests/test_views.py @@ -1347,15 +1347,16 @@ def test_rolls_back_if_tag_exists(self, logged_in_user, client, helpers): @pytest.mark.parametrize("route", ['main_form', 'edit_file']) def test_rerun_experiments(self, logged_in_user, other_user, client, helpers, route): # Set up previous experiments - model = recipes.model.make(author=logged_in_user) - model_first_commit = helpers.add_version(model, visibility='private') - model_commit = helpers.add_version(model, visibility='private') - other_model = recipes.model.make(author=other_user) - other_model_commit = helpers.add_version(other_model, visibility='public') + m1 = recipes.model.make(author=logged_in_user) + m1v1 = helpers.add_version(m1, visibility='private') + m1v2 = helpers.add_version(m1, visibility='private') + + m2 = recipes.model.make(author=other_user) + m2v1 = helpers.add_version(m2, visibility='public') def _add_experiment(proto_author, proto_vis, shared=False, proto=None, proto_commit=None, - model=model, model_commit=model_commit): + model=m1, model_commit=m1v1): if proto is None: proto = recipes.protocol.make(author=proto_author) proto_commit = helpers.add_version(proto, visibility=proto_vis) @@ -1370,29 +1371,29 @@ def _add_experiment(proto_author, proto_vis, shared=False, assign_perm('edit_entity', logged_in_user, proto) return proto - my_private_protocol = _add_experiment(logged_in_user, 'private') # Re-run case 1 - _add_experiment(logged_in_user, 'private', # Shouldn't be re-run - model=model, model_commit=model_first_commit) - _add_experiment(logged_in_user, 'private', # Does get re-run because same proto version as case 1 - proto=my_private_protocol, proto_commit=my_private_protocol.repo.latest_commit, - model=model, model_commit=model_first_commit) - public_protocol = _add_experiment(other_user, 'public') # Re-run case 2 - _add_experiment(other_user, 'public', # Not re-run as other model - proto=public_protocol, proto_commit=public_protocol.repo.latest_commit, - model=other_model, model_commit=other_model_commit) - _add_experiment(other_user, 'private') # Not re-run as can't see protocol - visible_protocol = _add_experiment(other_user, 'private', shared=True) # Re-run case 3 + p1 = _add_experiment(logged_in_user, 'private', model=m1, model_commit=m1v2) + p1v2 = helpers.add_version(p1, visibility='private') + _add_experiment(logged_in_user, 'private', model=m1, model_commit=m1v1, proto=p1, proto_commit=p1v2) + _add_experiment(logged_in_user, 'private', model=m2, model_commit=m2v1, proto=p1, proto_commit=p1v2) + _add_experiment(logged_in_user, 'public', model=m2, model_commit=m2v1) + p3 = _add_experiment(other_user, 'public', model=m1, model_commit=m1v2) + p3v1 = p3.repocache.latest_version + p3v2 = helpers.add_version(p3, visibility='private') + _add_experiment(logged_in_user, 'private', model=m1, model_commit=m1v2, proto=p3, proto_commit=p3v2) + p4 = _add_experiment(other_user, 'public', model=m1, model_commit=m1v2, shared=True) + p4v2 = helpers.add_version(p4, visibility='private') + _add_experiment(other_user, 'private', model=m1, model_commit=m1v2) # Create a new version of our model, re-running experiments helpers.add_permission(logged_in_user, 'create_model') if route == 'main_form': recipes.model_file.make( - entity=model, + entity=m1, upload=SimpleUploadedFile('file2.txt', b'file 2'), original_name='file2.txt', ) response = client.post( - '/entities/models/%d/versions/new' % model.pk, + '/entities/models/%d/versions/new' % m1.pk, data={ 'filename[]': ['uploads/file2.txt'], 'mainEntry': ['file2.txt'], @@ -1403,12 +1404,12 @@ def _add_experiment(proto_author, proto_vis, shared=False, }, ) assert response.status_code == 302 - new_commit = model.repo.latest_commit - assert response.url == '/entities/models/%d/versions/%s' % (model.id, new_commit.sha) + new_commit = m1.repo.latest_commit + assert response.url == '/entities/models/%d/versions/%s' % (m1.id, new_commit.sha) else: assert route == 'edit_file' - response = client.post('/entities/models/%d/versions/edit' % model.id, json.dumps({ - 'parent_hexsha': model_commit.sha, + response = client.post('/entities/models/%d/versions/edit' % m1.id, json.dumps({ + 'parent_hexsha': m1v2.sha, 'file_name': 'file1.txt', 'file_contents': 'new file 1', 'visibility': 'private', @@ -1418,22 +1419,24 @@ def _add_experiment(proto_author, proto_vis, shared=False, }), content_type='application/json') assert response.status_code == 200 detail = json.loads(response.content.decode()) + print(detail) assert 'updateEntityFile' in detail + assert detail['updateEntityFile']['response'] - new_commit = model.repo.latest_commit + new_commit = m1.repo.latest_commit assert detail['updateEntityFile']['url'] == '/entities/models/%d/versions/%s' % ( - model.id, new_commit.sha) + m1.id, new_commit.sha) # Test that planned experiments have been added correctly expected_proto_versions = set([ - (my_private_protocol, my_private_protocol.repo.latest_commit.sha), - (public_protocol, public_protocol.repo.latest_commit.sha), - (visible_protocol, visible_protocol.repo.latest_commit.sha), + (p1, p1v2.sha), + (p3, p3v1.sha), + (p4, p4v2.sha), ]) assert len(expected_proto_versions) == PlannedExperiment.objects.count() for planned_experiment in PlannedExperiment.objects.all(): assert planned_experiment.submitter == logged_in_user - assert planned_experiment.model == model + assert planned_experiment.model == m1 assert planned_experiment.model_version == new_commit.sha assert (planned_experiment.protocol, planned_experiment.protocol_version) in expected_proto_versions From 5c03af0dbbd91e3e524bf2ea517354f305486557 Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Fri, 16 Oct 2020 16:59:36 +0100 Subject: [PATCH 3/5] Update weblab/entities/tests/test_views.py Co-authored-by: Jonathan Cooper --- weblab/entities/tests/test_views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/weblab/entities/tests/test_views.py b/weblab/entities/tests/test_views.py index ce6fe0b63..875515d25 100644 --- a/weblab/entities/tests/test_views.py +++ b/weblab/entities/tests/test_views.py @@ -1421,7 +1421,6 @@ def _add_experiment(proto_author, proto_vis, shared=False, detail = json.loads(response.content.decode()) print(detail) assert 'updateEntityFile' in detail - assert detail['updateEntityFile']['response'] new_commit = m1.repo.latest_commit assert detail['updateEntityFile']['url'] == '/entities/models/%d/versions/%s' % ( From 6b75514e00f8a2c437f56b4234e08234552d5dd5 Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Fri, 16 Oct 2020 16:59:50 +0100 Subject: [PATCH 4/5] Update weblab/entities/tests/test_views.py Co-authored-by: Jonathan Cooper --- weblab/entities/tests/test_views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/weblab/entities/tests/test_views.py b/weblab/entities/tests/test_views.py index 875515d25..4eb7263fd 100644 --- a/weblab/entities/tests/test_views.py +++ b/weblab/entities/tests/test_views.py @@ -1419,7 +1419,6 @@ def _add_experiment(proto_author, proto_vis, shared=False, }), content_type='application/json') assert response.status_code == 200 detail = json.loads(response.content.decode()) - print(detail) assert 'updateEntityFile' in detail assert detail['updateEntityFile']['response'] new_commit = m1.repo.latest_commit From 567a970968986f97c3d966afb4bf58aae92f1e34 Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Fri, 16 Oct 2020 17:01:25 +0100 Subject: [PATCH 5/5] Changed documentation --- weblab/entities/processing.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/weblab/entities/processing.py b/weblab/entities/processing.py index c8480925b..2809eafe6 100644 --- a/weblab/entities/processing.py +++ b/weblab/entities/processing.py @@ -160,8 +160,10 @@ def process_check_protocol_callback(data): def record_experiments_to_run(user, entity, commit): """Record what experiments to run automatically on a new entity version. - The latest experiments that were run with the parent version(s) (that are visible to the user) - should be repeated with the new version. + Find all experiments run with the entity for which we're adding a new version (consider case of a model) + Get the list of corresponding protocol IDs. We now know both our model ID, and all protocols that have + previously had any version run on any version of this model. + Run our new model version under the latest (visible) version of all those protocols. :param user: the user that created the new version :param entity: the entity that has had a new version created