Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds support for registering descriptions and example imports for artifact classes #655

Merged
merged 19 commits into from
Oct 25, 2022

Conversation

gregcaporaso
Copy link
Member

Adds support for registering descriptions and import examples for artifact classes. Artifact class is a new term that we're using to refer to semantic types with a registered directory format. Previously these would be registered with Plugin.register_semantic_type_to_format, and that is still possible for backward compatibility. These can also now be registered with Plugin.register_artifact_class, which optionally takes a description and a dict of usage examples.

Addresses #649.

ArtifactClassRecords are now returned by PluginManager.get_semantic_types()
as these are more descriptive than SemanticTypeRecords. For backward
compatibility, ArtifactClassRecords have values semantic_type and
type_expression, which are set to the same value upon creation of
ArtifactClassRecords during plugin registration.
Plugin.types returns dict of ArtifactClassRecords instead of
SemanticTypeRecords as the former are more descriptive.
Plugin.type_formats was renamed Plugin.artifact_classes for clarity,
and a Plugin.type_formats property was added for backward compatibility.

PluginManager.type_formats was renamed PluginManager.artifact_classes for clarity,
and a PluginManager.type_formats property was added for backward compatibility.

Both properties return the corresponding artifact_classes variable
Copy link
Member

@ebolyen ebolyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @gregcaporaso, this all looks good to me, but one thing that I think will simplify a lot of the code would be to turn artifact_classes into a dictionary keyed by semantic type.

Then anywhere you cared about a list, artifact_classes.values() would be fine, and the rest of the time, you actually are interested in the semantic type for duplicate checking which would be a key.


to_import = use.init_format('to_import', factory, ext='.hello')

ints = use.import_from_format('ints',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linter is upset about these, you can delete the variable assignment and the usage example won't care

def factory():
from qiime2.core.testing.format import IntSequenceFormat
from qiime2.plugin.util import transform
ff = transform([1, 2, 3], to_type=IntSequenceFormat)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably the best use of transform within a plugin context I've seen. I always forget this function exists.

def type_formats(self):
# self.type_formats was replaced with self.artifact_classes - this
# property provides backward compatibility
return self.artifact_classes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

examples = {}

registered_artifact_classes = \
set(str(e.type_expression) for e in self.artifact_classes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be worth storing on the plugin instance as a private member, as otherwise we'll be constructing this set for each registration, resulting in n^2 iterations through self.artifact_classes.

A good option might be to turn types into a mappingproxy of an internal dict, then you can simply check against self.types as things are being constructed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually instead of self.types self.artifact_classes would be fine I think.

@@ -79,7 +83,7 @@ def __init__(self, name, version, website, package=None, project_name=None,
self.views = {}
self.type_fragments = {}
self.transformers = {}
self.type_formats = []
self.artifact_classes = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be better as a dictionary for some of the registration behavior

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call - thanks for catching that! Made this change and most of my commit responding to your comments is updates to address this.

@@ -201,7 +201,7 @@ def _integrate_plugin(self, plugin):
)

self.formats[name] = record
self.type_formats.extend(plugin.type_formats)
self.artifact_classes.extend(plugin.artifact_classes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was missing originally, but we should do a similar check as the other properties to make sure duplicate registrations (across plugins) are forbidden.

Copy link
Member Author

@gregcaporaso gregcaporaso Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this test and validated that it works manually and reports a useful error message (from my test: ValueError: Duplicate semantic type ('PCoAResults') defined in plugins: 'sapienns' and 'types'). I didn't see where/if this type of functionality was being unit tested, so I didn't add unit tests for it now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no pleasant way to test for this, so eyeballing it is fine with me.

@gregcaporaso gregcaporaso marked this pull request as ready for review October 24, 2022 01:43
@gregcaporaso gregcaporaso requested a review from ebolyen October 24, 2022 01:43
# Evan, ideally we could just lookup semantic_type in
# self.artifact_classes but properties get in the way. Is there a way
# to strip properties so this could be simplified to return
# self.artifact_classes[semantic_type] while catching a KeyError?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's not a global helper for this, but there is a .duplicate() method which allows you to override attributes of a type, so (SomeType % Foo).duplicate(predicate=IntersectionExp()) would accomplish your goal. It's not pretty and it's also not recursive, so any variant fields would need to be recursively duplicated as well.

The behavior below is correct and the above sounds like too much work. So let's ignore this for now.

Copy link
Member

@ebolyen ebolyen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just promoting the comment to a TODO, since it's useful context and asks the kind of question which would necessitate some nicer mechanisms than what I described.

qiime2/sdk/plugin_manager.py Outdated Show resolved Hide resolved
@ebolyen ebolyen merged commit 05c515b into qiime2:master Oct 25, 2022
@ebolyen
Copy link
Member

ebolyen commented Oct 25, 2022

Thanks @gregcaporaso!

@gregcaporaso
Copy link
Member Author

Thanks for the reviews @ebolyen!

@gregcaporaso gregcaporaso deleted the issue-649 branch October 25, 2022 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants