From ffaed8372f5eab3500a9cc6df1f9d327d064fcdc Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Fri, 17 Jan 2020 11:02:39 +0100 Subject: [PATCH 1/4] On the way to fixing #50 : created stubs for module `entry_points_annotations`. --- valid8/entry_points_annotations.py | 122 +------------------------ valid8/entry_points_annotations.pyi | 137 ++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 119 deletions(-) create mode 100644 valid8/entry_points_annotations.pyi diff --git a/valid8/entry_points_annotations.py b/valid8/entry_points_annotations.py index 8cc1258..9e01c15 100644 --- a/valid8/entry_points_annotations.py +++ b/valid8/entry_points_annotations.py @@ -23,7 +23,7 @@ except ImportError: from funcsigs import signature, Signature -from makefun import with_signature, wraps +from makefun import wraps from valid8.utils.decoration_tools import apply_on_each_func_args_sig from valid8.utils.typing_tools import is_pep484_nonable @@ -93,25 +93,11 @@ def get_variable_str(self): return self.validator.validated_field_name + '=' + str(self.var_value) -# Python 3+: load the 'more explicit api' -if use_typing: - new_sig = """(self, - validated_func: Callable, - *validation_func: ValidationFuncs, - error_type: 'Type[ValidationError]' = None, - help_msg: str = None, - none_policy: int = None, - **kw_context_args):""" -else: - new_sig = None - - class FuncValidator(Validator): """ Represents a special kind of `Validator` responsible to validate a function input or output """ - @with_signature(new_sig) def __init__(self, validated_func, # type: Callable *validation_func, # type: ValidationFuncs @@ -162,7 +148,6 @@ class InputValidator(FuncValidator): Represents a special kind of `Validator` responsible to validate a function input. """ - @with_signature(new_sig) def __init__(self, validated_func, # type: Callable, *validation_func, # type: ValidationFuncs @@ -203,7 +188,6 @@ def __init__(self, class OutputValidator(FuncValidator): """ Represents a special kind of `Validator` responsible to validate a function output. """ - @with_signature(new_sig) def __init__(self, validated_func, # type: Callable *validation_func, # type: ValidationFuncs @@ -260,27 +244,12 @@ def assert_valid(self, **kw_context_args) -# Python 3+: load the 'more explicit api' -if use_typing: - new_sig = """(self, - validated_class: Callable, - validated_field_name: str, - *validation_func: ValidationFuncs, - error_type: 'Type[ClassFieldValidationError]' = None, - help_msg: str = None, - none_policy: int = None, - **kw_context_args):""" -else: - new_sig = None - - class ClassFieldValidator(Validator): """ Represents a special kind of `Validator` responsible to validate a class field. As opposed to other validators, the name of the field is hardcoded. """ - @with_signature(new_sig) def __init__(self, validated_class, # type: Callable, validated_field_name, # type: str @@ -341,21 +310,7 @@ def get_validated_class_display_name(self): return self.validated_class.__name__ -# Python 3+: load the 'more explicit api' -if use_typing: - new_sig = """(cls, - field_name, - *validation_func: ValidationFuncs, - help_msg: str = None, - error_type: 'Type[InputValidationError]' = None, - none_policy: int = None, - **kw_context_args) -> 'Type':""" -else: - new_sig = None - - @class_decorator(flat_mode_decorated_name='cls') -@with_signature(new_sig) def validate_field(cls, field_name, *validation_func, # type: ValidationFuncs @@ -439,21 +394,7 @@ def myfunc(a, b): return decorate_several_with_validation(f, none_policy=none_policy, _out_=_out_, **kw_validation_funcs) -# Python 3+: load the 'more explicit api' -if use_typing: - new_sig = """(f, - arg_name, - *validation_func: ValidationFuncs, - help_msg: str = None, - error_type: 'Type[InputValidationError]' = None, - none_policy: int = None, - **kw_context_args) -> Callable:""" -else: - new_sig = None - - @function_decorator(flat_mode_decorated_name='f') -@with_signature(new_sig) def validate_arg(f, arg_name, *validation_func, # type: ValidationFuncs @@ -485,18 +426,6 @@ def validate_arg(f, return decorate_with_validation(f, arg_name, *validation_func, **kwargs) -# Python 3+: load the 'more explicit api' -if use_typing: - new_sig = """(*validation_func: ValidationFuncs, - help_msg: str = None, - error_type: 'Type[OutputValidationError]' = None, - none_policy: int = None, - **kw_context_args) -> Callable:""" -else: - new_sig = None - - -@with_signature(new_sig) def validate_out(*validation_func, # type: ValidationFuncs **kwargs): # type: (...) -> Callable @@ -530,20 +459,6 @@ def decorate(f): """ The reserved key for output validation """ -# Python 3+: load the 'more explicit api' -if use_typing: - new_sig = """(cls, - field_name: str, - *validation_func: ValidationFuncs, - help_msg: str = None, - error_type: 'Union[Type[InputValidationError], Type[OutputValidationError]]' = None, - none_policy: int = None, - **kw_context_args) -> Callable:""" -else: - new_sig = None - - -@with_signature(new_sig) def decorate_cls_with_validation(cls, field_name, # type: str *validation_func, # type: ValidationFuncs @@ -721,21 +636,6 @@ def decorate_several_with_validation(func, return func -# Python 3+: load the 'more explicit api' -if use_typing: - new_sig = """(func, - arg_name: str, - *validation_func: ValidationFuncs, - help_msg: str = None, - error_type: 'Union[Type[InputValidationError], Type[OutputValidationError]]' = None, - none_policy: int = None, - _constructor_of_cls_: 'Type'=None, - **kw_context_args) -> Callable:""" -else: - new_sig = None - - -@with_signature(new_sig) def decorate_with_validation(func, arg_name, # type: str *validation_func, # type: ValidationFuncs @@ -826,23 +726,6 @@ class fields)""" pass -# Python 3+: load the 'more explicit api' -if use_typing: - new_sig = """(validated_func: Callable, - s: Signature, - arg_name: str, - *validation_func: ValidationFuncs, - help_msg: str = None, - error_type: 'Type[InputValidationError]' = None, - none_policy: int = None, - validated_class: 'Type'=None, - validated_class_field_name: str=None, - **kw_context_args):""" -else: - new_sig = None - - -@with_signature(new_sig) def _create_function_validator(validated_func, # type: Callable s, # type: Signature arg_name, # type: str @@ -895,10 +778,11 @@ def _create_function_validator(validated_func, # type: Callable error_type=error_type, help_msg=help_msg, **kw_context_args) -def decorate_with_validators(func, +def decorate_with_validators(func, # type: Callable func_signature=None, # type: Signature **validators # type: Union[Validator, List[Validator]] ): + # type: (...) -> Callable """ Utility method to decorate the provided function with the provided input and output Validator objects. Since this method takes Validator objects as argument, it is for advanced users. diff --git a/valid8/entry_points_annotations.pyi b/valid8/entry_points_annotations.pyi new file mode 100644 index 0000000..fb754a6 --- /dev/null +++ b/valid8/entry_points_annotations.pyi @@ -0,0 +1,137 @@ +from typing import Callable, List, Union, Type + +try: + from inspect import signature, Signature +except ImportError: + from funcsigs import signature, Signature + +from valid8.composition import ValidationFuncs +from valid8.entry_points import ValidationError, Validator + + +class InputValidationError(ValidationError): + ... + + +class OutputValidationError(ValidationError): + ... + + +class ClassFieldValidationError(ValidationError): + ... + + +class FuncValidator(Validator): + def __init__(self, + validated_func: Callable, + *validation_func: ValidationFuncs, + error_type: Type[ValidationError] = None, + help_msg: str = None, + none_policy: int = None, + **kw_context_args): + ... + + def get_validated_func_display_name(self): + ... + + +class InputValidator(FuncValidator): + ... + + +class OutputValidator(FuncValidator): + ... + + +class ClassFieldValidator(Validator): + ... + + def get_validated_class_display_name(self): + ... + + +def validate_field(cls, + field_name, + *validation_func: ValidationFuncs, + help_msg: str = None, + error_type: Type[InputValidationError] = None, + none_policy: int = None, + **kw_context_args) -> Callable[[Type], Type]: + ... + + +def validate_io(none_policy: int=None, + _out_: ValidationFuncs=None, + **kw_validation_funcs: ValidationFuncs + ) -> Callable[[Callable], Callable]: + ... + + +def validate_arg(arg_name, + *validation_func: ValidationFuncs, + help_msg: str = None, + error_type: Type[InputValidationError] = None, + none_policy: int = None, + **kw_context_args) -> Callable[[Callable], Callable]: + ... + + +def validate_out(*validation_func: ValidationFuncs, + help_msg: str = None, + error_type: Type[OutputValidationError] = None, + none_policy: int = None, + **kw_context_args) -> Callable[[Callable], Callable]: + ... + + +def decorate_cls_with_validation(cls, + field_name: str, + *validation_func: ValidationFuncs, + help_msg: str = None, + error_type: 'Union[Type[InputValidationError], Type[OutputValidationError]]' = None, + none_policy: int = None, + **kw_context_args) -> Callable: + ... + + +def decorate_several_with_validation(func, + _out_: ValidationFuncs = None, + none_policy: int = None, + **validation_funcs: ValidationFuncs + ) -> Callable: + ... + + +def decorate_with_validation(func, + arg_name: str, + *validation_func: ValidationFuncs, + help_msg: str = None, + error_type: Union[Type[InputValidationError], Type[OutputValidationError]] = None, + none_policy: int = None, + _constructor_of_cls_: Type=None, + **kw_context_args) -> Callable: + ... + + +class InvalidNameError(ValueError): + ... + + +def _create_function_validator(validated_func: Callable, + s: Signature, + arg_name: str, + *validation_func: ValidationFuncs, + help_msg: str = None, + error_type: Type[InputValidationError] = None, + none_policy: int = None, + validated_class: Type=None, + validated_class_field_name: str=None, + **kw_context_args) -> Union[ClassFieldValidator, InputValidator, OutputValidator]: + ... + + +def decorate_with_validators(func: Callable, + func_signature: Signature = None, + **validators: Union[Validator, List[Validator]] + ) -> Callable: + ... From 637a1a006ea63e3661577cef5a598392523dd88f Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Jan 2020 11:26:51 +0100 Subject: [PATCH 2/4] Package made PEP561 compatible. Fixed #55 --- setup.py | 11 ++++++++--- valid8/py.typed | 0 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 valid8/py.typed diff --git a/setup.py b/setup.py index 3cec893..78ebfb7 100644 --- a/setup.py +++ b/setup.py @@ -137,9 +137,8 @@ # If there are data files included in your packages that need to be # installed, specify them here. If using Python 2.6 or less, then these # have to be included in MANIFEST.in as well. - # package_data={ - # 'sample': ['package_data.dat'], - # }, + # Note: we use the empty string so that this also works with submodules + package_data={"": ['py.typed', '*.pyi']}, # Although 'package_data' is the preferred approach, in some case you may # need to place data files outside of your packages. See: @@ -155,4 +154,10 @@ # 'sample=sample:main', # ], # }, + + # explicitly setting the flag to avoid `ply` being downloaded + # see https://github.com/smarie/python-getversion/pull/5 + # and to make mypy happy + # see https://mypy.readthedocs.io/en/latest/installed_packages.html + zip_safe=False, ) diff --git a/valid8/py.typed b/valid8/py.typed new file mode 100644 index 0000000..e69de29 From 3050afeab357b4f2ce94f59d45edd82789668c41 Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Jan 2020 11:39:39 +0100 Subject: [PATCH 3/4] Improved type hints for decorators so that they do not make the decorated item loose its type hints. Fixed #56 --- valid8/entry_points_annotations.py | 39 +++++++++++++----------- valid8/entry_points_annotations.pyi | 47 ++++++++++++++++------------- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/valid8/entry_points_annotations.py b/valid8/entry_points_annotations.py index 9e01c15..d159696 100644 --- a/valid8/entry_points_annotations.py +++ b/valid8/entry_points_annotations.py @@ -5,7 +5,7 @@ try: # python 3.5+ # noinspection PyUnresolvedReferences - from typing import Callable, Any, List, Union + from typing import Callable, Any, List, Union, TypeVar try: # python 3.5.3- # noinspection PyUnresolvedReferences from typing import Type @@ -14,6 +14,10 @@ else: # noinspection PyUnresolvedReferences from valid8.composition import ValidationFuncs + + DecoratedClass = TypeVar("DecoratedClass", bound=Type[Any]) + DecoratedFunc = TypeVar("DecoratedFunc", bound=Callable) + use_typing = sys.version_info > (3, 0) except ImportError: use_typing = False @@ -311,11 +315,11 @@ def get_validated_class_display_name(self): @class_decorator(flat_mode_decorated_name='cls') -def validate_field(cls, +def validate_field(cls, # type: DecoratedClass field_name, *validation_func, # type: ValidationFuncs **kwargs): - # type: (...) -> Callable + # type: (...) -> DecoratedClass """ A class decorator. It goes through all class variables and for all of those that are descriptors with a __set__, it wraps the descriptors' setter function with a `validate_arg` annotation @@ -340,11 +344,12 @@ def validate_field(cls, @function_decorator -def validate_io(f=DECORATED, +def validate_io(f=DECORATED, # type: DecoratedFunc none_policy=None, # type: int _out_=None, # type: ValidationFuncs **kw_validation_funcs # type: ValidationFuncs ): + # type: (...) -> DecoratedFunc """ A function decorator to add input validation prior to the function execution. It should be called with named arguments: for each function arg name, provide a single validation function or a list of validation functions to @@ -395,12 +400,12 @@ def myfunc(a, b): @function_decorator(flat_mode_decorated_name='f') -def validate_arg(f, +def validate_arg(f, # type: DecoratedFunc arg_name, *validation_func, # type: ValidationFuncs **kwargs ): - # type: (...) -> Callable + # type: (...) -> DecoratedFunc """ A decorator to apply function input validation for the given argument name, with the provided base validation function(s). You may use several such decorators on a given function as long as they are stacked on top of each @@ -428,7 +433,7 @@ def validate_arg(f, def validate_out(*validation_func, # type: ValidationFuncs **kwargs): - # type: (...) -> Callable + # type: (...) -> Callable[[DecoratedFunc], DecoratedFunc] """ A decorator to apply function output validation to this function's output, with the provided base validation function(s). You may use several such decorators on a given function as long as they are stacked on top of each @@ -459,11 +464,11 @@ def decorate(f): """ The reserved key for output validation """ -def decorate_cls_with_validation(cls, +def decorate_cls_with_validation(cls, # type: DecoratedClass field_name, # type: str *validation_func, # type: ValidationFuncs **kwargs): - # type: (...) -> Type[Any] + # type: (...) -> DecoratedClass """ This method is equivalent to decorating a class with the `@validate_field` decorator but can be used a posteriori. @@ -603,12 +608,12 @@ def decorate_cls_with_validation(cls, return cls -def decorate_several_with_validation(func, +def decorate_several_with_validation(func, # type: DecoratedFunc _out_=None, # type: ValidationFuncs none_policy=None, # type: int **validation_funcs # type: ValidationFuncs ): - # type: (...) -> Callable + # type: (...) -> DecoratedFunc """ This method is equivalent to applying `decorate_with_validation` once for each of the provided arguments of the function `func` as well as output `_out_`. validation_funcs keyword arguments are validation functions for each @@ -636,11 +641,11 @@ def decorate_several_with_validation(func, return func -def decorate_with_validation(func, +def decorate_with_validation(func, # type: DecoratedFunc arg_name, # type: str *validation_func, # type: ValidationFuncs **kwargs): - # type: (...) -> Callable + # type: (...) -> DecoratedFunc """ This method is the inner method used in `@validate_io`, `@validate_arg` and `@validate_out`. It can be used if you with to perform decoration manually without a decorator. @@ -697,7 +702,7 @@ def decorate_with_validation(func, def _get_final_none_policy_for_validator(is_nonable, # type: bool none_policy # type: NoneArgPolicy - ): + ) -> NoneArgPolicy: """ Depending on none_policy and of the fact that the target parameter is nonable or not, returns a corresponding NonePolicy @@ -731,7 +736,7 @@ def _create_function_validator(validated_func, # type: Callable arg_name, # type: str *validation_func, # type: ValidationFuncs **kwargs): - + # type: (...) -> Union[ClassFieldValidator, InputValidator, OutputValidator] error_type, help_msg, none_policy, validated_class, validated_class_field_name = \ pop_kwargs(kwargs, [('error_type', None), ('help_msg', None), ('none_policy', None), ('validated_class', None), ('validated_class_field_name', None)], allow_others=True) @@ -778,11 +783,11 @@ def _create_function_validator(validated_func, # type: Callable error_type=error_type, help_msg=help_msg, **kw_context_args) -def decorate_with_validators(func, # type: Callable +def decorate_with_validators(func, # type: DecoratedFunc func_signature=None, # type: Signature **validators # type: Union[Validator, List[Validator]] ): - # type: (...) -> Callable + # type: (...) -> DecoratedFunc """ Utility method to decorate the provided function with the provided input and output Validator objects. Since this method takes Validator objects as argument, it is for advanced users. diff --git a/valid8/entry_points_annotations.pyi b/valid8/entry_points_annotations.pyi index fb754a6..6482b57 100644 --- a/valid8/entry_points_annotations.pyi +++ b/valid8/entry_points_annotations.pyi @@ -1,4 +1,4 @@ -from typing import Callable, List, Union, Type +from typing import Callable, List, Union, Any, Type, TypeVar try: from inspect import signature, Signature @@ -50,20 +50,25 @@ class ClassFieldValidator(Validator): ... -def validate_field(cls, - field_name, +DecoratedClass = TypeVar("DecoratedClass", bound=Type[Any]) + + +def validate_field(field_name, *validation_func: ValidationFuncs, help_msg: str = None, error_type: Type[InputValidationError] = None, none_policy: int = None, - **kw_context_args) -> Callable[[Type], Type]: + **kw_context_args) -> Callable[[DecoratedClass], DecoratedClass]: ... +DecoratedFunc = TypeVar("DecoratedFunc", bound=Callable) + + def validate_io(none_policy: int=None, _out_: ValidationFuncs=None, **kw_validation_funcs: ValidationFuncs - ) -> Callable[[Callable], Callable]: + ) -> Callable[[DecoratedFunc], DecoratedFunc]: ... @@ -72,7 +77,7 @@ def validate_arg(arg_name, help_msg: str = None, error_type: Type[InputValidationError] = None, none_policy: int = None, - **kw_context_args) -> Callable[[Callable], Callable]: + **kw_context_args) -> Callable[[DecoratedFunc], DecoratedFunc]: ... @@ -80,36 +85,36 @@ def validate_out(*validation_func: ValidationFuncs, help_msg: str = None, error_type: Type[OutputValidationError] = None, none_policy: int = None, - **kw_context_args) -> Callable[[Callable], Callable]: + **kw_context_args) -> Callable[[DecoratedFunc], DecoratedFunc]: ... -def decorate_cls_with_validation(cls, +def decorate_cls_with_validation(cls: DecoratedClass, field_name: str, *validation_func: ValidationFuncs, help_msg: str = None, error_type: 'Union[Type[InputValidationError], Type[OutputValidationError]]' = None, none_policy: int = None, - **kw_context_args) -> Callable: + **kw_context_args) -> DecoratedClass: ... -def decorate_several_with_validation(func, +def decorate_several_with_validation(func: DecoratedFunc, _out_: ValidationFuncs = None, none_policy: int = None, **validation_funcs: ValidationFuncs - ) -> Callable: + ) -> DecoratedFunc: ... -def decorate_with_validation(func, - arg_name: str, - *validation_func: ValidationFuncs, - help_msg: str = None, - error_type: Union[Type[InputValidationError], Type[OutputValidationError]] = None, - none_policy: int = None, - _constructor_of_cls_: Type=None, - **kw_context_args) -> Callable: +def decorate_with_validation(func: DecoratedFunc, + arg_name: str, + *validation_func: ValidationFuncs, + help_msg: str = None, + error_type: Union[Type[InputValidationError], Type[OutputValidationError]] = None, + none_policy: int = None, + _constructor_of_cls_: Type=None, + **kw_context_args) -> DecoratedFunc: ... @@ -130,8 +135,8 @@ def _create_function_validator(validated_func: Callable, ... -def decorate_with_validators(func: Callable, +def decorate_with_validators(func: DecoratedFunc, func_signature: Signature = None, **validators: Union[Validator, List[Validator]] - ) -> Callable: + ) -> DecoratedFunc: ... From c8eb65f3d40ef4a433dbd6eb7211e1b171a7fdea Mon Sep 17 00:00:00 2001 From: Sylvain MARIE Date: Wed, 22 Jan 2020 11:44:06 +0100 Subject: [PATCH 4/4] Fixed python 2 compliance --- valid8/entry_points_annotations.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/valid8/entry_points_annotations.py b/valid8/entry_points_annotations.py index d159696..61e8331 100644 --- a/valid8/entry_points_annotations.py +++ b/valid8/entry_points_annotations.py @@ -702,7 +702,8 @@ def decorate_with_validation(func, # type: DecoratedFunc def _get_final_none_policy_for_validator(is_nonable, # type: bool none_policy # type: NoneArgPolicy - ) -> NoneArgPolicy: + ): + # type: (...) -> NoneArgPolicy """ Depending on none_policy and of the fact that the target parameter is nonable or not, returns a corresponding NonePolicy