RussBaz / enforce

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

(some?) built-in generics are not supported #52

Open grayfall opened 6 years ago

grayfall commented 6 years ago

I use the Sequence generic ABC in my codebase a lot, but runtime_validation doesn't seem to handle it. Here is a small reproducible example:

from typing import Sequence, List
from enforce import runtime_validation

@runtime_validation
def add_one(arg: Sequence[int]) -> List[int]:
    return [arg + 1] 

add_one(range(10))

The traceback:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/.venvs/py3/lib/python3.5/site-packages/enforce/nodes.py in preprocess_data(self, validator, data)
    538         try:
--> 539             enforcer = data.__enforcer__
    540         except AttributeError:

AttributeError: 'range' object has no attribute '__enforcer__'

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
<ipython-input-4-5dfdb081f76a> in <module>()
----> 1 add_one(range(10))

~/.venvs/py3/lib/python3.5/site-packages/enforce/decorators.py in universal(wrapped, instance, args, kwargs)
    102 
    103             # First, check argument types (every key not labelled 'return')
--> 104             _args, _kwargs, _ = enforcer.validate_inputs(parameters)
    105 
    106             if instance_method:

~/.venvs/py3/lib/python3.5/site-packages/enforce/enforcers.py in validate_inputs(self, input_data)
     76             if name != 'return':
     77                 argument = binded_arguments.arguments.get(name)
---> 78                 if not self.validator.validate(argument, name):
     79                     break
     80                 binded_arguments.arguments[name] = self.validator.data_out[name]

~/.venvs/py3/lib/python3.5/site-packages/enforce/validator.py in validate(self, data, param_name)
     24         validation_tree = hint_validator.validate(data, self)
     25 
---> 26         validation_result = visit(validation_tree)
     27 
     28         self.data_out[param_name] = self.roots[param_name].data_out

~/.venvs/py3/lib/python3.5/site-packages/enforce/utils.py in visit(generator)
     15             last = stack[-1]
     16             if isinstance(last, typing.Generator):
---> 17                 stack.append(last.send(last_result))
     18                 last_result = None
     19             else:

~/.venvs/py3/lib/python3.5/site-packages/enforce/nodes.py in validate(self, data, validator, force)
     62 
     63         # 1
---> 64         clean_data = self.preprocess_data(validator, data)
     65 
     66         # 2

~/.venvs/py3/lib/python3.5/site-packages/enforce/nodes.py in preprocess_data(self, validator, data)
    539             enforcer = data.__enforcer__
    540         except AttributeError:
--> 541             return GenericProxy(data)
    542         else:
    543             covariant = self.covariant or validator.settings.covariant

~/.venvs/py3/lib/python3.5/site-packages/enforce/enforcers.py in __init__(self, wrapped)
    128             apply_enforcer(self, generic=True)
    129         else:
--> 130             raise TypeError('Only generics can be wrapped in GenericProxy')
    131 
    132     def __call__(self, *args, **kwargs):

TypeError: Only generics can be wrapped in GenericProxy
RussBaz commented 6 years ago

OK, this is caused by a failing check - it tests if the type constraint is a generic. I found a potential cause of recent generic problems in there.

Enforce.py looks for typing.GenericMeta in mro to confirm if a constraint is a generic. I recently checked mro of some generics in python 3.6.2 and it was not available there. Either something has changed and I missed it, or some generics need special processing.

I will need to investigate it more. This is almost the same issues as #51.