diff --git a/q2_qsip2/plugin_setup.py b/q2_qsip2/plugin_setup.py index 5ce9ea9..627d045 100644 --- a/q2_qsip2/plugin_setup.py +++ b/q2_qsip2/plugin_setup.py @@ -13,7 +13,7 @@ from q2_qsip2 import __version__ from q2_qsip2.workflow import standard_workflow, create_qsip_data -from q2_qsip2.types import QSIP2Data +from q2_qsip2.types import QSIP2Data, Unfiltered, Filtered, EAF from q2_qsip2.visualizers._visualizers import ( plot_weighted_average_densities, plot_sample_curves, plot_density_outliers, show_comparison_groups @@ -40,7 +40,7 @@ function=standard_workflow, inputs={ 'table': FeatureTable[Frequency], - 'qsip_metadata': QSIP2Data, + 'qsip_metadata': QSIP2Data[Unfiltered], }, parameters={}, outputs=[ @@ -76,7 +76,7 @@ 'gradient_pos_amt_column': Str, }, outputs=[ - ('qsip_data', QSIP2Data) + ('qsip_data', QSIP2Data[Unfiltered]) ], input_descriptions={}, parameter_descriptions={ @@ -101,7 +101,7 @@ plugin.visualizers.register_function( function=plot_weighted_average_densities, inputs={ - 'qsip_data': QSIP2Data + 'qsip_data': QSIP2Data[Unfiltered] }, parameters={ 'group': Str @@ -124,7 +124,7 @@ plugin.visualizers.register_function( function=plot_sample_curves, inputs={ - 'qsip_data': QSIP2Data + 'qsip_data': QSIP2Data[Unfiltered] }, parameters={}, input_descriptions={ @@ -141,7 +141,7 @@ plugin.visualizers.register_function( function=plot_density_outliers, inputs={ - 'qsip_data': QSIP2Data + 'qsip_data': QSIP2Data[Unfiltered] }, parameters={}, input_descriptions={ @@ -159,7 +159,7 @@ plugin.visualizers.register_function( function=show_comparison_groups, inputs={ - 'qsip_data': QSIP2Data + 'qsip_data': QSIP2Data[Unfiltered] }, parameters={ 'groups': List[Str] diff --git a/q2_qsip2/types/__init__.py b/q2_qsip2/types/__init__.py index 113063a..2602fbe 100644 --- a/q2_qsip2/types/__init__.py +++ b/q2_qsip2/types/__init__.py @@ -1,8 +1,13 @@ from q2_qsip2.types._formats import ( - QSIP2DataFormat, QSIP2DataDirectoryFormat + QSIP2DataUnfilteredFormat, QSIP2DataUnfilteredDirectoryFormat, + QSIP2DataFilteredFormat, QSIP2DataFilteredDirectoryFormat, + QSIP2DataEAFFormat, QSIP2DataEAFDirectoryFormat ) -from q2_qsip2.types._types import QSIP2Data +from q2_qsip2.types._types import QSIP2Data, Unfiltered, Filtered, EAF __all__ = [ - 'QSIP2DataFormat', 'QSIP2DataDirectoryFormat', 'QSIP2Data' + 'QSIP2Data', 'Unfiltered', 'Filtered', 'EAF', + 'QSIP2DataUnfilteredFormat', 'QSIP2DataUnfilteredDirectoryFormat', + 'QSIP2DataFilteredFormat', 'QSIP2DataFilteredDirectoryFormat', + 'QSIP2DataEAFFormat', 'QSIP2DataEAFDirectoryFormat' ] diff --git a/q2_qsip2/types/_deferred_setup/__init__.py b/q2_qsip2/types/_deferred_setup/__init__.py index 5f86101..0c0f86b 100644 --- a/q2_qsip2/types/_deferred_setup/__init__.py +++ b/q2_qsip2/types/_deferred_setup/__init__.py @@ -9,21 +9,49 @@ import importlib from q2_qsip2.plugin_setup import plugin -from q2_qsip2.types import ( - QSIP2Data, QSIP2DataFormat, QSIP2DataDirectoryFormat +from q2_qsip2.types._types import ( + QSIP2Data, Unfiltered, Filtered, EAF +) +from q2_qsip2.types._formats import ( + QSIP2DataUnfilteredFormat, QSIP2DataUnfilteredDirectoryFormat, + QSIP2DataFilteredFormat, QSIP2DataFilteredDirectoryFormat, + QSIP2DataEAFFormat, QSIP2DataEAFDirectoryFormat ) -plugin.register_semantic_types(QSIP2Data) +plugin.register_semantic_types(QSIP2Data, Unfiltered, Filtered, EAF) plugin.register_formats( - QSIP2DataFormat, QSIP2DataDirectoryFormat + QSIP2DataUnfilteredFormat, QSIP2DataUnfilteredDirectoryFormat, + QSIP2DataFilteredFormat, QSIP2DataFilteredDirectoryFormat, + QSIP2DataEAFFormat, QSIP2DataEAFDirectoryFormat +) + +plugin.register_artifact_class( + QSIP2Data[Unfiltered], + directory_format=QSIP2DataUnfilteredDirectoryFormat, + description=( + 'Represents initial imported qSIP2 data, containing source- and ' + 'sample-level metadata and a feature table.' + ) +) + +plugin.register_artifact_class( + QSIP2Data[Filtered], + directory_format=QSIP2DataFilteredDirectoryFormat, + description=( + 'Represents filtered qSIP2 data, containg labeled and unlabeled ' + 'sources that constitute a desired comparison.' + ) ) plugin.register_artifact_class( - QSIP2Data, - directory_format=QSIP2DataDirectoryFormat, - description="A serialized qSIP2 data object." + QSIP2Data[EAF], + directory_format=QSIP2DataEAFDirectoryFormat, + description=( + 'Represents fully process qSIP2 data that has been resampled and ' + 'had excess atom fractions (EAF) calculated.' + ) ) importlib.import_module('._transformers', __name__) diff --git a/q2_qsip2/types/_deferred_setup/_transformers.py b/q2_qsip2/types/_deferred_setup/_transformers.py index 1532248..ee22925 100644 --- a/q2_qsip2/types/_deferred_setup/_transformers.py +++ b/q2_qsip2/types/_deferred_setup/_transformers.py @@ -11,12 +11,19 @@ import pickle from q2_qsip2.plugin_setup import plugin -from q2_qsip2.types import QSIP2DataFormat +from q2_qsip2.types import ( + QSIP2DataUnfilteredFormat, QSIP2DataFilteredFormat, QSIP2DataEAFFormat +) -@plugin.register_transformer -def _1(qsip_object: RS4) -> QSIP2DataFormat: - ff = QSIP2DataFormat() +def _format_to_qsip_object(ff): + with ff.open() as fh: + qsip_object = pickle.load(fh) + + return qsip_object + + +def _qsip_object_to_format(qsip_object, ff): with ff.open() as fh: pickle.dump(qsip_object, fh) @@ -24,8 +31,33 @@ def _1(qsip_object: RS4) -> QSIP2DataFormat: @plugin.register_transformer -def _2(ff: QSIP2DataFormat) -> RS4: - with ff.open() as fh: - qsip_object = pickle.load(fh) +def _1(qsip_object: RS4) -> QSIP2DataUnfilteredFormat: + ff = QSIP2DataUnfilteredFormat() + return _qsip_object_to_format(qsip_object, ff) - return qsip_object + +@plugin.register_transformer +def _2(ff: QSIP2DataUnfilteredFormat) -> RS4: + return _format_to_qsip_object(ff) + + +@plugin.register_transformer +def _3(qsip_object: RS4) -> QSIP2DataFilteredFormat: + ff = QSIP2DataFilteredFormat() + return _qsip_object_to_format(qsip_object, ff) + + +@plugin.register_transformer +def _4(ff: QSIP2DataFilteredFormat) -> RS4: + return _format_to_qsip_object(ff) + + +@plugin.register_transformer +def _5(qsip_object: RS4) -> QSIP2DataEAFFormat: + ff = QSIP2DataFilteredFormat() + return _qsip_object_to_format(qsip_object, ff) + + +@plugin.register_transformer +def _6(ff: QSIP2DataEAFFormat) -> RS4: + return _format_to_qsip_object(ff) diff --git a/q2_qsip2/types/_formats.py b/q2_qsip2/types/_formats.py index c4d3432..b9ef587 100644 --- a/q2_qsip2/types/_formats.py +++ b/q2_qsip2/types/_formats.py @@ -15,16 +15,19 @@ # TODO: communicate warning about using pickled data -class QSIP2DataFormat(model.BinaryFileFormat): +class QSIP2DataFormatBase(model.BinaryFileFormat): package = 'q2_qsip2.types.tests' + def stage_specific_validation_method(self, qsip_data_obj): + pass + def _validate_(self, level): with self.open() as fh: qsip_data_obj = pickle.load(fh) try: - # TODO: why not implemented in R package? ro.r['validate'](qsip_data_obj) + self.stage_specific_validation_method(qsip_data_obj) except Exception as e: msg = ( 'There was a problem loading your qSIP2 data. See the below ' @@ -33,6 +36,40 @@ def _validate_(self, level): raise ValidationError(msg) -QSIP2DataDirectoryFormat = model.SingleFileDirectoryFormat( - 'QSIP2DataDirectoryFormat', 'qsip-data.pickle', QSIP2DataFormat +class QSIP2DataUnfilteredFormat(QSIP2DataFormatBase): + def stage_specific_validation_method(self, qsip_data_obj): + # TODO: update once implemented in R + pass + + +QSIP2DataUnfilteredDirectoryFormat = model.SingleFileDirectoryFormat( + 'QSIP2DataUnfilteredDirectoryFormat', + 'qsip-data.pickle', + QSIP2DataUnfilteredFormat +) + + +class QSIP2DataFilteredFormat(QSIP2DataFormatBase): + def stage_speicif_validation_method(self, qsip_data_obj): + # TODO: update once implemented in R + pass + + +QSIP2DataFilteredDirectoryFormat = model.SingleFileDirectoryFormat( + 'QSIP2DataFilteredDirectoryFormat', + 'qsip-data.pickle', + QSIP2DataFilteredFormat +) + + +class QSIP2DataEAFFormat(QSIP2DataFormatBase): + def stage_specific_validation_method(self, qsip_data_obj): + # TODO: update once implemented in R + pass + + +QSIP2DataEAFDirectoryFormat = model.SingleFileDirectoryFormat( + 'QSIP2DataEAFDirectoryFormat', + 'qsip-data.pickle', + QSIP2DataEAFFormat ) diff --git a/q2_qsip2/types/_types.py b/q2_qsip2/types/_types.py index 458ba35..8472dc7 100644 --- a/q2_qsip2/types/_types.py +++ b/q2_qsip2/types/_types.py @@ -9,4 +9,10 @@ from qiime2.plugin import SemanticType -QSIP2Data = SemanticType('QSIP2Data') +QSIP2Data = SemanticType('QSIP2Data', field_names='stage') + +Unfiltered = SemanticType('Unfiltered', variant_of=QSIP2Data.field['stage']) + +Filtered = SemanticType('Filtered', variant_of=QSIP2Data.field['stage']) + +EAF = SemanticType('EAF', variant_of=QSIP2Data.field['stage']) diff --git a/q2_qsip2/types/tests/test_formats.py b/q2_qsip2/types/tests/test_formats.py index 7138eac..b7517fc 100644 --- a/q2_qsip2/types/tests/test_formats.py +++ b/q2_qsip2/types/tests/test_formats.py @@ -20,7 +20,7 @@ from qiime2.plugin import ValidationError from qiime2.plugin.testing import TestPluginBase -from q2_qsip2.types import QSIP2DataFormat +from q2_qsip2.types import QSIP2DataUnfilteredFormat from q2_qsip2.workflow import create_qsip_data @@ -47,7 +47,7 @@ def get_feature_table(self): df.values, observation_ids=df.index, sample_ids=df.columns ) - def test_valid_QSIP2DataFormat_from_files(self): + def test_valid_QSIP2DataUnfilteredFormat_from_files(self): source_md = self.get_source_metadata() sample_md = self.get_sample_metadata() table = self.get_feature_table() @@ -55,23 +55,23 @@ def test_valid_QSIP2DataFormat_from_files(self): qsip_object = create_qsip_data(table, sample_md, source_md) transformer = self.get_transformer( - RS4, QSIP2DataFormat + RS4, QSIP2DataUnfilteredFormat ) format = transformer(qsip_object) format.validate() - def test_valid_QSIP2DataFormat_from_pickle(self): + def test_valid_QSIP2DataUnfilteredFormat_from_pickle(self): pickle_fp = ( importlib.resources.files(__package__) / 'data' / 'qsip-data.pickle' ) - format = QSIP2DataFormat(pickle_fp, mode='r') + format = QSIP2DataUnfilteredFormat(pickle_fp, mode='r') format.validate() - def test_invalid_QSIP2DataFormat(self): + def test_invalid_QSIP2DataUnfilteredFormat(self): # create useless rpy2 object to pickle vector = ro.r("c('Q', 'I', 'I', 'M', 'E', '2')") @@ -81,7 +81,7 @@ def test_invalid_QSIP2DataFormat(self): with open(fp, 'wb') as fh: pickle.dump(vector, fh) - format = QSIP2DataFormat(fp, mode='r') + format = QSIP2DataUnfilteredFormat(fp, mode='r') msg = 'There was a problem loading your qSIP2 data.*' with self.assertRaisesRegex(ValidationError, msg): diff --git a/q2_qsip2/types/tests/test_transformers.py b/q2_qsip2/types/tests/test_transformers.py index a05d362..170f77f 100644 --- a/q2_qsip2/types/tests/test_transformers.py +++ b/q2_qsip2/types/tests/test_transformers.py @@ -14,7 +14,7 @@ from qiime2.plugin.testing import TestPluginBase -from q2_qsip2.types import QSIP2DataFormat +from q2_qsip2.types import QSIP2DataUnfilteredFormat class TestTransformers(TestPluginBase): @@ -26,13 +26,17 @@ def get_qsip_object(self): 'data' / 'qsip-data.pickle' ) - transformer = self.get_transformer(QSIP2DataFormat, RS4) + transformer = self.get_transformer(QSIP2DataUnfilteredFormat, RS4) - return transformer(QSIP2DataFormat(pickle_fp, mode='r')) + return transformer(QSIP2DataUnfilteredFormat(pickle_fp, mode='r')) def test_object_to_pickle_file_and_back(self): - from_object_transformer = self.get_transformer(RS4, QSIP2DataFormat) - from_format_transformer = self.get_transformer(QSIP2DataFormat, RS4) + from_object_transformer = self.get_transformer( + RS4, QSIP2DataUnfilteredFormat + ) + from_format_transformer = self.get_transformer( + QSIP2DataUnfilteredFormat, RS4 + ) qsip_object = self.get_qsip_object() ro.r['validate'](qsip_object) diff --git a/q2_qsip2/types/tests/test_types.py b/q2_qsip2/types/tests/test_types.py index f7e0f83..9b0b464 100644 --- a/q2_qsip2/types/tests/test_types.py +++ b/q2_qsip2/types/tests/test_types.py @@ -8,11 +8,14 @@ from qiime2.plugin.testing import TestPluginBase -from q2_qsip2.types import QSIP2Data +from q2_qsip2.types import QSIP2Data, Unfiltered, Filtered, EAF class TestTypes(TestPluginBase): package = 'q2_qsip2.types.tests' - def test_QSIP2Data_type_is_registered(self): + def test_QSIP2Data_types_registered(self): self.assertRegisteredSemanticType(QSIP2Data) + self.assertRegisteredSemanticType(Unfiltered) + self.assertRegisteredSemanticType(Filtered) + self.assertRegisteredSemanticType(EAF)