RussBaz / enforce

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

Passing an instance of a derived class instead of the base class raise a type error #37

Closed ghost closed 7 years ago

ghost commented 7 years ago

I have

from enforce import runtime_validation
class A(object):
   pass
class B(A):
   pass
class C(B):
   pass

@runtime_validation
def fct(a: A):
  pass

c= C()
fct(c)

and I get Argument 'a' was not of type <class '__main__.A'>. Actual type was C.

I was expecting this to work (c is an instance of A). Is it a bug or did I miss something?

RussBaz commented 7 years ago

It works as intended.

The default type check is invariant. Therefore, if it requires class A, then it has to be class A. If you want it to accept subclasses of A, then you have to set the type checking mode to covariant.

At the moment you can either set it up globally through the configuration:

from enforce import config

config({'mode': 'covariant'})

Or you can use some TypeVars:

from typing import TypeVar

T = TypeVar('T', bound=A, covariant=True)

@enforce.runtime_validation
def foo(a: T): pass

Hope it helps, and thanks for trying it out!

ghost commented 7 years ago

Thanks!! very quick answer! I think you should put this in the readme. I tried looking at this in the doc and google and didn't find anything (except that mypy would accept my code)

RussBaz commented 7 years ago

Well, it is in the overview (https://github.com/RussBaz/enforce#overview), third bullet point. I think that it works in MyPy because it uses Python's default approach of covariant type checking (issubclass). I personally think it is generally safer to assume everything is invariant by default, though.

Do you think I should describe it in more details in the README?

ghost commented 7 years ago

It may seem obvious when you are used to those precise but quite technical term, but I don't think it will speak to most peoples. In fact I did not do the link at all with my issue. maybe mention that for example a derived instance will not match a base class would help.

RussBaz commented 7 years ago

OK, I will try giving an explicit example of how it works with the next update. Maybe a FAQ section too, we will see. And thanks for pointing it out.