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

Frank Bessou frank.bessou at logilab.fr
Mon Feb 17 10:40:00 CET 2020



On 14/02/2020 15:59, Laurent Peuch wrote:
> # HG changeset patch
> # User Laurent Peuch <cortex at worlddomination.be>
> # Date 1579099986 -3600
> #      Wed Jan 15 15:53:06 2020 +0100
> # Node ID 89d379f5a59bb5cb9307a4155454ac450bdbfdd7
> # Parent  173467b25265bbd4c7eeab7fbd2410bf9e791077
> # Available At https://hg.logilab.org/users/lpeuch/yams
> #              hg pull https://hg.logilab.org/users/lpeuch/yams -r 89d379f5a59b
> [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,33 @@ KEYWORD_MAP = {
>       'Date': {'TODAY': date.today}
>   }
>   
> +# bw compat for literal date/time values stored as strings in schemas
>   
> -# bw compat for literal date/time values stored as strings in schemas
> -DATE_FACTORY_MAP = {
> +# parse_datetime_or_time_type = Union[Callable[[str], datetime],
> +#                                     Callable[[str], float],
> +#                                     # strptime_time can have an optional format argument
> +#                                     Callable[[str, str], float]]


If the second argument is optional and is never used (which seems to be 
the case) you can remove this lat type.
Also, you can take a look at TypedDict which seems to be appropriate to 
type this kind of values:
https://mypy.readthedocs.io/en/latest/more_types.html#id3
(this is only available since python3.8 but there is a backport in the 
typing-extensions package).


> +# Apparently this is not possible, see https://github.com/python/mypy/issues/8282
> +# DATE_FACTORY_MAP: Dict[str, parse_datetime_or_time_type] = {
> +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]:

Why int and bool ?


>       # rdef can be either a .schema.RelationDefinitionSchema
>       # or a .buildobjs.RelationDefinition
> -    rtype = getattr(rdef, 'name', None) or rdef.rtype.type
> +    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 +115,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 +124,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")
> 

-- 
Frank Bessou
Logilab         https://www.logilab.fr



More information about the cubicweb-devel mailing list