RussBaz / enforce

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

Correct handling of None in TupleNode + Extended support from typing.List to typing.Iterable #65

Closed smarie closed 6 years ago

smarie commented 6 years ago

Correct handling of None in TupleNode: Fixes #62.

You might wish to add corresponding unit tests:

This should not raise an error anymore:

from typing import Optional, Tuple
from enforce import runtime_validation

@runtime_validation
def foo(arg: Optional[Tuple[str, str]] = None):
    pass

foo()

While this should continue to raise an error as usual:

from typing import Tuple
from enforce import runtime_validation

@runtime_validation
def foo(arg: Tuple[str, str]):
    pass

foo(None)

Extended support from typing.List to typing.Iterable (and Dict to Mapping)

Fixes #52, Fixes #51, fixes #47.

You might wish to include the following basic tests:

from enforce import runtime_validation, config
from enforce.exceptions import RuntimeTypeError
config(dict(mode='covariant'))  # by the way, so sad that this is not the default :)

########## SEQUENCE / ITERABLE / SET / GENERATOR
from typing import Sequence, Iterable, Set, Generator

@runtime_validation
def seq(s: Sequence[str]):
    pass

@runtime_validation
def it(s: Iterable[str]):
    pass

seq(['a'])
# transform this into appropriate test handler
try:
    seq(['r', 1])
    raise Exception('failed!')
except RuntimeTypeError:
    pass

it(['a'])
# transform this into appropriate test handler
try:
    it(['r', 1])
    raise Exception('failed!')
except RuntimeTypeError:
    pass

@runtime_validation
def st(m: Set[int]):
    pass

st({2, 2})
# transform this into appropriate test handler
try:
    st({'r', 1})
    raise Exception('failed!')
except RuntimeTypeError:
    pass

@runtime_validation
def generator() -> Generator[int, None, None]:
    i = 0
    while True:
        if i == 0:
            yield i
        else:
            yield 'a string! it cannot be detected by enforce but thats a bit normal'
        i += 1

g = generator()
print(next(g))
print(next(g))

########## MAPPING
from typing import Mapping

@runtime_validation
def bar(m: Mapping[int, int]):
    pass

bar({2: 2})
# transform this into appropriate test handler
try:
    bar({'r': 1})
    raise Exception('failed!')
except RuntimeTypeError:
    pass

Note: the fix with Iterable can probably fail in some rare cases where users define a custom typing.GenericMeta type inheriting from typing.Iterable BUT having several inner generic arguments (like it is in typing.Mapping, that we currently handle correctly by handling it BEFORE other Iterables). Not sure that this case will happen soon :)

coveralls commented 6 years ago

Coverage Status

Coverage decreased (-0.3%) to 92.622% when pulling 4ae2ea7fa05d9eb88aab8f576b242e72e162d7e9 on smarie:dev into b5a8899a348f1a7a79b34f47109c4e24391d5c08 on RussBaz:dev.

coveralls commented 6 years ago

Coverage Status

Coverage decreased (-0.3%) to 92.622% when pulling 4ae2ea7fa05d9eb88aab8f576b242e72e162d7e9 on smarie:dev into b5a8899a348f1a7a79b34f47109c4e24391d5c08 on RussBaz:dev.