RussBaz / enforce

Python 3.5+ runtime type checking for integration testing and data validation
543 stars 21 forks source link

Callable's arguments' types may be lost #46

Open kevinboulain opened 6 years ago

kevinboulain commented 6 years ago

As requested, I am opening this issue instead of #44.

Seen with enforce 0.3.4.

Example code where a bound method is used:

from typing import Optional, Callable, Any, Dict
import enforce

class Example:
    @enforce.runtime_validation
    def method(self, d: Dict):
        pass

@enforce.runtime_validation
def bound_callback_example(callback: Optional[Callable[[Dict], Any]]=None):
    pass

example = Example()
bound_callback_example(example.method)

Funnily two different exceptions may be raised by the above code:

Traceback (most recent call last):
  File "enforce_examples.py", line 14, in <module>
    bound_callback_example(example.method)
  File ".../venv/lib/python3.6/site-packages/enforce/decorators.py", line 104, in universal
    _args, _kwargs, _ = enforcer.validate_inputs(parameters)
  File ".../venv/lib/python3.6/site-packages/enforce/enforcers.py", line 86, in validate_inputs
    raise RuntimeTypeError(exception_text)
enforce.exceptions.RuntimeTypeError: 
  The following runtime type errors were encountered:
       Argument 'callback' was not of type typing.Union[typing.Callable[[typing.Dict], typing.Any], NoneType]. Actual type was BoundFunctionWrapper.
Traceback (most recent call last):
  File "enforce_examples.py", line 14, in <module>
    bound_callback_example(example.method)
  File ".../venv/lib/python3.6/site-packages/enforce/decorators.py", line 104, in universal
    _args, _kwargs, _ = enforcer.validate_inputs(parameters)
  File ".../venv/lib/python3.6/site-packages/enforce/enforcers.py", line 86, in validate_inputs
    raise RuntimeTypeError(exception_text)
enforce.exceptions.RuntimeTypeError: 
  The following runtime type errors were encountered:
       Argument 'callback' was not of type typing.Union[typing.Callable[[typing.Dict], typing.Any], NoneType]. Actual type was typing.Callable.

For now, the problem can be avoided by using a less strict type for the callback, to the detriment of self-documenting code and pushing the burden of type verification to the callable itself (if needed):

@enforce.runtime_validation
def bound_callback_example(callback: Optional[Callable]=None):
    pass
RussBaz commented 6 years ago

OK, I think I fixed this error. You can test it in the dev branch. BTW, passing Example.method will fail here because 'self' must be taken into account. It should produce a correct result now.