[PATCH 05 of 24 yams V2] [mypy] type __init__.py and add types.py

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


# HG changeset patch
# User Laurent Peuch <cortex at worlddomination.be>
# Date 1579099986 -3600
#      Wed Jan 15 15:53:06 2020 +0100
# Node ID fcf88607a18da9fa279022bab4c60c4e43b6a8bc
# Parent  6401553004303a68f2f362aa958dc4beaf3d521f
# Available At https://hg.logilab.org/users/lpeuch/yams
#              hg pull https://hg.logilab.org/users/lpeuch/yams -r fcf88607a18d
# EXP-Topic type_annotations
[mypy] type __init__.py and add types.py

diff --git a/yams/__init__.py b/yams/__init__.py
--- a/yams/__init__.py
+++ b/yams/__init__.py
@@ -17,56 +17,60 @@
 # along with yams. If not, see <https://www.gnu.org/licenses/>.
 """Object model and utilities to define generic Entities/Relations schemas.
 """
-__docformat__ = "restructuredtext en"
-
 import warnings
 from datetime import datetime, date
 
+from typing import Set, Type, Dict, Tuple, Union, Callable, Any, cast, Iterable, TypeVar
+
 import pkg_resources
 
 from logilab.common.date import strptime, strptime_time
 from logilab.common import nullobject
 
 from yams._exceptions import SchemaError, UnknownType, BadSchemaDefinition, ValidationError  # noqa
+from yams.types import _RdefType
 
+__docformat__: str = "restructuredtext en"
 
-__version__ = pkg_resources.get_distribution('yams').version
+_PermissionsType = Dict[str, Tuple[str, ...]]
 
-_ = str
+__version__: str = pkg_resources.get_distribution('yams').version
 
-MARKER = nullobject()
+_: Type[str] = str
+
+MARKER: nullobject = nullobject()
 
-BASE_TYPES = set(('String', 'Password', 'Bytes',
-                  'Int', 'BigInt', 'Float', 'Boolean', 'Decimal',
-                  'Date', 'Time', 'Datetime', 'TZTime', 'TZDatetime',
-                  'Interval',
-                  ))
+BASE_TYPES: Set[str] = set(('String', 'Password', 'Bytes',
+                            'Int', 'BigInt', 'Float', 'Boolean', 'Decimal',
+                            'Date', 'Time', 'Datetime', 'TZTime', 'TZDatetime',
+                            'Interval',
+                            ))
 
 # base groups used in permissions
-BASE_GROUPS = set((_('managers'), _('users'), _('guests'), _('owners')))
+BASE_GROUPS: Set[str] = set((_('managers'), _('users'), _('guests'), _('owners')))
 
 # default permissions for entity types, relations and attributes
-DEFAULT_ETYPEPERMS = {'read': ('managers', 'users', 'guests',),
-                      'update': ('managers', 'owners',),
-                      'delete': ('managers', 'owners'),
-                      'add': ('managers', 'users',)}
-DEFAULT_RELPERMS = {'read': ('managers', 'users', 'guests',),
-                    'delete': ('managers', 'users'),
-                    'add': ('managers', 'users',)}
-DEFAULT_ATTRPERMS = {'read': ('managers', 'users', 'guests',),
-                     'add': ('managers', 'users'),
-                     'update': ('managers', 'owners')}
-DEFAULT_COMPUTED_RELPERMS = {'read': ('managers', 'users', 'guests',),
-                             'delete': (),
-                             'add': ()}
-DEFAULT_COMPUTED_ATTRPERMS = {'read': ('managers', 'users', 'guests',),
-                              'add': (),
-                              'update': ()}
-
+DEFAULT_ETYPEPERMS: _PermissionsType = {'read': ('managers', 'users', 'guests',),
+                                        'update': ('managers', 'owners',),
+                                        'delete': ('managers', 'owners'),
+                                        'add': ('managers', 'users',)}
+DEFAULT_RELPERMS: _PermissionsType = {'read': ('managers', 'users', 'guests',),
+                                      'delete': ('managers', 'users'),
+                                      'add': ('managers', 'users',)}
+DEFAULT_ATTRPERMS: _PermissionsType = {'read': ('managers', 'users', 'guests',),
+                                       'add': ('managers', 'users'),
+                                       'update': ('managers', 'owners')}
+DEFAULT_COMPUTED_RELPERMS: _PermissionsType = {'read': ('managers', 'users', 'guests',),
+                                               'delete': (),
+                                               'add': ()}
+DEFAULT_COMPUTED_ATTRPERMS: _PermissionsType = {'read': ('managers', 'users', 'guests',),
+                                                'add': (),
+                                                'update': ()}
 
 # This provides a way to specify callable objects as default values
 # First level is the final type, second is the keyword to callable map
-KEYWORD_MAP = {
+_current_date_or_datetime_constructor_type = Callable[[], Union[date, datetime]]
+KEYWORD_MAP: Dict[str, Dict[str, _current_date_or_datetime_constructor_type]] = {
     'Datetime': {'NOW': datetime.now,
                  'TODAY': datetime.today},
     'TZDatetime': {'NOW': datetime.utcnow,
@@ -74,21 +78,30 @@ KEYWORD_MAP = {
     'Date': {'TODAY': date.today}
 }
 
-
 # bw compat for literal date/time values stored as strings in schemas
-DATE_FACTORY_MAP = {
+DATE_FACTORY_MAP: Dict[str, Callable[[str], Union[datetime, float]]] = {
     'Datetime': lambda x: ':' in x and strptime(x, '%Y/%m/%d %H:%M') or strptime(x, '%Y/%m/%d'),
     'Date': lambda x: strptime(x, '%Y/%m/%d'),
-    'Time': strptime_time
+    'Time': strptime_time  # returns a float (from time())
 }
 
-KNOWN_METAATTRIBUTES = set(('format', 'encoding', 'name'))
+KNOWN_METAATTRIBUTES: Set[str] = set(('format', 'encoding', 'name'))
+
+
+_type_of_default = TypeVar('_type_of_default')
 
 
-def convert_default_value(rdef, default):
+def convert_default_value(rdef: _RdefType,
+                          default: _type_of_default) -> Union[_type_of_default, datetime,
+                                                              date, float, str, int, bool]:
     # rdef can be either a .schema.RelationDefinitionSchema
     # or a .buildobjs.RelationDefinition
-    rtype = getattr(rdef, 'name', None) or rdef.rtype.type
+
+    # mypy ouput: Item "RelationDefinition" of "Union[RelationDefinitionSchema,
+    # mypy output: RelationDefinition]" has no attribute "rtype"
+    # RelationDefinition will succed on having a name so it won't jump to ".rtype"
+    rtype = getattr(rdef, 'name', None) or rdef.rtype.type  # type: ignore
+
     if isinstance(default, str) and rdef.object != 'String':
         # real Strings can be anything,
         # including things that look like keywords for other base types
@@ -99,6 +112,7 @@ def convert_default_value(rdef, default)
                 # the default was likely not a special constant
                 # like TODAY but some literal
                 pass
+
         # bw compat for old schemas
         if rdef.object in DATE_FACTORY_MAP:
             warnings.warn('using strings as default values '
@@ -107,42 +121,59 @@ def convert_default_value(rdef, default)
                           'the plain python objects instead'
                           % (rtype, rdef.object),
                           DeprecationWarning)
+
             try:
-                return DATE_FACTORY_MAP[rdef.object](default)
+                if rdef.object == "Time":
+                    cast(Callable[[str], float], DATE_FACTORY_MAP[rdef.object])
+                    return DATE_FACTORY_MAP[rdef.object](default)
+                else:
+                    cast(Callable[[str], datetime], DATE_FACTORY_MAP[rdef.object])
+                    return DATE_FACTORY_MAP[rdef.object](default)
+
             except ValueError as verr:
                 raise ValueError('creating a default value for '
                                  'attribute %s of type %s from the string %r'
                                  ' is not supported (cause %s)'
                                  % (rtype, rdef.object, default, verr))
+
     if rdef.object == 'String':
-        default = str(default)
+        return str(default)
+
     return default  # general case: untouched default
 
 
-def register_base_type(name, parameters=(), check_function=None):
+def register_base_type(name: str, parameters: Union[Dict[str, Any], Iterable[str]] = (),
+                       check_function: Callable = None) -> None:
     """register a yams base (final) type. You'll have to call
     base_type_class to generate the class.
     """
     from yams.schema import RelationDefinitionSchema
     from yams.constraints import BASE_CHECKERS, yes
+
     # Add the datatype to yams base types
     assert name not in BASE_TYPES, '%s already in BASE_TYPES %s' % (
         name, BASE_TYPES)
+
     BASE_TYPES.add(name)
+
     # Add the new datatype to the authorized types of RelationDefinitionSchema
     if not isinstance(parameters, dict):
         # turn tuple/list into dict with None values
         parameters = dict((p, None) for p in parameters)
+
     RelationDefinitionSchema.BASE_TYPE_PROPERTIES[name] = parameters
+
     # Add a yams checker or yes is not specified
     BASE_CHECKERS[name] = check_function or yes
 
 
-def unregister_base_type(name):
+def unregister_base_type(name: str) -> None:
     """Unregister a yams base (final) type"""
     from yams.schema import RelationDefinitionSchema
     from yams.constraints import BASE_CHECKERS
+
     assert name in BASE_TYPES, '%s not in BASE_TYPES %s' % (name, BASE_TYPES)
+
     BASE_TYPES.remove(name)
     RelationDefinitionSchema.BASE_TYPE_PROPERTIES.pop(name)
     BASE_CHECKERS.pop(name)
diff --git a/yams/schema.py b/yams/schema.py
--- a/yams/schema.py
+++ b/yams/schema.py
@@ -16,6 +16,7 @@
 # You should have received a copy of the GNU Lesser General Public License along
 # with yams. If not, see <http://www.gnu.org/licenses/>.
 from logilab.common.logging_ext import set_log_methods
+from typing import Dict, Any
 import logging
 """Classes to define generic Entities/Relations schemas."""
 
@@ -598,9 +599,11 @@ class RelationDefinitionSchema(Permissio
                           'formula': None,
                           }
     # Use a TYPE_PROPERTIES dictionnary to store type-dependant parameters.
-    BASE_TYPE_PROPERTIES = {'String': {'fulltextindexed': False,
-                                       'internationalizable': False},
-                            'Bytes': {'fulltextindexed': False}}
+    # in certains situation in yams.register_base_type, the value of the
+    # subdict can be None or Any
+    BASE_TYPE_PROPERTIES: Dict[str, Dict[str, Any]] = {'String': {'fulltextindexed': False,
+                                                                  'internationalizable': False},
+                                                       'Bytes': {'fulltextindexed': False}}
 
     @classmethod
     def ALL_PROPERTIES(cls):
diff --git a/yams/types.py b/yams/types.py
new file mode 100644
--- /dev/null
+++ b/yams/types.py
@@ -0,0 +1,31 @@
+# copyright 2019 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact at logilab.fr
+#
+# This file is part of yams.
+#
+# yams is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# yams is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with yams. If not, see <https://www.gnu.org/licenses/>.
+
+"""Types declarations for types annotations"""
+
+from typing import Union, TYPE_CHECKING, TypeVar
+
+
+# to avoid circular imports
+if TYPE_CHECKING:
+    from yams import schema, buildobjs
+
+    _RdefType = Union[schema.RelationDefinitionSchema, buildobjs.RelationDefinition]
+
+else:
+    _RdefType = TypeVar("rdef")



More information about the cubicweb-devel mailing list