[PATCH 11 of 24 yams V2] [mypy] type buildobjs.py

Laurent Peuch cortex at worlddomination.be
Wed Mar 4 15:17:47 CET 2020


# HG changeset patch
# User Laurent Peuch <cortex at worlddomination.be>
# Date 1579713779 -3600
#      Wed Jan 22 18:22:59 2020 +0100
# Node ID d78a8746c5d51327527c3626398711ab844c6665
# Parent  b2bbde9cc6828a72b7c463932bef056ad703128a
# Available At https://hg.logilab.org/users/lpeuch/yams
#              hg pull https://hg.logilab.org/users/lpeuch/yams -r d78a8746c5d5
# EXP-Topic type_annotations
[mypy] type buildobjs.py

diff --git a/yams/__init__.py b/yams/__init__.py
--- a/yams/__init__.py
+++ b/yams/__init__.py
@@ -20,7 +20,7 @@
 import warnings
 from datetime import datetime, date
 
-from typing import Set, Type, Dict, Tuple, Union, Callable, Any, cast, Iterable, TypeVar
+from typing import Set, Type, Dict, Union, Callable, Any, cast, Iterable, TypeVar
 
 import pkg_resources
 
@@ -28,12 +28,10 @@ from logilab.common.date import strptime
 from logilab.common import nullobject
 
 from yams._exceptions import SchemaError, UnknownType, BadSchemaDefinition, ValidationError  # noqa
-from yams.types import _RdefRdefSchemaType
+from yams.types import _RdefRdefSchemaType, _PermissionsType
 
 __docformat__: str = "restructuredtext en"
 
-_PermissionsType = Dict[str, Tuple[str, ...]]
-
 __version__: str = pkg_resources.get_distribution('yams').version
 
 _: Type[str] = str
diff --git a/yams/buildobjs.py b/yams/buildobjs.py
--- a/yams/buildobjs.py
+++ b/yams/buildobjs.py
@@ -17,52 +17,62 @@
 # with yams. If not, see <http://www.gnu.org/licenses/>.
 """Classes used to build a schema."""
 
-__docformat__ = "restructuredtext en"
-
 from typing import Optional, List
 from warnings import warn
 from copy import copy
 
+from typing import Any, Generator, Tuple, Type, Union, Dict, Sequence, Iterable
+
 from logilab.common import attrdict
 
 from yams import (BASE_TYPES, MARKER, BadSchemaDefinition, KNOWN_METAATTRIBUTES,
                   DEFAULT_ETYPEPERMS, DEFAULT_RELPERMS, DEFAULT_ATTRPERMS,
                   DEFAULT_COMPUTED_ATTRPERMS)
 from yams.constraints import (SizeConstraint, UniqueConstraint,
-                              StaticVocabularyConstraint, FORMAT_CONSTRAINT)
+                              StaticVocabularyConstraint, FORMAT_CONSTRAINT, BaseConstraint)
 from yams.schema import RelationDefinitionSchema
 
+from yams.types import _RdefType, _SchemaType, _PermissionsType
+
+__docformat__: str = "restructuredtext en"
+
 # will be modified by the yams'reader when schema is
 # beeing read
-PACKAGE = '<builtin>'
+PACKAGE: str = '<builtin>'
 
 
-__all__ = ('EntityType', 'RelationType', 'RelationDefinition',
-           'SubjectRelation', 'ObjectRelation',
-           'RichString', ) + tuple(BASE_TYPES)
+__all__: Tuple[str, ...] = ('EntityType', 'RelationType', 'RelationDefinition',
+                            'SubjectRelation', 'ObjectRelation',
+                            'RichString', ) + tuple(BASE_TYPES)
 
 # EntityType properties
-ETYPE_PROPERTIES = ('description', '__permissions__', '__unique_together__')
+ETYPE_PROPERTIES: Tuple[str, ...] = ('description', '__permissions__', '__unique_together__')
+
 # RelationType properties. Don't put description inside, handled specifically
-RTYPE_PROPERTIES = ('symmetric', 'inlined', 'fulltext_container')
+RTYPE_PROPERTIES: Tuple[str, ...] = ('symmetric', 'inlined', 'fulltext_container')
+
 # RelationDefinition properties have to be computed dynamically since new ones
 # may be added at runtime
 
 
-def _RDEF_PROPERTIES():
+def _RDEF_PROPERTIES() -> Tuple[str, ...]:
     base = RelationDefinitionSchema.ALL_PROPERTIES()
+
     # infered is an internal property and should not be specified explicitly
     base.remove('infered')
+
     # replace permissions by __permissions__ as it's spelled that way in schema
     # definition files
     base.remove('permissions')
     base.add('__permissions__')
+
     return tuple(base)
+
 # regroup all rtype/rdef properties as they may be defined one on each other in
 # some cases
 
 
-def _REL_PROPERTIES():
+def _REL_PROPERTIES() -> Tuple[str, ...]:
     return RTYPE_PROPERTIES + _RDEF_PROPERTIES()
 
 
@@ -70,35 +80,48 @@ def _REL_PROPERTIES():
 RDEF_PROPERTIES = ()  # stuff added here is also added to underlying dict, nevermind
 
 
-CREATION_RANK = 0
+CREATION_RANK: int = 0
 
 
-def _add_constraint(kwargs, constraint):
+def _add_constraint(kwargs: Dict[str, Any], constraint) -> None:
     """Add constraint to param kwargs."""
     constraints = kwargs.setdefault('constraints', [])
+
     for i, existingconstraint in enumerate(constraints):
         if existingconstraint.__class__ is constraint.__class__:
             constraints[i] = constraint
+
             return
+
     constraints.append(constraint)
 
 
-def _add_relation(relations, rdef, name=None, insertidx=None):
+def _add_relation(relations: List,
+                  rdef: _RdefType,
+                  name: Optional[str] = None,
+                  insertidx: Optional[int] = None) -> None:
     """Add relation (param rdef) to list of relations (param relations)."""
     if name is not None:
         rdef.name = name
+
     if insertidx is None:
         insertidx = len(relations)
+
     relations.insert(insertidx, rdef)
+
     if getattr(rdef, 'metadata', {}):
-        for meta_name, value in rdef.metadata.items():
+        # mypy: "RelationDefinition" has no attribute "metadata"
+        # dynamic attribute tested in the if
+        for meta_name, value in rdef.metadata.items():  # type: ignore
             assert meta_name in KNOWN_METAATTRIBUTES
+
             insertidx += 1  # insert meta after main
             meta_rel_name = '_'.join(((name or rdef.name), meta_name))
+
             _add_relation(relations, value, meta_rel_name, insertidx)
 
 
-def _check_kwargs(kwargs, attributes):
+def _check_kwargs(kwargs: Dict, attributes: Sequence) -> None:
     """Check that all keys of kwargs are actual attributes."""
     for key in kwargs:
         if key not in attributes:
@@ -106,32 +129,43 @@ def _check_kwargs(kwargs, attributes):
                                       % (key, attributes))
 
 
-def _copy_attributes(fromobj, toobj, attributes):
+def _copy_attributes(fromobj, toobj, attributes: Iterable) -> None:
     for attr in attributes:
         value = getattr(fromobj, attr, MARKER)
+
         if value is MARKER:
             continue
+
         ovalue = getattr(toobj, attr, MARKER)
+
         if ovalue is not MARKER and value != ovalue:
             rname = getattr(toobj, 'name', None) or toobj.__name__
+
             raise BadSchemaDefinition(
                 'conflicting values %r/%r for property %s of relation %r'
                 % (ovalue, value, attr, rname))
+
         setattr(toobj, attr, value)
 
 
-def register_base_types(schema):
+def register_base_types(schema: _SchemaType) -> None:
     """add base (final) entity types to the given schema"""
     for etype in BASE_TYPES:
         edef = EntityType(name=etype)
+
         schema.add_entity_type(edef)
 
 
 # first class schema definition objects #######################################
 
 class autopackage(type):
-    def __new__(mcs, name, bases, classdict):
+    def __new__(mcs: 'Type[autopackage]',
+                name: str,
+                bases: Tuple,
+                classdict: Dict[str, Any]) -> Any:
+
         classdict['package'] = PACKAGE
+
         return super(autopackage, mcs).__new__(mcs, name, bases, classdict)
 
 
@@ -141,38 +175,41 @@ class Definition(object, metaclass=autop
     description = MARKER
     __permissions__ = MARKER
 
-    def __init__(self, name=None):
+    def __init__(self, name=None) -> None:
         self.name = (name or getattr(self, 'name', None)
                      or self.__class__.__name__)
+
         if self.__doc__:
             self.description = ' '.join(self.__doc__.split())
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return '<%s %r @%x>' % (self.__class__.__name__, self.name, id(self))
 
     @classmethod
-    def expand_type_definitions(cls, defined):
+    def expand_type_definitions(cls: Type['Definition'], defined) -> None:
         """Schema building step 1: register definition objects by adding them
         to the `defined` dictionnary.
         """
         raise NotImplementedError()
 
     @classmethod
-    def expand_relation_definitions(cls, defined, schema):
+    def expand_relation_definitions(cls: Type['Definition'], defined, schema) -> None:
         """Schema building step 2: register all relations definition,
         expanding wildcard if necessary.
         """
         raise NotImplementedError()
 
-    def get_permissions(self, final=False):
+    def get_permissions(self, final: bool = False) -> Any:
         if self.__permissions__ is MARKER:
             if final:
                 return DEFAULT_ATTRPERMS
+
             return DEFAULT_RELPERMS
+
         return self.__permissions__
 
     @classmethod
-    def set_permissions(cls, perms):
+    def set_permissions(cls: Type['Definition'], perms: _PermissionsType) -> None:
         cls.__permissions__ = perms
 
 
@@ -185,36 +222,46 @@ class ObjectRelation(object):
     cardinality = MARKER
     constraints = MARKER
 
-    def __init__(self, etype, **kwargs):
+    def __init__(self, etype, **kwargs) -> None:
         if self.__class__.__name__ == 'ObjectRelation':
             warn('[yams 0.29] ObjectRelation is deprecated, '
                  'use RelationDefinition subclass', DeprecationWarning,
                  stacklevel=2)
+
         global CREATION_RANK
         CREATION_RANK += 1
         self.creation_rank = CREATION_RANK
+
         self.package = PACKAGE
         self.name = '<undefined>'
         self.etype = etype
+
         if self.constraints:
             self.constraints = list(self.constraints)
+
         self.override = kwargs.pop('override', False)
+
         if kwargs.pop('meta', None):
             warn('[yams 0.37.0] meta is deprecated',
                  DeprecationWarning, stacklevel=3)
+
         try:
             _check_kwargs(kwargs, _REL_PROPERTIES())
         except BadSchemaDefinition as bad:
             # XXX (auc) bad field name + required attribute can lead there
             # instead of schema.py ~ 920
-            bsd_ex = BadSchemaDefinition(('%s in relation to entity %r (also is %r defined ? '
-                                          '(check two lines above in the backtrace))')
+            bsd_ex = BadSchemaDefinition('%s in relation to entity %r (also is %r defined ? '
+                                         '(check two lines above in the backtrace))'
                                          % (bad.args, etype, etype))
-            bsd_ex.tb_offset = 2
+            # mypy: "BadSchemaDefinition" has no attribute "tb_offset"
+            # hack to transport information
+            bsd_ex.tb_offset = 2  # type: ignore
+
             raise bsd_ex
+
         self.__dict__.update(kwargs)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return '%(name)s %(etype)s' % self.__dict__
 
 
@@ -225,7 +272,7 @@ class SubjectRelation(ObjectRelation):
     internationalizable = MARKER
     default = MARKER
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return '%(etype)s %(name)s' % self.__dict__
 
 
@@ -235,12 +282,15 @@ class AbstractTypedAttribute(SubjectRela
     subclasses must provide a <etype> attribute to be instantiable
     """
 
-    def __init__(self, metadata: Optional[dict] = None, required: bool = False,
-                 maxsize: Optional[int] = None, formula=MARKER,
+    def __init__(self, metadata: Optional[Dict] = None,
+                 required: bool = False,
+                 maxsize: Optional[int] = None,
+                 formula=MARKER,
                  vocabulary: Optional[List[str]] = None,
                  unique: Optional[bool] = None,
                  override: bool = False,
                  **kwargs):
+
         # Store metadata
         if metadata is None:
             metadata = {}
@@ -271,6 +321,7 @@ class AbstractTypedAttribute(SubjectRela
         # use the etype attribute provided by subclasses
         kwargs["override"] = override
         super(AbstractTypedAttribute, self).__init__(self.etype, **kwargs)
+
         # reassign creation rank
         #
         # Main attribute are marked as created before it's metadata.
@@ -279,27 +330,32 @@ class AbstractTypedAttribute(SubjectRela
             meta = sorted(metadata.values(), key=lambda x: x.creation_rank)
             if meta[0].creation_rank < self.creation_rank:
                 _previous = self
+
                 for _next in meta:
                     if _previous.creation_rank < _next.creation_rank:
                         break
+
                     _previous.creation_rank, _next.creation_rank = (_next.creation_rank,
                                                                     _previous.creation_rank)
                     _next = _previous
 
-    def set_vocabulary(self, vocabulary, kwargs=None):
+    def set_vocabulary(self, vocabulary, kwargs=None) -> None:
         if kwargs is None:
             kwargs = self.__dict__
+
         # constraints = kwargs.setdefault('constraints', [])
         _add_constraint(kwargs, StaticVocabularyConstraint(vocabulary))
+
         if self.__class__.__name__ == 'String':  # XXX
             maxsize = max(len(x) for x in vocabulary)
+
             _add_constraint(kwargs, SizeConstraint(max=maxsize))
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         return '<%(name)s(%(etype)s)>' % self.__dict__
 
 
-def make_type(etype):
+def make_type(etype: str) -> Type[AbstractTypedAttribute]:
     """create a python class for a Yams base type.
 
     Notice it is now possible to create a specific type with user-defined
@@ -378,7 +434,7 @@ class Interval(AbstractTypedAttribute):
 
 # provides a RichString factory for convenience
 def RichString(default_format: str = 'text/plain',
-               format_constraints=None,
+               format_constraints: Optional[List[BaseConstraint]] = None,
                required: bool = False,
                maxsize: Optional[int] = None,
                formula=MARKER,
@@ -409,7 +465,10 @@ def RichString(default_format: str = 'te
     else:
         format_args['constraints'] = format_constraints
 
-    meta = {'format': String(internationalizable=True, **format_args)}
+    # mypy: Argument 2 to "String" has incompatible type "**Dict[str, object]"; expected
+    # mypy: "Optional[bool]"
+    # really looks like mypy is failing on AbstractTypedAttribute constructor here
+    meta = {'format': String(internationalizable=True, **format_args)}  # type: ignore
 
     return String(
         metadata=meta,
@@ -431,42 +490,56 @@ class metadefinition(autopackage):
     """
     stacklevel = 3
 
-    def __new__(mcs, name, bases, classdict):
+    def __new__(mcs: 'Type[metadefinition]', name: str, bases: Tuple,
+                classdict: Dict[str, Any]) -> Any:
         # Move (any) relation from the class dict to __relations__ attribute
-        rels = classdict.setdefault('__relations__', [])
-        relations = dict((rdef.name, rdef) for rdef in rels)
+        rels: List = classdict.setdefault('__relations__', [])
+        relations: Dict[str, Any] = dict((rdef.name, rdef) for rdef in rels)
+
         for rname, rdef in list(classdict.items()):
             if isinstance(rdef, ObjectRelation):
                 # relation's name **must** be removed from class namespace
                 # to avoid conflicts with instance's potential attributes
                 del classdict[rname]
+
                 relations[rname] = rdef
+
         # handle logical inheritance
         if '__specializes_schema__' in classdict:
             specialized = bases[0]
             classdict['__specializes__'] = specialized.__name__
+
             if '__specialized_by__' not in specialized.__dict__:
                 specialized.__specialized_by__ = []
+
             specialized.__specialized_by__.append(name)
+
         # Initialize processed class
         defclass = super(metadefinition, mcs).__new__(mcs, name, bases, classdict)
+
         for rname, rdef in relations.items():
             _add_relation(defclass.__relations__, rdef, rname)
+
         # take base classes'relations into account
         for base in bases:
             for rdef in getattr(base, '__relations__', ()):
                 if rdef.name not in relations or not relations[rdef.name].override:
                     if isinstance(rdef, RelationDefinition):
                         rdef = copy(rdef)
+
                         if rdef.subject == base.__name__:
                             rdef.subject = name
+
                         if rdef.object == base.__name__:
                             rdef.object = name
+
                     rels.append(rdef)
                 else:
                     relations[rdef.name].creation_rank = rdef.creation_rank
+
         # sort relations by creation rank
         defclass.__relations__ = sorted(rels, key=lambda r: r.creation_rank)
+
         return defclass
 
 
@@ -497,143 +570,195 @@ class EntityType(Definition, metaclass=m
 
     __permissions__ = DEFAULT_ETYPEPERMS
 
-    def __init__(self, name=None, **kwargs):
+    def __init__(self, name: Optional[str] = None, **kwargs) -> None:
         super(EntityType, self).__init__(name)
+
         _check_kwargs(kwargs, ETYPE_PROPERTIES)
+
         self.__dict__.update(kwargs)
         self.specialized_type = self.__class__.__dict__.get('__specializes__')
 
-    def __str__(self):
+    def __str__(self) -> str:
         return 'entity type %r' % self.name
 
     @property
-    def specialized_by(self):
+    def specialized_by(self) -> List:
         return self.__class__.__dict__.get('__specialized_by__', [])
 
     @classmethod
-    def expand_type_definitions(cls, defined):
+    def expand_type_definitions(cls: Type['EntityType'], defined: Dict[str, Any]) -> None:
         """Schema building step 1: register definition objects by adding
         them to the `defined` dictionnary.
         """
         name = getattr(cls, 'name', cls.__name__)
+
         assert cls is not defined.get(name), 'duplicate registration: %s' % name
         assert name not in defined, \
             "type '%s' was already defined here %s, new definition here %s" % \
             (name, defined[name].__module__, cls)
-        cls._defined = defined  # XXX may be used later (eg .add_relation())
+
+        # mypy: "Type[EntityType]" has no attribute "_defined"
+        # dynamic attribute
+        cls._defined = defined  # type: ignore # XXX may be used later (eg .add_relation())
+
         defined[name] = cls
-        for relation in cls.__relations__:
+
+        # mypy: "Type[EntityType]" has no attribute "__relations__"
+        # dynamically set attribute, full yams magic
+        for relation in cls.__relations__:  # type: ignore
             cls._ensure_relation_type(relation)
 
     @classmethod
-    def _ensure_relation_type(cls, relation):
+    def _ensure_relation_type(cls: Type['EntityType'], relation) -> bool:
         """Check the type the relation
 
         return False if the class is not yet finalized
         (XXX raise excep instead ?)"""
+
         rtype = RelationType(relation.name)
+
         _copy_attributes(relation, rtype, RTYPE_PROPERTIES)
+
         # assert hasattr(cls, '_defined'), "Type definition for %s not yet expanded.
         # you can't register new type through it" % cls
+
         if hasattr(cls, '_defined'):
-            defined = cls._defined
+            # mypy: "Type[EntityType]" has no attribute "_defined"
+            # dynamically set attribute
+            defined = cls._defined  # type: ignore
+
             if relation.name in defined:
                 _copy_attributes(rtype, defined[relation.name], RTYPE_PROPERTIES)
             else:
                 defined[relation.name] = rtype
+
             return True
         else:
             return False
 
     @classmethod
-    def expand_relation_definitions(cls, defined, schema):
+    def expand_relation_definitions(cls: Type['EntityType'], defined, schema: _SchemaType) -> None:
         """schema building step 2:
 
         register all relations definition, expanding wildcards if necessary
         """
-        order = 1
-        name = getattr(cls, 'name', cls.__name__)
-        rdefprops = _RDEF_PROPERTIES()
-        for relation in cls.__relations__:
+        order: int = 1
+        name: str = getattr(cls, 'name', cls.__name__)
+        rdefprops: Tuple[str, ...] = _RDEF_PROPERTIES()
+
+        # mypy: "Type[EntityType]" has no attribute "__relations__"
+        # dynamically set attribute, full yams magic
+        for relation in cls.__relations__:  # type: ignore
             if isinstance(relation, SubjectRelation):
                 rdef = RelationDefinition(subject=name, name=relation.name,
                                           object=relation.etype, order=order,
                                           package=relation.package)
+
                 _copy_attributes(relation, rdef, rdefprops)
+
             elif isinstance(relation, ObjectRelation):
                 rdef = RelationDefinition(subject=relation.etype,
                                           name=relation.name,
                                           object=name, order=order,
                                           package=relation.package)
+
                 _copy_attributes(relation, rdef, rdefprops)
+
             elif isinstance(relation, RelationDefinition):
                 rdef = relation
             else:
                 raise BadSchemaDefinition('dunno how to handle %s' % relation)
+
             order += 1
             rdef._add_relations(defined, schema)
 
     # methods that can be used to extend an existant schema definition ########
 
     @classmethod
-    def extend(cls, othermetadefcls):
+    def extend(cls: Type['EntityType'], othermetadefcls) -> None:
         """add all relations of ``othermetadefcls`` to the current class"""
         for rdef in othermetadefcls.__relations__:
             cls.add_relation(rdef)
 
     @classmethod
-    def add_relation(cls, rdef, name=None):
+    def add_relation(cls: Type['EntityType'], rdef: _RdefType, name: Optional[str] = None) -> None:
         """Add ``rdef`` relation to the class"""
         if name:
             rdef.name = name
+
         if cls._ensure_relation_type(rdef):
-            _add_relation(cls.__relations__, rdef, name)
-            if getattr(rdef, 'metadata', {}) and rdef not in cls._defined:
-                for meta_name in rdef.metadata:
+            # mypy: "Type[EntityType]" has no attribute "__relations__"
+            # dynamically set attribute, full yams magic
+            _add_relation(cls.__relations__, rdef, name)  # type: ignore
+
+            # mypy: "Type[EntityType]" has no attribute "_defined"
+            # dynamically set attribute
+            if getattr(rdef, 'metadata', {}) and rdef not in cls._defined:  # type: ignore
+                for meta_name in rdef.metadata:  # type: ignore
                     format_attr_name = '_'.join(((name or rdef.name), meta_name))
                     rdef = next(cls.get_relations(format_attr_name))
                     cls._ensure_relation_type(rdef)
+
         else:
-            _add_relation(cls.__relations__, rdef, name=name)
+            # mypy: "Type[EntityType]" has no attribute "__relations__"
+            # dynamically set attribute, full yams magic
+            _add_relation(cls.__relations__, rdef, name=name)  # type: ignore
 
     @classmethod
-    def insert_relation_after(cls, afterrelname, name, rdef):
+    def insert_relation_after(cls: Type['EntityType'], afterrelname: str, name: str,
+                              rdef: _RdefType) -> None:
         """Add ``rdef`` relation to the class right after another"""
         # FIXME change order of arguments to rdef, name, afterrelname ?
         rdef.name = name
         cls._ensure_relation_type(rdef)
-        for i, rel in enumerate(cls.__relations__):
+
+        # mypy: "Type[EntityType]" has no attribute "__relations__"
+        # dynamically set attribute, full yams magic
+        i = 0
+        for i, rel in enumerate(cls.__relations__):  # type: ignore
             if rel.name == afterrelname:
                 break
+
         else:
             raise BadSchemaDefinition("can't find %s relation on %s" % (
                 afterrelname, cls
             ))
-        _add_relation(cls.__relations__, rdef, name, i + 1)
+
+        # mypy: "Type[EntityType]" has no attribute "__relations__"
+        # dynamically set attribute, full yams magic
+        _add_relation(cls.__relations__, rdef, name, i + 1)  # type: ignore
 
     @classmethod
-    def remove_relation(cls, name):
+    def remove_relation(cls: Type['EntityType'], name: str) -> None:
         """Remove relation from the class"""
+
+        # mypy: "Type[EntityType]" has no attribute "__relations__"
+        # dynamically set attribute, full yams magic
         for rdef in cls.get_relations(name):
-            cls.__relations__.remove(rdef)
+            cls.__relations__.remove(rdef)  # type: ignore
 
     @classmethod
-    def get_relations(cls, name):
+    def get_relations(cls: Type['EntityType'], name: str) -> Generator[_RdefType, Any, None]:
         """Iterate over relations definitions that match the ``name`` parameters
 
         It may iterate multiple definitions when the class is both object and
         sujet of a relation:
         """
-        for rdef in cls.__relations__[:]:
+
+        # mypy: "Type[EntityType]" has no attribute "__relations__"
+        # dynamically set attribute, full yams magic
+        for rdef in cls.__relations__[:]:  # type: ignore
             if rdef.name == name:
                 yield rdef
 
     @classmethod
-    def get_relation(cls, name):
+    def get_relation(cls: Type['EntityType'], name: str) -> _RdefType:
         """Return relation definitions by name. Fails if there is multiple one.
         """
         relations = tuple(cls.get_relations(name))
+
         assert len(relations) == 1, "can't use get_relation for relation with multiple definitions"
+
         return relations[0]
 
 
@@ -643,45 +768,59 @@ class RelationType(Definition):
     fulltext_container = MARKER
     rule = MARKER
 
-    def __init__(self, name=None, **kwargs):
+    def __init__(self, name: Optional[str] = None, **kwargs) -> None:
         """kwargs must have values in RTYPE_PROPERTIES"""
         super(RelationType, self).__init__(name)
+
         if kwargs.pop('meta', None):
             warn('[yams 0.37] meta is deprecated', DeprecationWarning, stacklevel=2)
+
         _check_kwargs(kwargs, RTYPE_PROPERTIES + ('description', '__permissions__'))
+
         self.__dict__.update(kwargs)
 
-    def __str__(self):
+    def __str__(self) -> str:
         return 'relation type %r' % self.name
 
     @classmethod
-    def expand_type_definitions(cls, defined):
+    def expand_type_definitions(cls: Type['RelationType'], defined: Dict[str, Type]) -> None:
         """schema building step 1:
 
         register definition objects by adding them to the `defined` dictionnary
         """
-        name = getattr(cls, 'name', cls.__name__)
+        name: str = getattr(cls, 'name', cls.__name__)
+
         if cls.__doc__ and not cls.description:
             cls.description = ' '.join(cls.__doc__.split())
+
         if name in defined:
             if defined[name].__class__ is not RelationType:
                 raise BadSchemaDefinition('duplicated relation type for %s'
                                           % name)
+
             # relation type created from a relation definition, override it
             allprops = _REL_PROPERTIES() + ('subject', 'object')
+
             _copy_attributes(defined[name], cls, allprops)
+
         defined[name] = cls
 
     @classmethod
-    def expand_relation_definitions(cls, defined, schema):
+    def expand_relation_definitions(cls: Type['RelationType'], defined,
+                                    schema: _SchemaType) -> None:
         """schema building step 2:
 
         register all relations definition, expanding wildcard if necessary
         """
-        name = getattr(cls, 'name', cls.__name__)
+        name: str = getattr(cls, 'name', cls.__name__)
+
         if getattr(cls, 'subject', None) and getattr(cls, 'object', None):
-            rdef = RelationDefinition(subject=cls.subject, name=name,
-                                      object=cls.object)
+            # mypy: "Type[RelationType]" has no attribute "subject"
+            # mypy: "Type[RelationType]" has no attribute "object"
+            # dynamically set attributes
+            rdef = RelationDefinition(subject=cls.subject, name=name,  # type: ignore
+                                      object=cls.object)  # type: ignore
+
             _copy_attributes(cls, rdef, _RDEF_PROPERTIES())
             rdef._add_relations(defined, schema)
 
@@ -689,9 +828,10 @@ class RelationType(Definition):
 class ComputedRelation(RelationType):
     __permissions__ = MARKER
 
-    def __init__(self, name=None, rule=None, **kwargs):
+    def __init__(self, name: Optional[str] = None, rule=None, **kwargs) -> None:
         if rule is not None:
             self.rule = rule
+
         super(ComputedRelation, self).__init__(name, **kwargs)
 
 
@@ -710,45 +850,63 @@ class RelationDefinition(Definition):
     inlined = MARKER
     formula = MARKER
 
-    def __init__(self, subject=None, name=None, object=None, package=None,
-                 **kwargs):
+    def __init__(self,
+                 subject: str = None,
+                 name: Optional[str] = None,
+                 object: str = None,
+                 package=None,
+                 **kwargs) -> None:
+
         """kwargs keys must have values in _RDEF_PROPERTIES()"""
         if subject:
             self.subject = subject
         else:
             self.subject = self.__class__.subject
+
         if object:
             self.object = object
         else:
             self.object = self.__class__.object
+
         super(RelationDefinition, self).__init__(name)
+
         global CREATION_RANK
         CREATION_RANK += 1
-        self.creation_rank = CREATION_RANK
+        self.creation_rank: int = CREATION_RANK
+
         if package is not None:
             self.package = package
         elif self.package == '<builtin>':
             self.package = PACKAGE
+
         if kwargs.pop('meta', None):
             warn('[yams 0.37] meta is deprecated', DeprecationWarning)
-        rdefprops = _RDEF_PROPERTIES()
+
+        rdefprops: Tuple[str, ...] = _RDEF_PROPERTIES()
+
         _check_kwargs(kwargs, rdefprops)
         _copy_attributes(attrdict(**kwargs), self, rdefprops)
+
         if self.constraints:
             self.constraints = list(self.constraints)
 
-    def __str__(self):
+    def __str__(self) -> str:
         return 'relation definition (%(subject)s %(name)s %(object)s)' % self.__dict__
 
+    _DefinedType = Dict[Union[Tuple[Any, str, Any], str],
+                        Union[RelationType, Type['RelationDefinition']]]
+
     @classmethod
-    def expand_type_definitions(cls, defined):
+    def expand_type_definitions(cls: Type['RelationDefinition'], defined: _DefinedType) -> None:
         """schema building step 1:
 
         register definition objects by adding them to the `defined` dictionnary
         """
-        name = getattr(cls, 'name', cls.__name__)
-        rtype = RelationType(name)
+        name: str = getattr(cls, 'name', cls.__name__)
+        rtype: RelationType = RelationType(name)
+
         _copy_attributes(cls, rtype, RTYPE_PROPERTIES)
+
         if name in defined:
             _copy_attributes(rtype, defined[name], RTYPE_PROPERTIES)
         else:
@@ -759,10 +917,12 @@ class RelationDefinition(Definition):
             subjects = cls.subject
         else:
             subjects = (cls.subject, )
+
         if isinstance(cls.object, tuple):
             objects = cls.object
         else:
             objects = (cls.object, )
+
         for sub in subjects:
             for obj in objects:
                 key = (sub, name, obj)
@@ -776,7 +936,9 @@ class RelationDefinition(Definition):
         defined[(cls.subject, name, cls.object)] = cls
 
     @classmethod
-    def expand_relation_definitions(cls, defined, schema):
+    def expand_relation_definitions(cls: Type['RelationDefinition'],
+                                    defined: _DefinedType,
+                                    schema: _SchemaType) -> None:
         """schema building step 2:
 
         register all relations definition, expanding wildcard if necessary
@@ -785,13 +947,15 @@ class RelationDefinition(Definition):
                                                                               cls.object)
         cls()._add_relations(defined, schema)
 
-    def _add_relations(self, defined, schema):
-        name = getattr(self, 'name', self.__class__.__name__)
+    def _add_relations(self, defined: _DefinedType, schema: _SchemaType) -> None:
+        name: str = getattr(self, 'name', self.__class__.__name__)
         rtype = defined[name]
-        rdefprops = _RDEF_PROPERTIES()
+        rdefprops: Tuple[str, ...] = _RDEF_PROPERTIES()
+
         # copy relation definition attributes set on the relation type, beside
         # description
         _copy_attributes(rtype, self, set(rdefprops) - set(('description',)))
+
         # process default cardinality and constraints if not set yet
         cardinality = self.cardinality
         if cardinality is MARKER:
@@ -803,14 +967,17 @@ class RelationDefinition(Definition):
             assert len(cardinality) == 2
             assert cardinality[0] in '1?+*'
             assert cardinality[1] in '1?+*'
+
         if not self.constraints:
             self.constraints = ()
+
         rschema = schema.rschema(name)
         if rschema.rule:
             raise BadSchemaDefinition(
                 'Cannot add relation definition "{0}" because an '
                 'homonymous computed relation already exists '
                 'with rule "{1}"'.format(rschema.type, rschema.rule))
+
         if self.__permissions__ is MARKER:
             final = next(iter(_actual_types(schema, self.object))) in BASE_TYPES
             if final:
@@ -822,29 +989,36 @@ class RelationDefinition(Definition):
                 permissions = DEFAULT_RELPERMS
         else:
             permissions = self.__permissions__
+
         for subj in _actual_types(schema, self.subject):
             for obj in _actual_types(schema, self.object):
                 rdef = RelationDefinition(subj, name, obj,
                                           __permissions__=permissions,
                                           package=self.package)
+
                 _copy_attributes(self, rdef, rdefprops)
                 schema.add_relation_def(rdef)
 
 
-def _actual_types(schema, etype):
+def _actual_types(schema: _SchemaType,
+                  etype: Union[str, list, Tuple[Any, Any]]) -> Union[Generator[Any, Any, None],
+                                                                     Tuple[Any], Any]:
     if etype == '*':
         return _pow_etypes(schema)
+
     if isinstance(etype, (list, tuple)):
         return etype
+
     if not isinstance(etype, str):
         raise RuntimeError('Entity types must not be instances but strings '
                            'or list/tuples thereof. Ex. (bad, good) : '
                            'SubjectRelation(Foo), SubjectRelation("Foo"). '
                            'Hence, %r is not acceptable.' % etype)
+
     return (etype,)
 
 
-def _pow_etypes(schema):
+def _pow_etypes(schema: _SchemaType) -> Generator[Any, Any, None]:
     for eschema in schema.entities():
         if eschema.final:
             continue
diff --git a/yams/types.py b/yams/types.py
--- a/yams/types.py
+++ b/yams/types.py
@@ -18,10 +18,11 @@
 
 """Types declarations for types annotations"""
 
-from typing import Union, TYPE_CHECKING, TypeVar
+from typing import Union, TYPE_CHECKING, TypeVar, Dict, Tuple
 
 
 _jsonSerializableType = TypeVar("_jsonSerializableType")
+_PermissionsType = Dict[str, Tuple[str, ...]]
 
 
 # to avoid circular imports



More information about the cubicweb-devel mailing list