Skip to content

Commit

Permalink
add study plugin override (#1885)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Sep 30, 2024
1 parent 3a52413 commit 93a13e9
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Added
- ``notify_email_irods_request`` user app setting (#1939)
- Assay app unit tests (#1980)
- Missing assay plugin ``__init__.py`` files (#2014)
- Study plugin override via ISA-Tab comments (#1885)

Changed
-------
Expand Down
18 changes: 18 additions & 0 deletions docs_manual/source/metadata_advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ SODAR currently supports the following study configurations:
If the configuration is not specified or is not known to SODAR, the shortcut
column will not be visible.

It is possible to override the plugin to be used for a study. This allows for
e.g. having different types of studies within a single investigation. The
overriding can be done by adding a ``SODAR Study Plugin`` comment within the
``STUDY`` section of the ISA-Tab investigation file. As the value, the full
internal study plugin name (e.g. ``samplesheets_study_cancer``) should be used.
If both the study plugin comment and investigation configuration comment are
present, the former will override the latter.

Example:

.. code-block::
STUDY
Study Identifier s_small2
Study Title Small Germline Study
Study Description
Comment[SODAR Study Plugin] samplesheets_study_cancer
Study plugins will search for the "latest" BAM and VCF files for shortcuts and
IGV session generation. This is determined by file name: in case of multiple
files, the last file sorted by file name is returned. Hence to ensure the most
Expand Down
1 change: 1 addition & 0 deletions docs_manual/source/sodar_release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Release for SODAR Core v1.0 upgrade, iRODS v4.3 upgrade and feature updates.
- Add opt-out settings for iRODS data request and zone status update emails
- Add REST API list view pagination
- Add Python v3.11 support
- Add study plugin override via ISA-Tab comments
- Add session control in Django settings and environment variables
- Update minimum supported iRODS version to v4.3.3
- Update REST API versioning
Expand Down
15 changes: 13 additions & 2 deletions samplesheets/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
IRODS_REQUEST_STATUS_FAILED = 'FAILED'
IRODS_REQUEST_STATUS_REJECTED = 'REJECTED'

# ISA-Tab SODAR metadata comment key for assay plugin override
# ISA-Tab SODAR metadata comment keys for study and assay plugin overrides
ISA_META_STUDY_PLUGIN = 'SODAR Study Plugin'
ISA_META_ASSAY_PLUGIN = 'SODAR Assay Plugin'


Expand Down Expand Up @@ -403,9 +404,19 @@ def get_plugin(self):
"""Return active study app plugin or None if not found"""
from samplesheets.plugins import SampleSheetStudyPluginPoint

inv_config = self.investigation.get_configuration()
study_override = self.comments.get(ISA_META_STUDY_PLUGIN)
if study_override:
try:
return SampleSheetStudyPluginPoint.get_plugin(
name=study_override
)
except Exception:
return None
for plugin in SampleSheetStudyPluginPoint.get_plugins():
if plugin.config_name == self.investigation.get_configuration():
if plugin.config_name == inv_config:
return plugin
return None

def get_url(self):
"""Return the URL for this study"""
Expand Down
9 changes: 6 additions & 3 deletions samplesheets/studyapps/cancer/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,19 @@ def update_cache(self, name=None, project=None, user=None):
)
except Investigation.DoesNotExist:
continue
# Only apply for investigations with the correct configuration
if investigation.get_configuration() != self.config_name:
continue
# If a name is given, only update that specific CacheItem
if name:
study_uuid = name.split('/')[-1]
studies = Study.objects.filter(sodar_uuid=study_uuid)
else:
studies = Study.objects.filter(investigation=investigation)
for study in studies:
# Only apply for studies using this plugin
if (
not study.get_plugin()
or study.get_plugin().__class__ != self.__class__
):
continue
if self._has_only_ms_assays(study):
continue
self._update_study_cache(study, user, cache_backend)
47 changes: 44 additions & 3 deletions samplesheets/studyapps/cancer/tests/test_plugins_taskflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
# Taskflowbackend dependency
from taskflowbackend.tests.base import TaskflowViewTestBase

from samplesheets.models import ISA_META_STUDY_PLUGIN
from samplesheets.plugins import SampleSheetStudyPluginPoint
from samplesheets.rendering import SampleSheetTableBuilder
from samplesheets.studyapps.cancer.utils import get_library_file_path
from samplesheets.studyapps.utils import get_igv_session_url
from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR
from samplesheets.tests.test_models import SampleSheetModelMixin
from samplesheets.tests.test_views_taskflow import SampleSheetTaskflowMixin
from samplesheets.plugins import SampleSheetStudyPluginPoint
from samplesheets.studyapps.cancer.utils import get_library_file_path
from samplesheets.studyapps.utils import get_igv_session_url


app_settings = AppSettingAPI()
Expand Down Expand Up @@ -560,6 +561,18 @@ def test_update_cache(self):
self.assertIsNone(ci['bam'][c])
self.assertIsNone(ci['vcf'][c])

def test_update_cache_no_config(self):
"""Test update_cache() without config"""
# Clear investigation configuration comments
self.investigation.comments = {}
self.investigation.save()
self.plugin.update_cache(self.cache_name, self.project)
self.assertIsNone(
self.cache_backend.get_cache_item(
APP_NAME, self.cache_name, self.project
)
)

def test_update_cache_files(self):
"""Test update_cache() with files in iRODS"""
self.irods.collections.create(self.source_path)
Expand All @@ -581,6 +594,34 @@ def test_update_cache_files(self):
self.assertEqual(ci['bam'][CASE_IDS[i]], None)
self.assertEqual(ci['vcf'][CASE_IDS[i]], None)

def test_update_cache_files_override(self):
"""Test update_cache() with files in iRODS and study override"""
# Clear investigation configuration comments and set study override
self.investigation.comments = {}
self.investigation.save()
self.study.comments = {
ISA_META_STUDY_PLUGIN: 'samplesheets_study_cancer'
}
self.study.save()
self.irods.collections.create(self.source_path)
bam_path = os.path.join(
self.source_path, '{}_test.bam'.format(SAMPLE_ID_NORMAL)
)
vcf_path = os.path.join(
self.source_path, '{}_test.vcf.gz'.format(SAMPLE_ID_NORMAL)
)
self.irods.data_objects.create(bam_path)
self.irods.data_objects.create(vcf_path)
self.plugin.update_cache(self.cache_name, self.project)
ci = self.cache_backend.get_cache_item(
APP_NAME, self.cache_name, self.project
).data
self.assertEqual(ci['bam'][CASE_IDS[0]], bam_path)
self.assertEqual(ci['vcf'][CASE_IDS[0]], vcf_path)
for i in range(1, len(CASE_IDS) - 1):
self.assertEqual(ci['bam'][CASE_IDS[i]], None)
self.assertEqual(ci['vcf'][CASE_IDS[i]], None)

def test_update_cache_cram(self):
"""Test update_cache() with CRAM file in iRODS"""
self.irods.collections.create(self.source_path)
Expand Down
8 changes: 6 additions & 2 deletions samplesheets/studyapps/germline/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,6 @@ def update_cache(self, name=None, project=None, user=None):
if not investigation:
continue
# Only apply for investigations with the correct configuration
if investigation.get_configuration() != self.config_name:
continue
logger.debug(
'Updating cache for project {}..'.format(
project.get_log_title()
Expand All @@ -438,6 +436,12 @@ def update_cache(self, name=None, project=None, user=None):
else:
studies = Study.objects.filter(investigation=investigation)
for study in studies:
# Only apply for studies using this plugin
if (
not study.get_plugin()
or study.get_plugin().__class__ != self.__class__
):
continue
logger.debug(
'Updating cache for study "{}" ({})..'.format(
study.get_display_name(), study.sodar_uuid
Expand Down
46 changes: 42 additions & 4 deletions samplesheets/studyapps/germline/tests/test_plugins_taskflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
# Taskflowbackend dependency
from taskflowbackend.tests.base import TaskflowViewTestBase

from samplesheets.models import GenericMaterial
from samplesheets.models import GenericMaterial, ISA_META_STUDY_PLUGIN
from samplesheets.plugins import SampleSheetStudyPluginPoint
from samplesheets.rendering import SampleSheetTableBuilder
from samplesheets.studyapps.germline.utils import get_pedigree_file_path
from samplesheets.studyapps.utils import get_igv_session_url
from samplesheets.tests.test_io import SampleSheetIOMixin, SHEET_DIR
from samplesheets.tests.test_models import SampleSheetModelMixin
from samplesheets.tests.test_views_taskflow import SampleSheetTaskflowMixin
from samplesheets.plugins import SampleSheetStudyPluginPoint
from samplesheets.studyapps.germline.utils import get_pedigree_file_path
from samplesheets.studyapps.utils import get_igv_session_url


app_settings = AppSettingAPI()
Expand Down Expand Up @@ -594,6 +594,18 @@ def test_update_cache(self):
self.assertIsNone(ci['vcf'][FAMILY_ID])
self.assertIsNone(ci['vcf'][FAMILY_ID2])

def test_update_cache_no_config(self):
"""Test update_cache() without config"""
# Clear investigation configuration comments
self.investigation.comments = {}
self.investigation.save()
self.plugin.update_cache(self.cache_name, self.project)
self.assertIsNone(
self.cache_backend.get_cache_item(
APP_NAME, self.cache_name, self.project
)
)

def test_update_cache_files(self):
"""Test update_cache() with files in iRODS"""
self.irods.collections.create(self.source_path)
Expand All @@ -613,6 +625,32 @@ def test_update_cache_files(self):
self.assertEqual(ci['vcf'][FAMILY_ID], vcf_path)
self.assertIsNone(ci['vcf'][FAMILY_ID2])

def test_update_cache_files_override(self):
"""Test update_cache() with files in iRODS and study override"""
# Clear investigation configuration comments and set study override
self.investigation.comments = {}
self.investigation.save()
self.study.comments = {
ISA_META_STUDY_PLUGIN: 'samplesheets_study_germline'
}
self.study.save()
self.irods.collections.create(self.source_path)
bam_path = os.path.join(
self.source_path, '{}_test.bam'.format(SAMPLE_ID)
)
vcf_path = os.path.join(
self.source_path, '{}_test.vcf.gz'.format(FAMILY_ID)
)
self.irods.data_objects.create(bam_path)
self.irods.data_objects.create(vcf_path)
self.plugin.update_cache(self.cache_name, self.project)
ci = self.cache_backend.get_cache_item(
APP_NAME, self.cache_name, self.project
).data
self.assertEqual(ci['bam'][self.source.name], bam_path)
self.assertEqual(ci['vcf'][FAMILY_ID], vcf_path)
self.assertIsNone(ci['vcf'][FAMILY_ID2])

def test_update_cache_cram(self):
"""Test update_cache() with CRAM file in iRODS"""
self.irods.collections.create(self.source_path)
Expand Down
48 changes: 48 additions & 0 deletions samplesheets/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@
IrodsDataRequest,
NOT_AVAILABLE_STR,
CONFIG_LABEL_CREATE,
ISA_META_STUDY_PLUGIN,
ISA_META_ASSAY_PLUGIN,
IRODS_REQUEST_ACTION_DELETE,
IRODS_REQUEST_STATUS_ACTIVE,
)
from samplesheets.plugins import SampleSheetStudyPluginPoint
from samplesheets.utils import get_alt_names


Expand Down Expand Up @@ -613,6 +615,52 @@ def test_get_url(self):
) + '#/study/{}'.format(self.study.sodar_uuid)
self.assertEqual(self.study.get_url(), expected)

def test_get_plugin_unset(self):
"""Test get_plugin() with no config set"""
self.assertIsNone(self.study.get_plugin())

def test_get_plugin_investigation(self):
"""Test get_plugin() with config set in investigation"""
self.investigation.comments = {CONFIG_LABEL_CREATE: 'bih_germline'}
self.assertIsInstance(
self.study.get_plugin(),
SampleSheetStudyPluginPoint.get_plugin(
name='samplesheets_study_germline'
).__class__,
)

def test_get_plugin_override(self):
"""Test get_plugin() with config set in study override"""
self.study.comments = {
ISA_META_STUDY_PLUGIN: 'samplesheets_study_germline'
}
self.assertIsInstance(
self.study.get_plugin(),
SampleSheetStudyPluginPoint.get_plugin(
name='samplesheets_study_germline'
).__class__,
)

def test_get_plugin_override_invalid(self):
"""Test get_plugin() with invalid config name set in study override"""
self.study.comments = {
ISA_META_STUDY_PLUGIN: 'samplesheets_study_NONEXISTENT_NAME'
}
self.assertIsNone(self.study.get_plugin())

def test_get_plugin_both(self):
"""Test get_plugin() with investigation and study configs"""
self.investigation.comments = {CONFIG_LABEL_CREATE: 'bih_germline'}
self.study.comments = {
ISA_META_STUDY_PLUGIN: 'samplesheets_study_cancer'
}
self.assertIsInstance(
self.study.get_plugin(),
SampleSheetStudyPluginPoint.get_plugin(
name='samplesheets_study_cancer'
).__class__,
)


class TestProtocol(SamplesheetsModelTestBase):
"""Tests for the Protocol model"""
Expand Down

0 comments on commit 93a13e9

Please sign in to comment.