From 7fd197fd92866628f4e421810a5dc9b8eedf7506 Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Tue, 26 Oct 2021 18:05:56 -0400 Subject: [PATCH 01/10] URIProperty auto-converts to lists Add auto-conversion of initial values to lists. In the process clean up typing declarations for URIProperty uses. --- sbol3/attachment.py | 5 +++-- sbol3/combderiv.py | 4 ++-- sbol3/component.py | 19 ++++++++++--------- sbol3/document.py | 7 ++++--- sbol3/extdef.py | 3 ++- sbol3/feature.py | 16 ++++++++++------ sbol3/identified.py | 15 +++++++++------ sbol3/interaction.py | 8 +++++--- sbol3/localsub.py | 3 ++- sbol3/location.py | 9 +++++---- sbol3/model.py | 13 +++++++------ sbol3/om_unit.py | 14 ++++++++------ sbol3/participation.py | 3 ++- sbol3/provenance.py | 27 +++++++++++++++------------ sbol3/sequence.py | 10 ++++++---- sbol3/subcomponent.py | 9 +++++---- sbol3/toplevel.py | 9 +++++---- sbol3/uri_property.py | 6 +++++- sbol3/varcomp.py | 5 +++-- test/test_identified.py | 8 ++++++++ 20 files changed, 116 insertions(+), 77 deletions(-) diff --git a/sbol3/attachment.py b/sbol3/attachment.py index f6261d0..1451667 100644 --- a/sbol3/attachment.py +++ b/sbol3/attachment.py @@ -1,4 +1,4 @@ -from typing import List, Any +from typing import List, Any, Optional from . import * @@ -11,7 +11,8 @@ class Attachment(TopLevel): """ def __init__(self, identity: str, source: str, - *, format: str = None, size: int = None, + *, format: Optional[str] = None, + size: int = None, hash: str = None, hash_algorithm: str = None, namespace: str = None, attachments: List[str] = None, diff --git a/sbol3/combderiv.py b/sbol3/combderiv.py index e70ca11..ae66eae 100644 --- a/sbol3/combderiv.py +++ b/sbol3/combderiv.py @@ -1,5 +1,5 @@ import math -from typing import Union, List, Any +from typing import Union, List, Any, Optional from . import * @@ -18,7 +18,7 @@ class properties template, hasVariableFeature, and strategy. """ def __init__(self, identity: str, template: Union[Component, str], - *, strategy: str = None, + *, strategy: Optional[str] = None, variable_features: List[str] = None, namespace: str = None, attachments: List[str] = None, diff --git a/sbol3/component.py b/sbol3/component.py index 8ab2053..618dc77 100644 --- a/sbol3/component.py +++ b/sbol3/component.py @@ -1,13 +1,16 @@ +from __future__ import annotations import math -from typing import List, Union, Any +from typing import List, Union, Any, Optional from . import * +from .typing import * + class Component(TopLevel): - def __init__(self, identity: str, types: Union[List[str], str], - *, roles: List[str] = None, + def __init__(self, identity: str, types: Union[str, list[str]], + *, roles: Optional[Union[str, list[str]]] = None, sequences: List[str] = None, features: List[Feature] = None, constraints: List[Constraint] = None, @@ -24,12 +27,10 @@ def __init__(self, identity: str, types: Union[List[str], str], attachments=attachments, name=name, description=description, derived_from=derived_from, generated_by=generated_by, measures=measures) - if isinstance(types, str): - types = [types] - self.types: Union[List, Property] = URIProperty(self, SBOL_TYPE, 1, math.inf, - initial_value=types) - self.roles = URIProperty(self, SBOL_ROLE, 0, math.inf, - initial_value=roles) + self.types: uri_list = URIProperty(self, SBOL_TYPE, 1, math.inf, + initial_value=types) + self.roles: uri_list = URIProperty(self, SBOL_ROLE, 0, math.inf, + initial_value=roles) self.sequences = ReferencedObject(self, SBOL_SEQUENCES, 0, math.inf, initial_value=sequences) self.features = OwnedObject(self, SBOL_FEATURES, 0, math.inf, diff --git a/sbol3/document.py b/sbol3/document.py index d5517e7..15a7882 100644 --- a/sbol3/document.py +++ b/sbol3/document.py @@ -6,7 +6,7 @@ # import typing for typing.Sequence, which we don't want to confuse # with sbol3.Sequence -import typing +import typing as pytyping import pyshacl import rdflib @@ -284,7 +284,7 @@ def assign_document(x: Identified): obj.traverse(assign_document) return obj - def _add_all(self, objects: typing.Sequence[TopLevel]) -> typing.Sequence[TopLevel]: + def _add_all(self, objects: pytyping.Sequence[TopLevel]) -> pytyping.Sequence[TopLevel]: # Perform type check of all objects. # We do this to avoid finding out part way through that an # object can't be added. That would leave the document in an @@ -302,7 +302,8 @@ def _add_all(self, objects: typing.Sequence[TopLevel]) -> typing.Sequence[TopLev # return the passed argument return objects - def add(self, objects: Union[TopLevel, typing.Sequence[TopLevel]]) -> Union[TopLevel, typing.Sequence[TopLevel]]: + def add(self, + objects: Union[TopLevel, pytyping.Sequence[TopLevel]]) -> Union[TopLevel, pytyping.Sequence[TopLevel]]: # objects must be TopLevel or iterable. If neither, raise a TypeError. # # Note: Python documentation for collections.abc says "The only diff --git a/sbol3/extdef.py b/sbol3/extdef.py index 7ea29bf..a80de53 100644 --- a/sbol3/extdef.py +++ b/sbol3/extdef.py @@ -1,3 +1,4 @@ +from __future__ import annotations import math from typing import Any @@ -15,7 +16,7 @@ class ExternallyDefined(Feature): """ - def __init__(self, types: List[str], definition: str, + def __init__(self, types: Union[str, list[str]], definition: str, *, roles: List[str] = None, orientation: str = None, name: str = None, description: str = None, derived_from: List[str] = None, diff --git a/sbol3/feature.py b/sbol3/feature.py index 80a251a..f3543b8 100644 --- a/sbol3/feature.py +++ b/sbol3/feature.py @@ -1,15 +1,19 @@ +from __future__ import annotations import abc import math -from typing import List +from typing import List, Optional from . import * +from .typing import * + class Feature(Identified, abc.ABC): """Feature is an abstract base class.""" def __init__(self, identity: str, type_uri: str, - *, roles: List[str] = None, orientation: str = None, + *, roles: Optional[str, list[str]] = None, + orientation: Optional[str] = None, name: str = None, description: str = None, derived_from: List[str] = None, generated_by: List[str] = None, @@ -17,10 +21,10 @@ def __init__(self, identity: str, type_uri: str, super().__init__(identity=identity, type_uri=type_uri, name=name, description=description, derived_from=derived_from, generated_by=generated_by, measures=measures) - self.roles = URIProperty(self, SBOL_ROLE, 0, math.inf, - initial_value=roles) - self.orientation = URIProperty(self, SBOL_ORIENTATION, 0, 1, - initial_value=orientation) + self.roles: uri_list = URIProperty(self, SBOL_ROLE, 0, math.inf, + initial_value=roles) + self.orientation: uri_singleton = URIProperty(self, SBOL_ORIENTATION, 0, 1, + initial_value=orientation) def validate(self, report: ValidationReport = None) -> ValidationReport: report = super().validate(report) diff --git a/sbol3/identified.py b/sbol3/identified.py index 36cd8c4..ab4fcba 100644 --- a/sbol3/identified.py +++ b/sbol3/identified.py @@ -1,11 +1,13 @@ +from __future__ import annotations import math import posixpath -from typing import Union, List, Callable, Any +from typing import Union, List, Callable, Any, Optional from urllib.parse import urlparse import rdflib from . import * +from .typing import * from .utils import parse_class_name @@ -20,7 +22,8 @@ class Identified(SBOLObject): def __init__(self, identity: str, type_uri: str, *, name: str = None, description: str = None, - derived_from: List[str] = None, generated_by: List[str] = None, + derived_from: Optional[Union[str, list[str]]] = None, + generated_by: List[str] = None, measures: List[SBOLObject] = None) -> None: super().__init__(identity) self._document = None @@ -28,8 +31,8 @@ def __init__(self, identity: str, type_uri: str, self.name = TextProperty(self, SBOL_NAME, 0, 1, initial_value=name) self.description = TextProperty(self, SBOL_DESCRIPTION, 0, 1, initial_value=description) - self.derived_from = URIProperty(self, PROV_DERIVED_FROM, 0, math.inf, - initial_value=derived_from) + self.derived_from: uri_list = URIProperty(self, PROV_DERIVED_FROM, 0, math.inf, + initial_value=derived_from) self.generated_by = ReferencedObject(self, PROV_GENERATED_BY, 0, math.inf, initial_value=generated_by) # The type_constraint for measures should really be Measure but @@ -41,8 +44,8 @@ def __init__(self, identity: str, type_uri: str, type_constraint=Identified) # Identity has been set by the SBOLObject constructor self._display_id = self._extract_display_id(self.identity) - self._rdf_types = URIProperty(self, RDF_TYPE, 1, math.inf, - initial_value=[type_uri]) + self._rdf_types: uri_list = URIProperty(self, RDF_TYPE, 1, math.inf, + initial_value=[type_uri]) @staticmethod def _is_valid_display_id(display_id: str) -> bool: diff --git a/sbol3/interaction.py b/sbol3/interaction.py index 09dc291..a7888b0 100644 --- a/sbol3/interaction.py +++ b/sbol3/interaction.py @@ -1,7 +1,9 @@ +from __future__ import annotations import math from typing import List, Any from . import * +from .typing import * class Interaction(Identified): @@ -19,7 +21,7 @@ class Interaction(Identified): """ - def __init__(self, types: List[str], + def __init__(self, types: Union[str, list[str]], *, participations: List[Participation] = None, name: str = None, description: str = None, derived_from: List[str] = None, @@ -31,8 +33,8 @@ def __init__(self, types: List[str], name=name, description=description, derived_from=derived_from, generated_by=generated_by, measures=measures) - self.types = URIProperty(self, SBOL_TYPE, 1, math.inf, - initial_value=types) + self.types: uri_list = URIProperty(self, SBOL_TYPE, 1, math.inf, + initial_value=types) self.participations = OwnedObject(self, SBOL_PARTICIPATIONS, 0, math.inf, initial_value=participations, type_constraint=Participation) diff --git a/sbol3/localsub.py b/sbol3/localsub.py index 587d902..c112a4d 100644 --- a/sbol3/localsub.py +++ b/sbol3/localsub.py @@ -1,3 +1,4 @@ +from __future__ import annotations import math from typing import Any @@ -15,7 +16,7 @@ class LocalSubComponent(Feature): """ - def __init__(self, types: List[str], + def __init__(self, types: Union[str, list[str]], *, locations: List[Location] = None, roles: List[str] = None, orientation: str = None, name: str = None, description: str = None, diff --git a/sbol3/location.py b/sbol3/location.py index 3e80136..cca6afc 100644 --- a/sbol3/location.py +++ b/sbol3/location.py @@ -1,7 +1,8 @@ import abc -from typing import Union, Any +from typing import Union, Any, Optional from . import * +from .typing import uri_singleton int_property = Union[IntProperty, int] @@ -15,11 +16,11 @@ class Location(Identified, abc.ABC): def __init__(self, sequence: Union[Sequence, str], identity: str, type_uri: str, - *, orientation: str = None, + *, orientation: Optional[str] = None, order: int = None) -> None: super().__init__(identity, type_uri) - self.orientation = URIProperty(self, SBOL_ORIENTATION, 0, 1, - initial_value=orientation) + self.orientation: uri_singleton = URIProperty(self, SBOL_ORIENTATION, 0, 1, + initial_value=orientation) self.order = IntProperty(self, SBOL_ORDER, 0, 1, initial_value=order) self.sequence = ReferencedObject(self, SBOL_SEQUENCES, 1, 1, diff --git a/sbol3/model.py b/sbol3/model.py index 937b445..0de118c 100644 --- a/sbol3/model.py +++ b/sbol3/model.py @@ -1,6 +1,7 @@ from typing import List, Any from . import * +from .typing import uri_singleton class Model(TopLevel): @@ -27,12 +28,12 @@ def __init__(self, identity: str, source: str, language: str, attachments=attachments, name=name, description=description, derived_from=derived_from, generated_by=generated_by, measures=measures) - self.source = URIProperty(self, SBOL_SOURCE, 1, 1, - initial_value=source) - self.language = URIProperty(self, SBOL_LANGUAGE, 1, 1, - initial_value=language) - self.framework = URIProperty(self, SBOL_FRAMEWORK, 1, 1, - initial_value=framework) + self.source: uri_singleton = URIProperty(self, SBOL_SOURCE, 1, 1, + initial_value=source) + self.language: uri_singleton = URIProperty(self, SBOL_LANGUAGE, 1, 1, + initial_value=language) + self.framework: uri_singleton = URIProperty(self, SBOL_FRAMEWORK, 1, 1, + initial_value=framework) def validate(self, report: ValidationReport = None) -> ValidationReport: report = super().validate(report) diff --git a/sbol3/om_unit.py b/sbol3/om_unit.py index 1a94773..7769e36 100644 --- a/sbol3/om_unit.py +++ b/sbol3/om_unit.py @@ -1,9 +1,11 @@ +from __future__ import annotations import abc import math -from typing import Union, List, Any +from typing import Union, List, Any, Optional from . import * from .om_prefix import Prefix +from .typing import * class Unit(CustomTopLevel, abc.ABC): @@ -55,7 +57,7 @@ class Measure(CustomIdentified): """ def __init__(self, value: float, unit: str, - *, types: List[str] = None, + *, types: Optional[str, list[str]] = None, name: str = None, description: str = None, derived_from: List[str] = None, generated_by: List[str] = None, @@ -68,10 +70,10 @@ def __init__(self, value: float, unit: str, measures=measures) self.value = FloatProperty(self, OM_HAS_NUMERICAL_VALUE, 1, 1, initial_value=value) - self.types = URIProperty(self, SBOL_TYPE, 0, math.inf, - initial_value=types) - self.unit = URIProperty(self, OM_HAS_UNIT, 1, 1, - initial_value=unit) + self.types: uri_list = URIProperty(self, SBOL_TYPE, 0, math.inf, + initial_value=types) + self.unit: uri_singleton = URIProperty(self, OM_HAS_UNIT, 1, 1, + initial_value=unit) def accept(self, visitor: Any) -> Any: """Invokes `visit_measure` on `visitor` with `self` as the only diff --git a/sbol3/participation.py b/sbol3/participation.py index 80b7a1f..a21e728 100644 --- a/sbol3/participation.py +++ b/sbol3/participation.py @@ -1,3 +1,4 @@ +from __future__ import annotations import math from typing import Any @@ -11,7 +12,7 @@ class Participation(Identified): """ - def __init__(self, roles: List[str], + def __init__(self, roles: Union[str, list[str]], participant: Union[SBOLObject, str], *, name: str = None, description: str = None, derived_from: List[str] = None, diff --git a/sbol3/provenance.py b/sbol3/provenance.py index 4d41e65..95c1e16 100644 --- a/sbol3/provenance.py +++ b/sbol3/provenance.py @@ -1,8 +1,10 @@ +from __future__ import annotations import datetime import math -from typing import Union, List, Any +from typing import Union, List, Any, Optional from . import * +from .typing import uri_list, uri_singleton class Usage(CustomIdentified): @@ -18,7 +20,7 @@ class Usage(CustomIdentified): """ def __init__(self, entity: str, - *, roles: str = None, + *, roles: Optional[str, list[str]] = None, name: str = None, description: str = None, derived_from: List[str] = None, generated_by: List[str] = None, @@ -29,10 +31,10 @@ def __init__(self, entity: str, name=name, description=description, derived_from=derived_from, generated_by=generated_by, measures=measures) - self.entity = URIProperty(self, PROV_ENTITY, 1, 1, - initial_value=entity) - self.roles = URIProperty(self, PROV_ROLES, 0, math.inf, - initial_value=roles) + self.entity: uri_singleton = URIProperty(self, PROV_ENTITY, 1, 1, + initial_value=entity) + self.roles: uri_list = URIProperty(self, PROV_ROLES, 0, math.inf, + initial_value=roles) def accept(self, visitor: Any) -> Any: """Invokes `visit_usage` on `visitor` with `self` as the only @@ -142,7 +144,8 @@ class Association(CustomIdentified): """ def __init__(self, agent: Union[str, Identified], - *, roles: str = None, plan: Union[Identified, str] = None, + *, roles: Optional[str, list[str]] = None, + plan: Union[Identified, str] = None, name: str = None, description: str = None, derived_from: List[str] = None, generated_by: List[str] = None, @@ -153,8 +156,8 @@ def __init__(self, agent: Union[str, Identified], name=name, description=description, derived_from=derived_from, generated_by=generated_by, measures=measures) - self.roles = URIProperty(self, PROV_ROLES, 0, math.inf, - initial_value=roles) + self.roles: uri_list = URIProperty(self, PROV_ROLES, 0, math.inf, + initial_value=roles) self.plan = ReferencedObject(self, PROV_PLANS, 0, 1, initial_value=plan) self.agent = ReferencedObject(self, PROV_AGENTS, 1, 1, @@ -206,7 +209,7 @@ class Activity(CustomTopLevel): """ def __init__(self, identity: str, - *, types: List[str] = None, + *, types: Optional[str, list[str]] = None, start_time: Union[str, datetime.datetime] = None, end_time: Union[str, datetime.datetime] = None, usage: List[Identified] = None, @@ -223,8 +226,8 @@ def __init__(self, identity: str, attachments=attachments, name=name, description=description, derived_from=derived_from, generated_by=generated_by, measures=measures) - self.types = URIProperty(self, SBOL_TYPE, 0, math.inf, - initial_value=types) + self.types: uri_list = URIProperty(self, SBOL_TYPE, 0, math.inf, + initial_value=types) self.start_time = DateTimeProperty(self, PROV_STARTED_AT_TIME, 0, 1, initial_value=start_time) self.end_time = DateTimeProperty(self, PROV_ENDED_AT_TIME, 0, 1, diff --git a/sbol3/sequence.py b/sbol3/sequence.py index 03b8964..11a4e05 100644 --- a/sbol3/sequence.py +++ b/sbol3/sequence.py @@ -1,12 +1,14 @@ -from typing import List, Any +from typing import List, Any, Optional from . import * +from .typing import uri_singleton class Sequence(TopLevel): def __init__(self, identity: str, *, - elements: str = None, encoding: str = None, + elements: str = None, + encoding: Optional[str] = None, namespace: str = None, attachments: List[str] = None, name: str = None, description: str = None, @@ -20,8 +22,8 @@ def __init__(self, identity: str, *, generated_by=generated_by, measures=measures) self.elements = TextProperty(self, SBOL_ELEMENTS, 0, 1, initial_value=elements) - self.encoding = URIProperty(self, SBOL_ENCODING, 0, 1, - initial_value=encoding) + self.encoding: uri_singleton = URIProperty(self, SBOL_ENCODING, 0, 1, + initial_value=encoding) def validate(self, report: ValidationReport = None) -> ValidationReport: report = super().validate(report) diff --git a/sbol3/subcomponent.py b/sbol3/subcomponent.py index 45ac643..da03b5c 100644 --- a/sbol3/subcomponent.py +++ b/sbol3/subcomponent.py @@ -1,9 +1,10 @@ import math -from typing import Union, List, Any +from typing import Union, List, Any, Optional from . import * # Feature is not exported from .feature import Feature +from .typing import uri_singleton class SubComponent(Feature): @@ -17,7 +18,7 @@ class SubComponent(Feature): """ def __init__(self, instance_of: Union[Identified, str], - *, role_integration: str = None, + *, role_integration: Optional[str] = None, locations: List[Location] = None, source_locations: List[Location] = None, roles: List[str] = None, orientation: str = None, @@ -31,8 +32,8 @@ def __init__(self, instance_of: Union[Identified, str], roles=roles, orientation=orientation, name=name, description=description, derived_from=derived_from, generated_by=generated_by, measures=measures) - self.role_integration = URIProperty(self, SBOL_ROLE, 0, 1, - initial_value=role_integration) + self.role_integration: uri_singleton = URIProperty(self, SBOL_ROLE, 0, 1, + initial_value=role_integration) self.instance_of = ReferencedObject(self, SBOL_INSTANCE_OF, 1, 1, initial_value=instance_of) self.source_locations = OwnedObject(self, SBOL_SOURCE_LOCATION, 0, math.inf, diff --git a/sbol3/toplevel.py b/sbol3/toplevel.py index 848a674..a6182ef 100644 --- a/sbol3/toplevel.py +++ b/sbol3/toplevel.py @@ -2,10 +2,11 @@ import math import posixpath import uuid -from typing import List, Dict, Callable, Union +from typing import List, Dict, Callable, Union, Optional from urllib.parse import urlparse from . import * +from .typing import uri_singleton class TopLevel(Identified): @@ -19,7 +20,7 @@ class that can be found at the top level of an SBOL document or """ def __init__(self, identity: str, type_uri: str, - *, namespace: str = None, + *, namespace: Optional[str] = None, attachments: List[str] = None, name: str = None, description: str = None, derived_from: List[str] = None, @@ -40,8 +41,8 @@ def __init__(self, identity: str, type_uri: str, msg = 'Namespace must be a prefix of identity.' msg += f' Namespace {namespace} is not a prefix of {self.identity}.' raise ValueError(msg) - self.namespace = URIProperty(self, SBOL_NAMESPACE, 1, 1, - initial_value=namespace) + self.namespace: uri_singleton = URIProperty(self, SBOL_NAMESPACE, 1, 1, + initial_value=namespace) self.attachments = ReferencedObject(self, SBOL_HAS_ATTACHMENT, 0, math.inf, initial_value=attachments) diff --git a/sbol3/uri_property.py b/sbol3/uri_property.py index 752b989..5330fec 100644 --- a/sbol3/uri_property.py +++ b/sbol3/uri_property.py @@ -1,3 +1,4 @@ +from __future__ import annotations from typing import Union, Any, List, Optional import rdflib @@ -35,10 +36,13 @@ class URIListProperty(URIPropertyMixin, ListProperty): def __init__(self, property_owner: Any, property_uri: str, lower_bound: int, upper_bound: int, validation_rules: Optional[List] = None, - initial_value: Optional[str] = None): + initial_value: Optional[Union[str, list[str]]] = None): super().__init__(property_owner, property_uri, lower_bound, upper_bound, validation_rules) if initial_value is not None: + if isinstance(initial_value, str): + # Wrap the singleton in a list + initial_value = [initial_value] self.set(initial_value) diff --git a/sbol3/varcomp.py b/sbol3/varcomp.py index 65ff7d0..3323ec2 100644 --- a/sbol3/varcomp.py +++ b/sbol3/varcomp.py @@ -2,6 +2,7 @@ from typing import List, Union, Any from . import * +from .typing import uri_singleton class VariableFeature(Identified): @@ -36,8 +37,8 @@ def __init__(self, cardinality: str, variable: Union[Identified, str], if variable is None: variable = PYSBOL3_MISSING # Create properties - self.cardinality = URIProperty(self, SBOL_CARDINALITY, 1, 1, - initial_value=cardinality) + self.cardinality: uri_singleton = URIProperty(self, SBOL_CARDINALITY, 1, 1, + initial_value=cardinality) self.variable = ReferencedObject(self, SBOL_VARIABLE, 1, 1, initial_value=variable) self.variants = ReferencedObject(self, SBOL_VARIANT, 0, math.inf, diff --git a/test/test_identified.py b/test/test_identified.py index e9eedde..b487752 100644 --- a/test/test_identified.py +++ b/test/test_identified.py @@ -70,6 +70,14 @@ def test_basic_serialization(self): # a triple for the namespace, and a triple for the component type self.assertEqual(4, len(triples)) + def test_singleton_wrapping_urls(self): + # See https://github.com/SynBioDex/pySBOL3/issues/301 + sbol3.set_namespace('https://github.com/synbiodex/pysbol3') + process1 = 'https://example.com/thing' + thing = sbol3.Sequence('thing1', derived_from=process1) + self.assertEqual(1, len(thing.derived_from)) + self.assertEqual(process1, thing.derived_from[0]) + if __name__ == '__main__': unittest.main() From dafd28e27ff69d0d48d3c9fcb16fe5805459894b Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Tue, 2 Nov 2021 15:32:33 -0400 Subject: [PATCH 02/10] TextProperty auto-converts to lists --- sbol3/identified.py | 6 ++-- sbol3/text_property.py | 8 +++++- test/test_text_property.py | 58 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 test/test_text_property.py diff --git a/sbol3/identified.py b/sbol3/identified.py index c1c2fdc..9be738e 100644 --- a/sbol3/identified.py +++ b/sbol3/identified.py @@ -2,7 +2,7 @@ import abc import math import posixpath -from typing import Union, List, Callable, Any, Optional +from typing import Callable, Any, Optional from urllib.parse import urlparse import rdflib @@ -24,8 +24,8 @@ class Identified(SBOLObject): def __init__(self, identity: str, type_uri: str, *, name: str = None, description: str = None, derived_from: Optional[Union[str, list[str]]] = None, - generated_by: List[str] = None, - measures: List[SBOLObject] = None) -> None: + generated_by: list[str] = None, + measures: list[SBOLObject] = None) -> None: """ :param identity: this object's Uniform Resource Identifier (URI). this URI MUST be globally unique among all other Identified diff --git a/sbol3/text_property.py b/sbol3/text_property.py index 6011456..a8b5aef 100644 --- a/sbol3/text_property.py +++ b/sbol3/text_property.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Union, Any, List, Optional import rdflib @@ -42,13 +44,17 @@ def __init__(self, property_owner: Any, property_uri: str, super().__init__(property_owner, property_uri, lower_bound, upper_bound, validation_rules) if initial_value is not None: + if isinstance(initial_value, str): + # Wrap the singleton in a list + initial_value = [initial_value] self.set(initial_value) def TextProperty(property_owner: Any, property_uri: str, lower_bound: int, upper_bound: Union[int, float], validation_rules: Optional[List] = None, - initial_value: Optional[Union[str, List[str]]] = None) -> Property: + initial_value: Optional[Union[str, List[str]]] = None + ) -> Union[str, list[str], Property]: if upper_bound == 1: return TextSingletonProperty(property_owner, property_uri, lower_bound, upper_bound, diff --git a/test/test_text_property.py b/test/test_text_property.py new file mode 100644 index 0000000..55f8c4e --- /dev/null +++ b/test/test_text_property.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import math +import unittest +from typing import Any, Optional + +import sbol3 + + +# Define an extension class that includes a text property whose max +# cardinality is greater than 1 to test auto-conversion of strings to +# lists. +class TextPropertyExtension(sbol3.CustomTopLevel): + TYPE_URI = 'https://github.com/synbiodex/pysbol3/TextPropertyExtension' + TPE_INFO_URI = 'https://github.com/synbiodex/pysbol3/information' + + def __init__(self, identity, + *, information: Optional[str, list[str]] = None, + namespace: str = None, + attachments: list[str] = None, + name: str = None, description: str = None, + derived_from: list[str] = None, + generated_by: list[str] = None, + measures: list[sbol3.SBOLObject] = None) -> None: + super().__init__(identity=identity, + type_uri=TextPropertyExtension.TYPE_URI, + namespace=namespace, + attachments=attachments, name=name, + description=description, derived_from=derived_from, + generated_by=generated_by, measures=measures) + self.information = sbol3.TextProperty(self, + TextPropertyExtension.TPE_INFO_URI, + 0, math.inf, + initial_value=information) + + def accept(self, visitor: Any) -> Any: + pass + + +class TestTextProperty(unittest.TestCase): + + def test_text_list_property(self): + sbol3.set_namespace('https://github.com/synbiodex/pysbol3') + # Test initializing with None + tpe1 = TextPropertyExtension('tpe1') + self.assertEqual([], tpe1.information) + # Test initializing with a list + info_value = ['foo', 'bar'] + tpe2 = TextPropertyExtension('tpe2', information=info_value) + self.assertListEqual(info_value, list(tpe2.information)) + # Test initializing with a string, which should be marshalled into a list + info_value = 'foo' + tpe3 = TextPropertyExtension('tpe3', information=info_value) + self.assertListEqual([info_value], list(tpe3.information)) + + +if __name__ == '__main__': + unittest.main() From 3dac935503d4b580231461d1c7e98082b3cffdf8 Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Tue, 2 Nov 2021 15:40:39 -0400 Subject: [PATCH 03/10] Clean up typing in URIProperty --- sbol3/identified.py | 8 ++++---- sbol3/uri_property.py | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/sbol3/identified.py b/sbol3/identified.py index 9be738e..88ce9f5 100644 --- a/sbol3/identified.py +++ b/sbol3/identified.py @@ -58,8 +58,8 @@ def __init__(self, identity: str, type_uri: str, self.name = TextProperty(self, SBOL_NAME, 0, 1, initial_value=name) self.description = TextProperty(self, SBOL_DESCRIPTION, 0, 1, initial_value=description) - self.derived_from: uri_list = URIProperty(self, PROV_DERIVED_FROM, 0, math.inf, - initial_value=derived_from) + self.derived_from = URIProperty(self, PROV_DERIVED_FROM, 0, math.inf, + initial_value=derived_from) self.generated_by = ReferencedObject(self, PROV_GENERATED_BY, 0, math.inf, initial_value=generated_by) # The type_constraint for measures should really be Measure but @@ -71,8 +71,8 @@ def __init__(self, identity: str, type_uri: str, type_constraint=Identified) # Identity has been set by the SBOLObject constructor self._display_id = self._extract_display_id(self.identity) - self._rdf_types: uri_list = URIProperty(self, RDF_TYPE, 1, math.inf, - initial_value=[type_uri]) + self._rdf_types = URIProperty(self, RDF_TYPE, 1, math.inf, + initial_value=[type_uri]) @staticmethod def _is_valid_display_id(display_id: str) -> bool: diff --git a/sbol3/uri_property.py b/sbol3/uri_property.py index 5330fec..8eadbc7 100644 --- a/sbol3/uri_property.py +++ b/sbol3/uri_property.py @@ -24,7 +24,8 @@ class URISingletonProperty(URIPropertyMixin, SingletonProperty): def __init__(self, property_owner: Any, property_uri: str, lower_bound: int, upper_bound: int, validation_rules: Optional[List] = None, - initial_value: Optional[str] = None): + initial_value: Optional[str] = None + ) -> None: super().__init__(property_owner, property_uri, lower_bound, upper_bound, validation_rules) if initial_value is not None: @@ -36,7 +37,8 @@ class URIListProperty(URIPropertyMixin, ListProperty): def __init__(self, property_owner: Any, property_uri: str, lower_bound: int, upper_bound: int, validation_rules: Optional[List] = None, - initial_value: Optional[Union[str, list[str]]] = None): + initial_value: Optional[Union[str, list[str]]] = None + ) -> None: super().__init__(property_owner, property_uri, lower_bound, upper_bound, validation_rules) if initial_value is not None: @@ -50,7 +52,8 @@ def URIProperty(property_owner: Any, property_uri: str, lower_bound: int, upper_bound: Union[int, float], *, # require keywords from here validation_rules: Optional[List] = None, - initial_value: Optional[Union[str, List[str]]] = None) -> Property: + initial_value: Optional[Union[str, List[str]]] = None + ) -> Union[str, list[str], Property]: if upper_bound == 1: return URISingletonProperty(property_owner, property_uri, lower_bound, upper_bound, From 761a4ca313bceeed9586e391e1c0c0274586bbe8 Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Tue, 2 Nov 2021 16:42:08 -0400 Subject: [PATCH 04/10] ReferencedObjectProperty auto-converts to lists --- sbol3/identified.py | 4 ++-- sbol3/refobj_property.py | 14 +++++++++++--- sbol3/sequence.py | 7 +++++-- sbol3/toplevel.py | 5 +++-- test/test_sequence.py | 24 ++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/sbol3/identified.py b/sbol3/identified.py index 88ce9f5..b22f564 100644 --- a/sbol3/identified.py +++ b/sbol3/identified.py @@ -24,8 +24,8 @@ class Identified(SBOLObject): def __init__(self, identity: str, type_uri: str, *, name: str = None, description: str = None, derived_from: Optional[Union[str, list[str]]] = None, - generated_by: list[str] = None, - measures: list[SBOLObject] = None) -> None: + generated_by: list[Union[Identified, str]] = None, + measures: list[Identified] = None) -> None: """ :param identity: this object's Uniform Resource Identifier (URI). this URI MUST be globally unique among all other Identified diff --git a/sbol3/refobj_property.py b/sbol3/refobj_property.py index af5413a..9ba37eb 100644 --- a/sbol3/refobj_property.py +++ b/sbol3/refobj_property.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Union, Any, List, Optional import rdflib @@ -52,7 +54,7 @@ class ReferencedObjectSingleton(ReferencedObjectMixin, SingletonProperty): def __init__(self, property_owner: Any, property_uri: str, lower_bound: int, upper_bound: int, validation_rules: Optional[List] = None, - initial_value: Optional[str] = None): + initial_value: Optional[Union[Identified, str]] = None) -> None: super().__init__(property_owner, property_uri, lower_bound, upper_bound, validation_rules) if initial_value is not None: @@ -69,10 +71,14 @@ class ReferencedObjectList(ReferencedObjectMixin, ListProperty): def __init__(self, property_owner: Any, property_uri: str, lower_bound: int, upper_bound: int, validation_rules: Optional[List] = None, - initial_value: Optional[str] = None): + initial_value: Optional[list[Union[Identified, str]]] = None) -> None: super().__init__(property_owner, property_uri, lower_bound, upper_bound, validation_rules) if initial_value is not None: + # Cannot use 'Identified' here because it isn't defined yet + if isinstance(initial_value, (str, SBOLObject)): + # Wrap the singleton in a list + initial_value = [initial_value] self.set(initial_value) # See bug 184 - don't add to document @@ -83,7 +89,9 @@ def __init__(self, property_owner: Any, property_uri: str, def ReferencedObject(property_owner: Any, property_uri: str, lower_bound: int, upper_bound: Union[int, float], validation_rules: Optional[List] = None, - initial_value: Optional[Union[str, List[str]]] = None) -> Property: + initial_value: Optional[Union[Union[Identified, str], + list[Union[Identified, str]]]] = None + ) -> Union[ReferencedURI, list[ReferencedURI], Property]: if upper_bound == 1: return ReferencedObjectSingleton(property_owner, property_uri, lower_bound, upper_bound, diff --git a/sbol3/sequence.py b/sbol3/sequence.py index 11a4e05..45411ae 100644 --- a/sbol3/sequence.py +++ b/sbol3/sequence.py @@ -1,4 +1,6 @@ -from typing import List, Any, Optional +from __future__ import annotations + +from typing import List, Any, Optional, Union from . import * from .typing import uri_singleton @@ -12,7 +14,8 @@ def __init__(self, identity: str, *, namespace: str = None, attachments: List[str] = None, name: str = None, description: str = None, - derived_from: List[str] = None, generated_by: List[str] = None, + derived_from: List[str] = None, + generated_by: list[Union[Identified, str]] = None, measures: List[SBOLObject] = None, type_uri: str = SBOL_SEQUENCE) -> None: super().__init__(identity=identity, type_uri=type_uri, diff --git a/sbol3/toplevel.py b/sbol3/toplevel.py index a6182ef..6b67f2a 100644 --- a/sbol3/toplevel.py +++ b/sbol3/toplevel.py @@ -1,9 +1,10 @@ +from __future__ import annotations + import copy import math import posixpath import uuid from typing import List, Dict, Callable, Union, Optional -from urllib.parse import urlparse from . import * from .typing import uri_singleton @@ -24,7 +25,7 @@ def __init__(self, identity: str, type_uri: str, attachments: List[str] = None, name: str = None, description: str = None, derived_from: List[str] = None, - generated_by: List[str] = None, + generated_by: list[Union[Identified, str]] = None, measures: List[SBOLObject] = None) -> None: # Check identity, which is required for a TopLevel # More checking on identity happens in Identified, but Identified diff --git a/test/test_sequence.py b/test/test_sequence.py index 530f41d..95aad98 100644 --- a/test/test_sequence.py +++ b/test/test_sequence.py @@ -97,6 +97,30 @@ def test_initial_value(self): self.assertEqual(elements, s1.elements) # self.assertEqual(encoding, s1.encoding) + def test_generated_by(self): + # See https://github.com/SynBioDex/pySBOL3/issues/301 + sbol3.set_namespace('https://github.com/synbiodex/pysbol3') + act1 = sbol3.Activity('act1') + act2 = sbol3.Activity('act2') + elements = 'acgt' + seq1 = sbol3.Sequence(identity='seq1', + elements=elements) + self.assertListEqual([], list(seq1.generated_by)) + # test a list of items + activities = [act1, act2] + seq2 = sbol3.Sequence(identity='seq2', + elements=elements, + generated_by=activities) + self.assertListEqual([a.identity for a in activities], + list(seq2.generated_by)) + # test a singleton, which should gracefully be marshalled into a list + activity = act1 + seq3 = sbol3.Sequence(identity='seq3', + elements=elements, + generated_by=activity) + self.assertListEqual([activity.identity], + list(seq3.generated_by)) + if __name__ == '__main__': unittest.main() From bfa0dde0b4d52f6bd838da11acf7140e891fefe6 Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Tue, 2 Nov 2021 17:07:57 -0400 Subject: [PATCH 05/10] Clean up typing declarations --- sbol3/identified.py | 10 +++++----- sbol3/toplevel.py | 18 +++++++++--------- test/test_text_property.py | 5 +---- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/sbol3/identified.py b/sbol3/identified.py index b22f564..0b3c32e 100644 --- a/sbol3/identified.py +++ b/sbol3/identified.py @@ -22,10 +22,11 @@ class Identified(SBOLObject): """ def __init__(self, identity: str, type_uri: str, - *, name: str = None, description: str = None, - derived_from: Optional[Union[str, list[str]]] = None, - generated_by: list[Union[Identified, str]] = None, - measures: list[Identified] = None) -> None: + *, name: Optional[str] = None, + description: Optional[str] = None, + derived_from: Optional[list[str]] = None, + generated_by: Optional[list[Union[Identified, str]]] = None, + measures: Optional[list[Identified]] = None) -> None: """ :param identity: this object's Uniform Resource Identifier (URI). this URI MUST be globally unique among all other Identified @@ -238,7 +239,6 @@ def serialize(self, graph: rdflib.Graph): graph.add((identity, rdf_prop, rdflib.URIRef(item.identity))) item.serialize(graph) - @abc.abstractmethod def accept(self, visitor: Any) -> Any: """ An abstract method for concrete classes to override. This diff --git a/sbol3/toplevel.py b/sbol3/toplevel.py index 6b67f2a..01c0d35 100644 --- a/sbol3/toplevel.py +++ b/sbol3/toplevel.py @@ -4,10 +4,9 @@ import math import posixpath import uuid -from typing import List, Dict, Callable, Union, Optional +from typing import Dict, Callable, Union, Optional from . import * -from .typing import uri_singleton class TopLevel(Identified): @@ -22,11 +21,12 @@ class that can be found at the top level of an SBOL document or def __init__(self, identity: str, type_uri: str, *, namespace: Optional[str] = None, - attachments: List[str] = None, - name: str = None, description: str = None, - derived_from: List[str] = None, - generated_by: list[Union[Identified, str]] = None, - measures: List[SBOLObject] = None) -> None: + attachments: Optional[list[Union[Identified, str]]] = None, + name: Optional[str] = None, + description: Optional[str] = None, + derived_from: Optional[list[str]] = None, + generated_by: Optional[list[Union[Identified, str]]] = None, + measures: Optional[list[Identified]] = None) -> None: # Check identity, which is required for a TopLevel # More checking on identity happens in Identified, but Identified # does not require an identity, only TopLevel does. @@ -42,8 +42,8 @@ def __init__(self, identity: str, type_uri: str, msg = 'Namespace must be a prefix of identity.' msg += f' Namespace {namespace} is not a prefix of {self.identity}.' raise ValueError(msg) - self.namespace: uri_singleton = URIProperty(self, SBOL_NAMESPACE, 1, 1, - initial_value=namespace) + self.namespace = URIProperty(self, SBOL_NAMESPACE, 1, 1, + initial_value=namespace) self.attachments = ReferencedObject(self, SBOL_HAS_ATTACHMENT, 0, math.inf, initial_value=attachments) diff --git a/test/test_text_property.py b/test/test_text_property.py index 55f8c4e..0547ec5 100644 --- a/test/test_text_property.py +++ b/test/test_text_property.py @@ -2,7 +2,7 @@ import math import unittest -from typing import Any, Optional +from typing import Optional import sbol3 @@ -33,9 +33,6 @@ def __init__(self, identity, 0, math.inf, initial_value=information) - def accept(self, visitor: Any) -> Any: - pass - class TestTextProperty(unittest.TestCase): From c29150798ab25ac6f7df1f5794e6ba47a5734e3c Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Tue, 2 Nov 2021 17:11:28 -0400 Subject: [PATCH 06/10] Clean up typing declarations --- sbol3/sequence.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sbol3/sequence.py b/sbol3/sequence.py index 45411ae..808f3ec 100644 --- a/sbol3/sequence.py +++ b/sbol3/sequence.py @@ -8,15 +8,17 @@ class Sequence(TopLevel): - def __init__(self, identity: str, *, - elements: str = None, + def __init__(self, identity: str, + *, # Keywords only after this + elements: Optional[str] = None, encoding: Optional[str] = None, - namespace: str = None, - attachments: List[str] = None, - name: str = None, description: str = None, - derived_from: List[str] = None, - generated_by: list[Union[Identified, str]] = None, - measures: List[SBOLObject] = None, + namespace: Optional[str] = None, + attachments: Optional[list[Union[Identified, str]]] = None, + name: Optional[str] = None, + description: Optional[str] = None, + derived_from: Optional[list[str]] = None, + generated_by: Optional[list[Union[Identified, str]]] = None, + measures: Optional[list[Identified]] = None, type_uri: str = SBOL_SEQUENCE) -> None: super().__init__(identity=identity, type_uri=type_uri, namespace=namespace, From bb3537bdbb9cf6046c2d2fee5ec456c9a06afad8 Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Tue, 2 Nov 2021 17:18:32 -0400 Subject: [PATCH 07/10] Clean up typing declarations --- sbol3/component.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/sbol3/component.py b/sbol3/component.py index 618dc77..8f79cc8 100644 --- a/sbol3/component.py +++ b/sbol3/component.py @@ -1,36 +1,39 @@ from __future__ import annotations + import math from typing import List, Union, Any, Optional from . import * -from .typing import * - class Component(TopLevel): - def __init__(self, identity: str, types: Union[str, list[str]], - *, roles: Optional[Union[str, list[str]]] = None, - sequences: List[str] = None, + def __init__(self, identity: str, types: list[str], + *, # Keywords only after this + roles: Optional[list[str]] = None, + sequences: Optional[list[Union[Identified, str]]] = None, features: List[Feature] = None, constraints: List[Constraint] = None, interactions: List[Interaction] = None, interface: Interface = None, - models: List[str] = None, - namespace: str = None, - attachments: List[str] = None, - name: str = None, description: str = None, - derived_from: List[str] = None, generated_by: List[str] = None, - measures: List[SBOLObject] = None, type_uri: str = SBOL_COMPONENT): + models: Optional[list[Union[Identified, str]]] = None, + namespace: Optional[str] = None, + attachments: Optional[list[Union[Identified, str]]] = None, + name: Optional[str] = None, + description: Optional[str] = None, + derived_from: Optional[list[str]] = None, + generated_by: Optional[list[Union[Identified, str]]] = None, + measures: Optional[list[Identified]] = None, + type_uri: str = SBOL_COMPONENT) -> None: super().__init__(identity=identity, type_uri=type_uri, namespace=namespace, attachments=attachments, name=name, description=description, derived_from=derived_from, generated_by=generated_by, measures=measures) - self.types: uri_list = URIProperty(self, SBOL_TYPE, 1, math.inf, - initial_value=types) - self.roles: uri_list = URIProperty(self, SBOL_ROLE, 0, math.inf, - initial_value=roles) + self.types = URIProperty(self, SBOL_TYPE, 1, math.inf, + initial_value=types) + self.roles = URIProperty(self, SBOL_ROLE, 0, math.inf, + initial_value=roles) self.sequences = ReferencedObject(self, SBOL_SEQUENCES, 0, math.inf, initial_value=sequences) self.features = OwnedObject(self, SBOL_FEATURES, 0, math.inf, From 42b5b1212f607b04432a030fa8948182cab0f6ff Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Tue, 9 Nov 2021 15:56:59 -0500 Subject: [PATCH 08/10] Add type coercion to owned object lists Allow singletons as initial values of owned object lists. --- sbol3/component.py | 3 ++- sbol3/identified.py | 7 +++---- sbol3/ownedobject.py | 4 ++++ sbol3/typing.py | 14 ++++++++++++-- test/test_component.py | 14 ++++++++++++++ 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/sbol3/component.py b/sbol3/component.py index 8f79cc8..1a515a1 100644 --- a/sbol3/component.py +++ b/sbol3/component.py @@ -4,6 +4,7 @@ from typing import List, Union, Any, Optional from . import * +from .typing import * class Component(TopLevel): @@ -23,7 +24,7 @@ def __init__(self, identity: str, types: list[str], description: Optional[str] = None, derived_from: Optional[list[str]] = None, generated_by: Optional[list[Union[Identified, str]]] = None, - measures: Optional[list[Identified]] = None, + measures: Optional[ownedobj_list_arg] = None, type_uri: str = SBOL_COMPONENT) -> None: super().__init__(identity=identity, type_uri=type_uri, namespace=namespace, diff --git a/sbol3/identified.py b/sbol3/identified.py index 0b3c32e..398b4bb 100644 --- a/sbol3/identified.py +++ b/sbol3/identified.py @@ -1,5 +1,4 @@ from __future__ import annotations -import abc import math import posixpath from typing import Callable, Any, Optional @@ -24,9 +23,9 @@ class Identified(SBOLObject): def __init__(self, identity: str, type_uri: str, *, name: Optional[str] = None, description: Optional[str] = None, - derived_from: Optional[list[str]] = None, - generated_by: Optional[list[Union[Identified, str]]] = None, - measures: Optional[list[Identified]] = None) -> None: + derived_from: Optional[Union[str, list[str]]] = None, + generated_by: Optional[refobj_list_arg] = None, + measures: Optional[ownedobj_list_arg] = None) -> None: """ :param identity: this object's Uniform Resource Identifier (URI). this URI MUST be globally unique among all other Identified diff --git a/sbol3/ownedobject.py b/sbol3/ownedobject.py index ef1e80b..52a307a 100644 --- a/sbol3/ownedobject.py +++ b/sbol3/ownedobject.py @@ -98,6 +98,10 @@ def __init__(self, property_owner: Any, property_uri: str, # to accept unknown keyword arguments. self.type_constraint = type_constraint if initial_value is not None: + if isinstance(initial_value, SBOLObject): + # coerce the singleton into a list + # see https://github.com/SynBioDex/pySBOL3/issues/301 + initial_value = [initial_value] self.set(initial_value) def validate(self, name: str, report: ValidationReport): diff --git a/sbol3/typing.py b/sbol3/typing.py index f377737..e4cfbff 100644 --- a/sbol3/typing.py +++ b/sbol3/typing.py @@ -1,9 +1,19 @@ -from typing import Union, List +from __future__ import annotations + +from typing import List, Sequence, Union from .property_base import Property # URIProperty typing uri_list = Union[List[str], Property] uri_singleton = Union[str, Property] + # Owned object property -oo_list = Union[List['Identified'], Property] +ownedobj = 'Identified' +ownedobj_list = Sequence['Identified'] +ownedobj_list_arg = Union[ownedobj, ownedobj_list] + +# ReferencedObject +refobj = Union['Identified', str] +refobj_list = Sequence[refobj] +refobj_list_arg = Union[refobj, refobj_list] diff --git a/test/test_component.py b/test/test_component.py index 4c8b5ee..223fc5e 100644 --- a/test/test_component.py +++ b/test/test_component.py @@ -169,6 +169,20 @@ def test_cloning_references(self): self.assertNotEqual(o.identity, o_clone.identity) self.assertEqual(o.refers_to, o_clone.refers_to) + def test_measures_initial_value(self): + # See https://github.com/SynBioDex/pySBOL3/issues/301 + sbol3.set_namespace('https://github.com/synbiodex/pysbol3') + metre = 'http://www.ontology-of-units-of-measure.org/resource/om-2/metre' + one_metre = sbol3.Measure(1, unit=metre) + two_metres = sbol3.Measure(2, unit=metre) + # Test passing a list of measures + c1 = sbol3.Component('c1', types=[sbol3.SBO_DNA], measures=[one_metre, two_metres]) + self.assertListEqual([one_metre, two_metres], list(c1.measures)) + # test passing a singleton measure + three_metres = sbol3.Measure(3, unit=metre) + c2 = sbol3.Component('c2', types=[sbol3.SBO_DNA], measures=three_metres) + self.assertListEqual([three_metres], list(c2.measures)) + if __name__ == '__main__': unittest.main() From 7d7d66d1472fd608d3b21b9501ed29c558a41b33 Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Tue, 9 Nov 2021 16:18:10 -0500 Subject: [PATCH 09/10] Typing for TopLevel and Sequence arguments --- sbol3/identified.py | 3 ++- sbol3/sequence.py | 13 +++++++------ sbol3/toplevel.py | 12 +++++++----- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/sbol3/identified.py b/sbol3/identified.py index 398b4bb..2ed2c11 100644 --- a/sbol3/identified.py +++ b/sbol3/identified.py @@ -2,6 +2,7 @@ import math import posixpath from typing import Callable, Any, Optional +import typing from urllib.parse import urlparse import rdflib @@ -23,7 +24,7 @@ class Identified(SBOLObject): def __init__(self, identity: str, type_uri: str, *, name: Optional[str] = None, description: Optional[str] = None, - derived_from: Optional[Union[str, list[str]]] = None, + derived_from: Optional[Union[str, typing.Sequence[str]]] = None, generated_by: Optional[refobj_list_arg] = None, measures: Optional[ownedobj_list_arg] = None) -> None: """ diff --git a/sbol3/sequence.py b/sbol3/sequence.py index 808f3ec..d05b8e4 100644 --- a/sbol3/sequence.py +++ b/sbol3/sequence.py @@ -1,9 +1,10 @@ from __future__ import annotations -from typing import List, Any, Optional, Union +from typing import Any, Optional +import typing from . import * -from .typing import uri_singleton +from .typing import * class Sequence(TopLevel): @@ -13,12 +14,12 @@ def __init__(self, identity: str, elements: Optional[str] = None, encoding: Optional[str] = None, namespace: Optional[str] = None, - attachments: Optional[list[Union[Identified, str]]] = None, + attachments: Optional[refobj_list_arg] = None, name: Optional[str] = None, description: Optional[str] = None, - derived_from: Optional[list[str]] = None, - generated_by: Optional[list[Union[Identified, str]]] = None, - measures: Optional[list[Identified]] = None, + derived_from: Optional[Union[str, typing.Sequence[str]]] = None, + generated_by: Optional[refobj_list_arg] = None, + measures: Optional[ownedobj_list_arg] = None, type_uri: str = SBOL_SEQUENCE) -> None: super().__init__(identity=identity, type_uri=type_uri, namespace=namespace, diff --git a/sbol3/toplevel.py b/sbol3/toplevel.py index 01c0d35..0899196 100644 --- a/sbol3/toplevel.py +++ b/sbol3/toplevel.py @@ -4,9 +4,11 @@ import math import posixpath import uuid -from typing import Dict, Callable, Union, Optional +from typing import Dict, Callable, Optional +import typing from . import * +from .typing import * class TopLevel(Identified): @@ -21,12 +23,12 @@ class that can be found at the top level of an SBOL document or def __init__(self, identity: str, type_uri: str, *, namespace: Optional[str] = None, - attachments: Optional[list[Union[Identified, str]]] = None, + attachments: Optional[refobj_list_arg] = None, name: Optional[str] = None, description: Optional[str] = None, - derived_from: Optional[list[str]] = None, - generated_by: Optional[list[Union[Identified, str]]] = None, - measures: Optional[list[Identified]] = None) -> None: + derived_from: Optional[Union[str, typing.Sequence[str]]] = None, + generated_by: Optional[refobj_list_arg] = None, + measures: Optional[ownedobj_list_arg] = None) -> None: # Check identity, which is required for a TopLevel # More checking on identity happens in Identified, but Identified # does not require an identity, only TopLevel does. From 9c4ae3413fab5a16ec341a1fcfc0fc96ab6c95a8 Mon Sep 17 00:00:00 2001 From: Tom Mitchell Date: Wed, 10 Nov 2021 14:10:09 -0500 Subject: [PATCH 10/10] Typing for Component arguments --- sbol3/component.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/sbol3/component.py b/sbol3/component.py index 1a515a1..7b33560 100644 --- a/sbol3/component.py +++ b/sbol3/component.py @@ -1,7 +1,8 @@ from __future__ import annotations import math -from typing import List, Union, Any, Optional +from typing import Any, Optional +import typing from . import * from .typing import * @@ -9,21 +10,21 @@ class Component(TopLevel): - def __init__(self, identity: str, types: list[str], + def __init__(self, identity: str, types: Optional[Union[str, typing.Sequence[str]]], *, # Keywords only after this - roles: Optional[list[str]] = None, - sequences: Optional[list[Union[Identified, str]]] = None, - features: List[Feature] = None, - constraints: List[Constraint] = None, - interactions: List[Interaction] = None, - interface: Interface = None, - models: Optional[list[Union[Identified, str]]] = None, + roles: Optional[Union[str, typing.Sequence[str]]] = None, + sequences: Optional[refobj_list_arg] = None, + features: Union[Feature, typing.Sequence[Feature]] = None, + constraints: Union[Constraint, typing.Sequence[Constraint]] = None, + interactions: Union[Interaction, typing.Sequence[Interaction]] = None, + interface: Union[Interface, typing.Sequence[Interface]] = None, + models: Optional[refobj_list_arg] = None, namespace: Optional[str] = None, - attachments: Optional[list[Union[Identified, str]]] = None, + attachments: Optional[refobj_list_arg] = None, name: Optional[str] = None, description: Optional[str] = None, - derived_from: Optional[list[str]] = None, - generated_by: Optional[list[Union[Identified, str]]] = None, + derived_from: Optional[Union[str, typing.Sequence[str]]] = None, + generated_by: Optional[refobj_list_arg] = None, measures: Optional[ownedobj_list_arg] = None, type_uri: str = SBOL_COMPONENT) -> None: super().__init__(identity=identity, type_uri=type_uri,