-
Notifications
You must be signed in to change notification settings - Fork 5
Support string-encoded types as per PEP-0563 #39
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| from dataclasses import Field, MISSING, dataclass, field | ||
| from typing import Optional, Callable, List, Union | ||
|
|
||
| from .naming import ReferenceByName | ||
| from .position import Position, Source | ||
| from .reflection import Multiplicity, PropertyDescription | ||
| from ..reflection import getannotations, get_type_arguments, is_sequence_type | ||
|
|
@@ -95,13 +96,57 @@ def provides_nodes(decl_type): | |
| return isinstance(decl_type, type) and issubclass(decl_type, Node) | ||
|
|
||
|
|
||
| def get_only_type_arg(decl_type): | ||
| """If decl_type has a single type argument, return it, otherwise return None""" | ||
| type_args = get_type_arguments(decl_type) | ||
| if len(type_args) == 1: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the type_args are more than one should we throw an exception perhaps? |
||
| return type_args[0] | ||
| else: | ||
| return None | ||
|
|
||
|
|
||
| def process_annotated_property(name, decl_type, known_property_names): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also here I would consider adding type annotations |
||
| multiplicity = Multiplicity.SINGULAR | ||
| is_reference = False | ||
| if get_type_origin(decl_type) is ReferenceByName: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In Python I often get confused by
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Here, we want to check if the type is exactly |
||
| decl_type = get_only_type_arg(decl_type) or decl_type | ||
| is_reference = True | ||
| if is_sequence_type(decl_type): | ||
| decl_type = get_only_type_arg(decl_type) or decl_type | ||
| multiplicity = Multiplicity.MANY | ||
| if get_type_origin(decl_type) is Union: | ||
| type_args = get_type_arguments(decl_type) | ||
| if len(type_args) == 1: | ||
| decl_type = type_args[0] | ||
| elif len(type_args) == 2: | ||
| if type_args[0] is type(None): | ||
| decl_type = type_args[1] | ||
| elif type_args[1] is type(None): | ||
| decl_type = type_args[0] | ||
| else: | ||
| raise Exception(f"Unsupported feature {name} of type {decl_type}") | ||
| if multiplicity == Multiplicity.SINGULAR: | ||
| multiplicity = Multiplicity.OPTIONAL | ||
| else: | ||
| raise Exception(f"Unsupported feature {name} of type {decl_type}") | ||
| if not isinstance(decl_type, type): | ||
| raise Exception(f"Unsupported feature {name} of type {decl_type}") | ||
| is_containment = provides_nodes(decl_type) and not is_reference | ||
| known_property_names.add(name) | ||
| return PropertyDescription(name, decl_type, is_containment, is_reference, multiplicity) | ||
|
|
||
|
|
||
| class Concept(ABCMeta): | ||
|
|
||
| def __init__(cls, what, bases=None, dict=None): | ||
| super().__init__(what, bases, dict) | ||
| cls.__internal_properties__ = \ | ||
| (["origin", "destination", "parent", "position", "position_override"] | ||
| + [n for n, v in inspect.getmembers(cls, is_internal_property_or_method)]) | ||
| cls.__internal_properties__ = [] | ||
| for base in bases: | ||
| if hasattr(base, "__internal_properties__"): | ||
| cls.__internal_properties__.extend(base.__internal_properties__) | ||
| if not cls.__internal_properties__: | ||
| cls.__internal_properties__ = ["origin", "destination", "parent", "position", "position_override"] | ||
| cls.__internal_properties__.extend([n for n, v in inspect.getmembers(cls, is_internal_property_or_method)]) | ||
|
|
||
| @property | ||
| def node_properties(cls): | ||
|
|
@@ -115,23 +160,11 @@ def _direct_node_properties(cls, cl, known_property_names): | |
| return | ||
| for name in anns: | ||
| if name not in known_property_names and cls.is_node_property(name): | ||
| is_child_property = False | ||
| multiplicity = Multiplicity.SINGULAR | ||
| if name in anns: | ||
| decl_type = anns[name] | ||
| if is_sequence_type(decl_type): | ||
| multiplicity = Multiplicity.MANY | ||
| type_args = get_type_arguments(decl_type) | ||
| if len(type_args) == 1: | ||
| is_child_property = provides_nodes(type_args[0]) | ||
| else: | ||
| is_child_property = provides_nodes(decl_type) | ||
| known_property_names.add(name) | ||
| yield PropertyDescription(name, is_child_property, multiplicity) | ||
| yield process_annotated_property(name, anns[name], known_property_names) | ||
| for name in dir(cl): | ||
| if name not in known_property_names and cls.is_node_property(name): | ||
| known_property_names.add(name) | ||
| yield PropertyDescription(name, False) | ||
| yield PropertyDescription(name, None, False, False) | ||
|
|
||
| def is_node_property(cls, name): | ||
| return not name.startswith('_') and name not in cls.__internal_properties__ | ||
|
|
@@ -180,7 +213,9 @@ def source(self) -> Optional[Source]: | |
|
|
||
| @internal_property | ||
| def properties(self): | ||
| return (PropertyDescription(p.name, p.provides_nodes, p.multiplicity, getattr(self, p.name)) | ||
| return (PropertyDescription(p.name, p.type, | ||
| is_containment=p.is_containment, is_reference=p.is_reference, | ||
| multiplicity=p.multiplicity, value=getattr(self, p.name)) | ||
| for p in self.__class__.node_properties) | ||
|
|
||
| @internal_property | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,25 +4,30 @@ | |
|
|
||
|
|
||
| def getannotations(cls): | ||
| import inspect | ||
| try: # On Python 3.10+ | ||
| return inspect.getannotations(cls) | ||
| try: | ||
| # https://peps.python.org/pep-0563/ | ||
| return typing.get_type_hints(cls, globalns=None, localns=None) | ||
| except AttributeError: | ||
| if isinstance(cls, type): | ||
| return cls.__dict__.get('__annotations__', None) | ||
| else: | ||
| return getattr(cls, '__annotations__', None) | ||
| try: | ||
| # On Python 3.10+ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we mention this in the README?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mention what? The algorithm for getting the annotations? |
||
| import inspect | ||
| return inspect.getannotations(cls) | ||
| except AttributeError: | ||
| if isinstance(cls, type): | ||
| return cls.__dict__.get('__annotations__', None) | ||
| else: | ||
| return getattr(cls, '__annotations__', None) | ||
|
|
||
|
|
||
| def get_type_origin(tp): | ||
| origin = None | ||
| if hasattr(typing, "get_origin"): | ||
| return typing.get_origin(tp) | ||
| origin = typing.get_origin(tp) | ||
| elif hasattr(tp, "__origin__"): | ||
| return tp.__origin__ | ||
| origin = tp.__origin__ | ||
| elif tp is typing.Generic: | ||
| return typing.Generic | ||
| else: | ||
| return None | ||
| origin = typing.Generic | ||
| return origin or (tp if isinstance(tp, type) else None) | ||
|
|
||
|
|
||
| def is_enum_type(attr_type): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to use type annotations for both the parameter and the return type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't know how to properly type them