Giter Club home page Giter Club logo

enforce's People

Contributors

hwayne avatar russbaz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

enforce's Issues

Incorrect Exception Message

Cool project. Here's a minor issue I ran into:

E enforce.exceptions.RuntimeTypeError:
E The following runtime type errors were encountered:
E Return value was not of type typing.Tuple[str, str, datetime.datetime]. Actual type was <class 'tuple'>

Which is misleading since the issue wasn't with the tuple type, but with the contents of the tuple. The actual message should have been something like:
"Return value was not of type Tuple[str, str, dt]. Actual type was Tuple[str, int, dt].

Code that ran into this issue:

@enforce.runtime_validation
def parse_fn(fn: str) -> Tuple[str, str, dt]:
    # feed-foo_cust-acme_sen-290_sver-1_letime-20161201T041042_uuid-243c5a95d14f449dbc9b4e62b8a241d9.csv.gz
    fn = basename(fn)
    try:
        kvparts  = fn.split('.')[0].split('_')
        cust_name = kvparts[1].split('-')[1]
        sensor_id = int(kvparts[2].split('-')[1])
        insert_dt = dt.strptime(kvparts[4].split('-')[1], '%Y%m%dT%H%M%S')
    except IndexError:
        raise ValueError('invalid filename: {}'.format(fn))
    else:
        return cust_name, sensor_id, insert_dt

Sequence type and nested types are not handled correctly.

Sequences do not trigger an exception if their contents are of incorrect type, i.e. any sequence is always a sequence and content type checks are ignored. This is a limitations of a current type parser and validator. This should be resolved when a new parser ( #2 ) is implemented.

Enable `runtime_validation` package/module-wide

It would be very practical to be able to enforce PEP484 type validation package/module-wide, typically when the package/module developer thinks it should be enforced for his package/module to work properly.

In this case he could write something in his __init__ file (for packages), or at the end of his python file (for modules), to automatically decorate all symbols of the package/module with PEP484 type validation.

Add Recursion Limit to Parser

Currently, if one were to enforce typing on a function that took a list as input, and the list was especially nested and/or large, it would iterate/recurse over every object asserting that the typing matches.

For example:

import enforce
@enforce.runtime_validation
def foo(bar: List[int]) -> int:
    return bar[0]
foo(range(10000000))

An easy workaround (currently) is to just define the input type as Any, which prevents further iteration

import enforce
@enforce.runtime_validation
def foo(bar: Any) -> int:
    return bar[0]
foo(range(10000000))

This has the downside of losing the type annotations. I propose the following syntax:

import enforce
@enforce.runtime_validation(recursion_limit=0)   # not necessarily the name
def foo(bar: List[int]) -> int:
    return bar[0]
foo(range(10000000))

Setting the recursion_limit to zero would mean that just the top level nodes are parsed, while one would mean that the top level nodes and each of their children would be parsed, etc. In the above example, this would mean that we would enforce the type of bar as a List, but we wouldn't check the types of its elements (basically turning it from List[int] to List[Any]).

This change would have huge benefits for time and space complexity, as currently large arrays are completely parsed.

This change would also require a minor rework of the core decorator in order to use arguments.

License file

The metadata on the [https://pypi.python.org/pypi/enforce](pypi page) says that the enforce package is licensed under MIT license - but the license requires that there should be a copy of its text included in the repository.
It would be great if such file was included.

Publicity

Lots of talk going on here about typing. Potentially now is a really good time to showcase what we've got.

How close would you say we are to production release? It's my current understanding that we cover probably 90% of typing use cases, with some uncommon cases not quite fully supported.

Errors with Threading

I'm running into odd issues with threading in which it appears that arguements and results are being replaced with None.

It runs fine with enforce with a single worker, and runs with with multiple workers and enforce disabled. But consistently breaks with multiple workers and enforce enabled.

Here's the code:

#!/usr/bin/env python3.5
import os, sys, concurrent.futures, enforce
from typing import Union, Any, List, Tuple

try:
    WORKERS = int(sys.argv[1])
    ENFORCE_ENABLED = True if sys.argv[2] == 'true' else False
    ASSERT_ENABLED = True if sys.argv[3] == 'true' else False
except:
    raise ValueError('invalid args, should be: \n'
                     ' arg 1 (int) = number of workers,\n'
                     ' arg 2 (true or false) = enforce enabling,\n'
                     ' arg 3 (true or false) = assertions enabling')

enforce.config({'enabled': ENFORCE_ENABLED})

def main():
    for i in range(100):
        test_runner(100)
        print('.', end='', flush=True)
    print('test success')

def test_runner(widget_number):

    widgets = [ x for x in range(widget_number) ]
    with concurrent.futures.ThreadPoolExecutor(max_workers=WORKERS) as executor:
        for score in executor.map(test_one_widget,
                                    widgets):
            if ASSERT_ENABLED:
                assert isinstance(score, int)

@enforce.runtime_validation
def test_one_widget(widget_id: int) -> int:
    score = widget_inspector(widget_id, a='foo', b=4, c='bar')
    return  score

@enforce.runtime_validation
def widget_inspector(widget_id: int, a: str, b: int, c: str) -> int:
    if ASSERT_ENABLED:
        assert isinstance(widget_id, int)
        assert isinstance(a, str)
        assert isinstance(b, int)
        assert isinstance(c, str)
    return b

if __name__ == '__main__':
    main()

Test results with 1 worker or multiple workers but enforce off:

(enforce):~/Envs/enforce/enforcechk$ ./threading_test.py   1 false true
....................................................................................................test success
(enforce):~/Envs/enforce/enforcechk$ ./threading_test.py   1 true true                                                                                                                                           
....................................................................................................test success
(enforce):~/Envs/enforce/enforcechk$ ./threading_test.py   40 false true
....................................................................................................test success

Test results with multiple workers and enforce on:

(enforce) kenfar@darth560:~/Envs/enforce/enforcechk$ ./threading_test.py   40 true true                         
..Traceback (most recent call last):
  File "./threading_test.py", line 47, in <module>
    main()
  File "./threading_test.py", line 19, in main
    test_runner(100)
  File "./threading_test.py", line 28, in test_runner
    widgets):
  File "/usr/lib/python3.5/concurrent/futures/_base.py", line 556, in result_iterator
    yield future.result()
  File "/usr/lib/python3.5/concurrent/futures/_base.py", line 398, in result
    return self.__get_result()
  File "/usr/lib/python3.5/concurrent/futures/_base.py", line 357, in __get_result
    raise self._exception
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/home/kenfar/Envs/enforce/lib/python3.5/site-packages/enforce/decorators.py", line 105, in universal
    result = wrapped(*_args, **_kwargs)
  File "./threading_test.py", line 34, in test_one_widget
    score = widget_inspector(widget_id, a='foo', b=4, c='bar')
  File "/home/kenfar/Envs/enforce/lib/python3.5/site-packages/enforce/decorators.py", line 105, in universal
    result = wrapped(*_args, **_kwargs)
  File "./threading_test.py", line 42, in widget_inspector
    assert isinstance(b, int)
AssertionError

(enforce) kenfar@darth560:~/Envs/enforce/enforcechk$ ./threading_test.py   40 true true
......Traceback (most recent call last):
  File "./threading_test.py", line 47, in <module>
    main()
  File "./threading_test.py", line 19, in main
    test_runner(100)
  File "./threading_test.py", line 28, in test_runner
    widgets):
  File "/usr/lib/python3.5/concurrent/futures/_base.py", line 556, in result_iterator
    yield future.result()
  File "/usr/lib/python3.5/concurrent/futures/_base.py", line 398, in result
    return self.__get_result()
  File "/usr/lib/python3.5/concurrent/futures/_base.py", line 357, in __get_result
    raise self._exception
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/home/kenfar/Envs/enforce/lib/python3.5/site-packages/enforce/decorators.py", line 111, in universal
    return enforcer.validate_outputs(result)
  File "/home/kenfar/Envs/enforce/lib/python3.5/site-packages/enforce/enforcers.py", line 98, in validate_outputs
    raise RuntimeTypeError(exception_text)
enforce.exceptions.RuntimeTypeError: 
  The following runtime type errors were encountered:
        Return value was not of type <class 'int'>. Actual type was NoneType.

(enforce) kenfar@darth560:~/Envs/enforce/enforcechk$ ./threading_test.py   40 true false
..................Traceback (most recent call last):
  File "./threading_test.py", line 47, in <module>
    main()
  File "./threading_test.py", line 19, in main
    test_runner(100)
  File "./threading_test.py", line 28, in test_runner
    widgets):
  File "/usr/lib/python3.5/concurrent/futures/_base.py", line 556, in result_iterator
    yield future.result()
  File "/usr/lib/python3.5/concurrent/futures/_base.py", line 398, in result
    return self.__get_result()
  File "/usr/lib/python3.5/concurrent/futures/_base.py", line 357, in __get_result
    raise self._exception
  File "/usr/lib/python3.5/concurrent/futures/thread.py", line 55, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/home/kenfar/Envs/enforce/lib/python3.5/site-packages/enforce/decorators.py", line 105, in universal
    result = wrapped(*_args, **_kwargs)
  File "./threading_test.py", line 34, in test_one_widget
    score = widget_inspector(widget_id, a='foo', b=4, c='bar')
  File "/home/kenfar/Envs/enforce/lib/python3.5/site-packages/enforce/decorators.py", line 97, in universal
    _args, _kwargs, _ = enforcer.validate_inputs(parameters)
  File "/home/kenfar/Envs/enforce/lib/python3.5/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 'widget_id' was not of type <class 'int'>. Actual type was NoneType.

The results show failures in various areas running the same code. Sometimes caught by enforce, othertimes caught by assertions.

runtime_validation yields a false positive when inheritance is involved

Run the following code:

import enforce


class Foo:
    @staticmethod
    def fun() -> None:
        print(42)


class Bar(Foo):
    @staticmethod
    def fun() -> None:
        print(84)


@enforce.runtime_validation
def meta(x: Foo) -> None:
    x.fun()


meta(Foo())
meta(Bar())

This errors out with:

42
Traceback (most recent call last):
  File "/tmp/test.py", line 19, in <module>
    meta(Bar())
  File "/home/marcin/venv3/golem/lib/python3.6/site-packages/enforce/decorators.py", line 104, in universal
    _args, _kwargs, _ = enforcer.validate_inputs(parameters)
  File "/home/marcin/venv3/golem/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 'x' was not of type <class '__main__.Foo'>. Actual type was Bar.

The code is validated properly by mypy --strict.

enforce is not transparent for Callable types - EnforceProxy

Hey there, I found this nice bug while developing valid8

With enforce 0.3.4,

@runtime_validation
class Foo:
    def __init__(self, fun: Callable):
        self.fun = fun

a = Foo(next)
assert a.fun.__name__ == 'next'  # => raises an AssertionError

Indeed a.fun does not hold your callable, but a proxy to it. This could be ok, but that proxy does not act as a transparent decorator. I would recommend using the very nice decorator library, which I use in valid8 and autoclass

Better error messages for some containers

Hi there.

You seems to be quite reactive (#39) so I'll bother you with a nitpick as I was testing a couple of type-checking libraries the other day.

Here is the simple script:

from typing import Tuple, Dict
from enforce import runtime_validation

@runtime_validation
def example1(param: Tuple[int, int, int]) -> None:
    return None

@runtime_validation
def example2(param: Dict[str, int]) -> None:
    return None

example1((42, 42)) # Argument 'param' was not of type typing.Tuple[int, int, int]. Actual type was tuple.
example2({42: 42}) # Argument 'param' was not of type typing.Dict[str, int]. Actual type was typing.Dict[int, int].

As you can see with the comments, for example2/dict the error message is quite clear.
For example1/tuple the message lacks a bit of precision.

It could be nice to introspect the passed tuple here but it's not clear for me why nodes.TupleNode.validate_data uses input_type.__name__ as type_name instead of relying on something like get_actual_data_type.

Some typing errors

Hi there, I got some typing errors and I'm not sure where the problems came from, could you give me your thoughts about the following examples?
Feel free to close the issue if there is nothing wrong on enforce's side.

Version used, pipe show enforce:

Name: enforce
Version: 0.3.4
...

Example 1

In this case, there is no problem if the callback is not a bound method.

Code:

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)

This code may raise two different tracebacks, the difference between the two being the type of the function:

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.

The errors go away when the callback type is less strict:

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

Is this the expected behavior?

Example 2

The traceback makes me think this may be an error on enforce's side.

Code:

from typing import Generator
import enforce

@enforce.runtime_validation
def generator() -> Generator[int, None, None]:
    i = 0
    while True:
        yield i
        i += 1

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

Traceback:

Traceback (most recent call last):
  File ".../venv/lib/python3.6/site-packages/enforce/nodes.py", line 539, in preprocess_data
    enforcer = data.__enforcer__
AttributeError: 'generator' object has no attribute '__enforcer__'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "enforce_examples.py", line 11, in <module>
    g = generator()
  File ".../venv/lib/python3.6/site-packages/enforce/decorators.py", line 118, in universal
    return enforcer.validate_outputs(result)
  File ".../venv/lib/python3.6/site-packages/enforce/enforcers.py", line 96, in validate_outputs
    if not self.validator.validate(output_data, 'return'):
  File ".../venv/lib/python3.6/site-packages/enforce/validator.py", line 26, in validate
    validation_result = visit(validation_tree)
  File ".../venv/lib/python3.6/site-packages/enforce/utils.py", line 17, in visit
    stack.append(last.send(last_result))
  File ".../venv/lib/python3.6/site-packages/enforce/nodes.py", line 64, in validate
    clean_data = self.preprocess_data(validator, data)
  File ".../venv/lib/python3.6/site-packages/enforce/nodes.py", line 541, in preprocess_data
    return GenericProxy(data)
  File ".../venv/lib/python3.6/site-packages/enforce/enforcers.py", line 130, in __init__
    raise TypeError('Only generics can be wrapped in GenericProxy')
TypeError: Only generics can be wrapped in GenericProxy

There is a similar problem when a function returns a Coroutine.

TypeError: 'NoneType' object is not iterable when using Optional[Tuple[str, str]]

from typing import Optional, Tuple
from enforce import runtime_validation

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

foo()

Yields TypeError: 'NoneType' object is not iterable. Note that the result is the same when the argument has no default value and None is explicitly passed.

Trying to dig-in the source it seems that at some point the None value is propagated to the Tuple node, which might be ok. I will provide a pull request with a fix.

Profiling

What are the performance considerations for users wishing to use this in a production environment?

Covariant mode may traceback

Version used:

pip show enforce
Name: enforce
Version: 0.3.3
...

Simplest reproducible example:

from typing import Optional
from enforce import config, runtime_validation

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

@runtime_validation
def example() -> Optional[str]:
    return None

example()

Note that if I comment out the config call, it works as expected.
Of course, the covariant mode isn't useful here, but I do need it in a larger script.

The traceback itself, for completeness:

Traceback (most recent call last):
  File "example.py", line 10, in <module>
    example()
  File ".../venv/lib/python3.6/site-packages/enforce/decorators.py", line 118, in universal
    return enforcer.validate_outputs(result)
  File ".../venv/lib/python3.6/site-packages/enforce/enforcers.py", line 96, in validate_outputs
    if not self.validator.validate(output_data, 'return'):
  File ".../venv/lib/python3.6/site-packages/enforce/validator.py", line 26, in validate
    validation_result = visit(validation_tree)
  File ".../venv/lib/python3.6/site-packages/enforce/utils.py", line 17, in visit
    stack.append(last.send(last_result))
  File ".../venv/lib/python3.6/site-packages/enforce/nodes.py", line 54, in validate
    self_validation_result = self.validate_data(validator, clean_data, force)
  File ".../venv/lib/python3.6/site-packages/enforce/nodes.py", line 210, in validate_data
    result = is_type_of_type(input_type, expected_data_type, covariant=covariant, contravariant=contravariant)
  File ".../venv/lib/python3.6/site-packages/enforce/types.py", line 206, in is_type_of_type
    subclass_check = perform_subclasscheck(data, data_type, covariant, contravariant)
  File ".../venv/lib/python3.6/site-packages/enforce/types.py", line 234, in perform_subclasscheck
    result = result or data_type.__subclasscheck__(reversed_data)
TypeError: issubclass() arg 1 must be a class

list index out of range when validating list in list

test.py:

from typing import Union, List
from enforce.decorators import runtime_validation

I18ableMessage = List[str]
I18ableMessages = Union[str,List[I18ableMessage]]

@runtime_validation
def test(msg: I18ableMessages):
    print(msg)
    
test([["a","b"],["x","y"]])

python3 test.py

Traceback (most recent call last):
  File "test.py", line 11, in <module>
    test([["a","b"],["x","y"]])
  File "/usr/local/lib/python3.5/dist-packages/enforce/decorators.py", line 109, in universal
    _args, _kwargs, _ = enforcer.validate_inputs(parameters)
  File "/usr/local/lib/python3.5/dist-packages/enforce/enforcers.py", line 77, in validate_inputs
    if not self.validator.validate(argument, name):
  File "/usr/local/lib/python3.5/dist-packages/enforce/validator.py", line 26, in validate
    result = visit(validation_tree)
  File "/usr/local/lib/python3.5/dist-packages/enforce/utils.py", line 17, in visit
    stack.append(last.send(last_result))
  File "/usr/local/lib/python3.5/dist-packages/enforce/nodes.py", line 84, in validate_children
    result = yield child.validate(propagated_data[i], validator, self.type_var)
IndexError: list index out of range

Handling of generator/iterables/coroutines/...

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

Seen with enforce 0.3.4.

Example code with a generator:

from typing import Generator
import enforce

@enforce.runtime_validation
def generator() -> Generator[int, None, None]:
    i = 0
    while True:
        yield i
        i += 1

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

An exception will occur in enforce:

Traceback (most recent call last):
  File ".../venv/lib/python3.6/site-packages/enforce/nodes.py", line 539, in preprocess_data
    enforcer = data.__enforcer__
AttributeError: 'generator' object has no attribute '__enforcer__'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "enforce_examples.py", line 11, in <module>
    g = generator()
  File ".../venv/lib/python3.6/site-packages/enforce/decorators.py", line 118, in universal
    return enforcer.validate_outputs(result)
  File ".../venv/lib/python3.6/site-packages/enforce/enforcers.py", line 96, in validate_outputs
    if not self.validator.validate(output_data, 'return'):
  File ".../venv/lib/python3.6/site-packages/enforce/validator.py", line 26, in validate
    validation_result = visit(validation_tree)
  File ".../venv/lib/python3.6/site-packages/enforce/utils.py", line 17, in visit
    stack.append(last.send(last_result))
  File ".../venv/lib/python3.6/site-packages/enforce/nodes.py", line 64, in validate
    clean_data = self.preprocess_data(validator, data)
  File ".../venv/lib/python3.6/site-packages/enforce/nodes.py", line 541, in preprocess_data
    return GenericProxy(data)
  File ".../venv/lib/python3.6/site-packages/enforce/enforcers.py", line 130, in __init__
    raise TypeError('Only generics can be wrapped in GenericProxy')
TypeError: Only generics can be wrapped in GenericProxy

There is a similar problem when a function returns a Coroutine.

This seems normal for now as there isn't any real support for these objects.

enforce on static methods

I'm attempting to use enforce over a class. However, one of the methods in this class is static, and marked so by:

@staticmethod

When attempting to run, i get the following:

In [7]: Keyboard._parse_modifier(2)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-2977a0e8d3a6> in <module>()
----> 1 Keyboard._parse_modifier(2)

~/.virtualenvs/usb_pcap/lib/python3.5/site-packages/enforce/decorators.py in universal(wrapped, instance, args, kwargs)
     83         """
     84         with RunLock:
---> 85             enforcer = wrapped.__enforcer__
     86             skip = False
     87

AttributeError: 'functools.partial' object has no attribute '__enforcer__'

Same error occurs if i remove my type hints.

Typing error

Any idea what that might mean?
argument 'pinfo' was not of type <class 'sos.PathInfo'>. Actual type was untyped PathInfo
We are in sos.py with a class PathInfo.

Python 3.5.0-3.5.2 no longer supported?

The question says it all.

I will try fixing the backward compatibility with those versions, but I think I should drop it as soon as it starts giving me too much headache.

Some notes on runtime type safety of Generics

Generics and Type Variables

Some Definitions

A 'Generic' is any mutable object which is a subclass of typing.Generic.

Parametrised Generic is a Generic which accepts one or more type parameters. These parameters must be instances of typing.TypeVar.

Restricted Generic is a Parametrised Generic class that specifies which types it accepts and at least one of the required types must be more specific than typing.Any.

Typed Generic is a Generic which has runtime type checking applied to it.

Type Safe Method/Function is any method/function with a runtime type checking applied to it.

A Proxy object is an object which wraps another mutable object and proxies all its attribute accesses to the wrapped object. If a proxy was defined with an attribute named identically to the wrapped one, then the proxy attribute will be accessed instead. In all other cases it should mimic the wrapped object as much as possible.

A Generic Proxy is a proxy object for a type safe access to the type un-safe Generic.

Safe Generic is either an instance of a typed generic or a generic proxy.

Enforcing generic type safety means ensuring that only safe generics are acceptable. If any non-typed restricted generics are encountered, then they must be silently wrapped in a generic proxy.

Methods, lambdas and functions can be collectively called callables.

Type Safe Callables (or just safe callables) are callables with a runtime type checking applied.

Basic Rules of Type Safety and Generics

Only callables can have a type safety checking.

If a safe callable expects a restricted generic anywhere in its input, then a generic type safety must be enforced on such input.

If a safe callable expects to return a restricted generic anywhere in its output data, then it also must enforce a generic type safety on its output.

TypeVars are invariant by default (PEP 484).

Parameters of Generics are always TypeVars (PEP 484).

Immutable types must always be treated invariantly.

TypeVars can be covariant, contravariant, both or neither (PEP 484).

Mutable types are covariant unless they are parametrised generics.

Parametrised generics are evaluated against their own type invariantly and against their type parameters according to their definition.

Markdown or reStructuredText for Readme?

PyPi does not support markdown descriptions.

There are two options:

  1. Replace md Readme with rst one
  2. Add some kind of build step to compile ms to rst on wheel build

Which one to choose . . .

What to do about __subclasshook__?

What should we do about this? I am a bit unsure where to fit this in the current model.

Should this hook replace the entire inheritance check? Should this work only in covariant mode? Or should this be used for all cases other than invariant and expect users to know about this?

Run on any function automatically

Hey, I just discovered this via pycoder's weekly ๐Ÿ˜„

It'd be nice to have some way to run the type checks automatically on any function call. I'm sure this will be slow as hell, but it'd be nice to run the tests with it once in a while, to discover things a static checker might not discover.

I'm not sure what the best way to implement this would be - sys.settrace and bytecode rewriting come to mind, but I'm not sure how hard this is...

Forward References

Do you support forward references? I'm getting this behavior:

import enforce
import typing

MyType = typing.List[typing.Union[int, 'MyType']]

@enforce.runtime_validation()
def foo(x: MyType):
  pass

foo([1, 2])    # Good
foo([1, [2]])  # RuntimeTypeError

The error states: Argument 'x' was not of type typing.List<~T>[typing.Union[int, _ForwardRef('MyType')]]. Actual type was typing.List[int, typing.List].

Are forward references not supported, is this a bug, or am I doing something wrong? Thanks :)

Multiprocessing issues

Continuation of Issue #36 - threading issues.

So, does anyone know how to make it work with multiprocessing? I cannot grasp where exactly the issue is arising. Is it because of 'wrapt'?

Set[] return type has errors

Working with 0.3.1, installed from PyPI

Example code:

import enforce

from typing import Set

@enforce.runtime_validation
def test() -> Set[int]:
    return set([1,2,3])

test()

Error:

Traceback (most recent call last):
  File "/tmp/x/py35/venv/lib/python3.5/site-packages/enforce/nodes.py", line 403, in preprocess_data
    enforcer = data.__enforcer__
AttributeError: 'list' object has no attribute '__enforcer__'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 9, in 
    test()
  File "/tmp/x/py35/venv/lib/python3.5/site-packages/enforce/decorators.py", line 123, in universal
    return enforcer.validate_outputs(result)
  File "/tmp/x/py35/venv/lib/python3.5/site-packages/enforce/enforcers.py", line 95, in validate_outputs
    if not self.validator.validate(output_data, 'return'):
  File "/tmp/x/py35/venv/lib/python3.5/site-packages/enforce/validator.py", line 26, in validate
    result = visit(validation_tree)
  File "/tmp/x/py35/venv/lib/python3.5/site-packages/enforce/utils.py", line 17, in visit
    stack.append(last.send(last_result))
  File "/tmp/x/py35/venv/lib/python3.5/site-packages/enforce/nodes.py", line 45, in validate
    clean_data = self.preprocess_data(validator, data)
  File "/tmp/x/py35/venv/lib/python3.5/site-packages/enforce/nodes.py", line 405, in preprocess_data
    return GenericProxy(data)
  File "/tmp/x/py35/venv/lib/python3.5/site-packages/enforce/enforcers.py", line 129, in __init__
    raise TypeError('Only generics can be wrapped in GenericProxy')
TypeError: Only generics can be wrapped in GenericProxy

If we change Set to List then it works fine though:

import enforce

from typing import List

@enforce.runtime_validation
def test() -> List[int]:
    return [1,2,3]

test()

Config Parameter "enable" is acting in the wrong scope

See the wiki page: https://github.com/RussBaz/enforce/wiki/Configuration

In a nutshell, the built in functionality with wrapt lets us disable these checks and skip over them, however since this happens at the bytecode level it needs to occur right as the file is read.

This behavior isn't ideal, as we would like to be able to dynamically disable functions based on possibly arguments, or other things. This currently isn't possible...

This is a low priority issue, however we still need to keep track of it. (I'll try to get to it soon, but if someone beats me to it that would also be great.)

Any should match against Lists

On dev branch.

from typing import Any, List
import enforce

@enforce.runtime_validation
def foo(a: Any) -> Any:
    return 10

foo([10,20])

will result in

enforce.exceptions.RuntimeTypeError: 
  The following runtime type errors were encountered:
       Argument 'a' was not of type typing.Any. Actual type was typing.List[int].

Any should match against a List[int], I believe.

Global switch for type checking mode

All the types are currently checked invariantly (i.e. it has to be the same as specified or it fails otherwise). This sometimes might not what our users expect. I think we should implement a global switch to change the type checking mode.

This switch should be able to specify whether type checking is: invariant, covariant, contravariant or bivariant.

Add Configuration

It would be nice to be able to configure some basic settings globally, rather than having to set them on a per-function basis. This would extend #7 and #4.

Possible syntax is

# set recursion limit to 10
# do not disable
enforce.config(recursion_limit=10, disable=False)

This would allow to build programs where type checking can be enabled through command line arguments.

I'm not sure how the code to implement this would work, but I can start looking into it. It might not also hurt to have "tagged" functions so that you can configure groups of functions at a time. For example

import enforce
from enforce import runtime_validation

@runtime_validation(group='foo')
def foo1(a:str)->str:
    return a
@runtime_validation(group='foo')
def foo2(a:str)->str:
    return a
@runtime_validation(group='bar')
def bar(a:str)->str:
    return a

# Disable all "foo" group checks
enforce.config(disable=False)
enforce.group('foo', disable=True)

I'm open to suggestions on syntax, especially for the function group config.

Feature Request: Implicit Typecasting

I think a great feature would be the option to attempt a typecast if @enforce.runtime_validation fails, either as a separate decorator @enforce.runtime_typecasting or as an optional parameter @enforce.runtime_validation(coerce=True)

A simple example might be

@enforce.runtime_typecasting
def elongator(s: str):
    return s * 10
...
elongator(5)
# returns '5555555555'

For the simple primitives, this could be a simple task. For more complicated objects, an attempt to pass the argument itself to the required type as a single arg, as *args, and as **kwargs could be attempted in sequence.

class SimpleClass:
    def __init__(self, x: int):
        self.value = x

@enforce.runtime_typecasting
def SillySimpleClassFactory(x: SimpleClass):
    return x
...
SillySimpleClassFactory(10)
# returns SimpleClass(10)
class ComplicatedClass:
    def __init__(self, first_arg, second_arg, third_arg):
        ...
...
@enforce.runtime_typecasting
def ComplicatedClassFactory(x: ComplicatedClass):
    return x

ComplicatedClassFactory((1, 2, 3))
# returns ComplicatedClass(1, 2, 3)

ComplicatedClassFactory({'first_arg' : 4, 'third_arg' : 6, 'second_arg' : 5})
# returns ComplicatedClass(4, 5, 6)

I imagine Callable wouldn't be properly supported. Probably others.

I would be happy to contribute this addition myself in a month or two.

Usage with `typing.NamedTuple`

First, thanks for this awesome project!

Can this project be used with typing.NamedTuple? I tried this but I couldn't get it to work.

import enforce
import typing
MyNamedTuple = typing.NamedTuple('MyNamedTuple', [('my_int', int)])
MyNamedTuple = enforce.runtime_validation(MyNamedTuple)
# I assumed enforce.runtime_validation would work on any callable
MyNamedTuple(5)  # For me it crashes here already on a "missing a required argument: 'my_int'"
MyNamedTuple(my_int=5) # Now it gives a "missing a required argument: '_cls'", I give up
MyNamedTuple(my_int='hello')  # I never got here

I use version typing=0.3.1 and python=3.5.2.

Problems with Python 3.6

This works fine with Python 3.5:

import enforce

from typing import List

@enforce.runtime_validation
def test() -> List[int]:
    return [1,2,3]

test()

But under Python 3.6 it has this error:

  File "test.py", line 1, in 
    import enforce
  File "/tmp/x/py36/venv/lib/python3.6/site-packages/enforce/__init__.py", line 1, in 
    from .decorators import runtime_validation
  File "/tmp/x/py36/venv/lib/python3.6/site-packages/enforce/decorators.py", line 9, in 
    from .enforcers import apply_enforcer, Parameters, GenericProxy
  File "/tmp/x/py36/venv/lib/python3.6/site-packages/enforce/enforcers.py", line 7, in 
    from .types import EnhancedTypeVar, is_type_of_type
  File "/tmp/x/py36/venv/lib/python3.6/site-packages/enforce/types.py", line 5, in 
    from typing import Optional, Union, UnionMeta, Any, TypeVar, Tuple, Generic
ImportError: cannot import name 'UnionMeta'

Callable's arguments' types may be lost

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

`numbers` ABCs are not supported

I get an NameError when I try to use ABCs from the numbers module, e.g.

from typing import NamedTuple, Text, Tuple
from numbers import Integral

Interval = Tuple[Integral, Integral]
Annotation = NamedTuple("Annotation", [("source", Text), ("start", Integral),
                                       ("end", Integral), ("text", Text),
                                       ("cls", Text)])

Using these annotations in any function gives me:

Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1599, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/Applications/PyCharm.app/Contents/helpers/pydev/pydevd.py", line 1026, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Applications/PyCharm.app/Contents/helpers/pycharm/docrunner.py", line 297, in <module>
    modules = [loadSource(a[0])]
  File "/Applications/PyCharm.app/Contents/helpers/pycharm/docrunner.py", line 229, in loadSource
    module = imp.load_source(moduleName, fileName)
  File "/Users/ilia/.venvs/py3/lib/python3.5/imp.py", line 172, in load_source
    module = _load(spec)
  File "<frozen importlib._bootstrap>", line 693, in _load
  File "<frozen importlib._bootstrap>", line 673, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 665, in exec_module
  File "<frozen importlib._bootstrap>", line 222, in _call_with_frames_removed
  File "/Users/ilia/OneDrive/GitHub/skoblov-lab/chempred/chempred/chemdner.py", line 52, in <module>
    -> List[Tuple[int, List[Annotation], List[Annotation]]]:
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/decorators.py", line 58, in runtime_validation
    return generate_decorated()
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/decorators.py", line 158, in build_wrapper
    return decorate(wrapped, configuration, None)
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/decorators.py", line 70, in decorate
    data = apply_enforcer(data, parent_root=parent_root, settings=configuration)
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/enforcers.py", line 159, in apply_enforcer
    func.__enforcer__ = generate_new_enforcer(func, generic, parent_root, instance_of, settings)
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/enforcers.py", line 227, in generate_new_enforcer
    validator = init_validator(hints, parent_root)
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/validator.py", line 71, in init_validator
    syntax_tree = visit(root_parser)
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/utils.py", line 17, in visit
    stack.append(last.send(last_result))
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/parsers.py", line 54, in _parse_namedtuple
    new_node = yield nodes.NamedTupleNode(hint)
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/nodes.py", line 390, in __init__
    super().__init__(runtime_validation(data_type), is_sequence=True, is_container=True, **kwargs)
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/decorators.py", line 46, in runtime_validation
    return get_typed_namedtuple(configuration, data, fields, field_types)
  File "/Users/ilia/.venvs/py3/lib/python3.5/site-packages/enforce/decorators.py", line 180, in get_typed_namedtuple
    exec(new_init_template, context)
  File "<string>", line 1, in <module>
NameError: name 'Integral' is not defined

Callables Not Handled Correctly

Callables are not currently working, as referenced in #3.

Thinking about callable objects, I think the best way to handle them is as follows.

Let's say we have some function that takes another function as an argument with correct type hinting syntax.

def foo(func: Callable[[int, int], float], s: str) -> str:
    return s + str(func(5, 5))

I think that the best way to handle this is to also require that the argument passed in func needs to also have complete type annotation.

def func(x: int, y: int) -> float:
    return float(x * y)

With func possibly also checked at runtime depending on whether or not the enforce.runtime_validation decorator is applied. The only thing that the high level function foo checks is that the call signature of func matches the type hinting specified.

How's that sound?

(some?) built-in generics are not supported

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

Disable option

Hmmm. I would like a way to disable these checks globally. For example with a configured variable or by invoking python with the optimize flag (python -o). I don't see any switches in the code. Is this possible?

Enforce throws TypeError when calling instance method with forgotten `self`

I have had this obscure problem, which I would call a bug since one would expect to get a proper error message like Python 3 normally does.

I use

  • Python 3.6.1 and
  • enforce==0.3.4
class A:
    def hello():
        print("Hello, World!")

a = A()
# Traceback (most recent call last):
#   File "example.py", line 13, in <module>
#     a.hello()
#   File "example.py", line 4, in hello
#     print("Hello, " + b.world())
# TypeError: world() takes 0 positional arguments but 1 was given
try:
    a.hello()
except TypeError:
    print("Error caught correctly")


# But now

import enforce

@enforce.runtime_validation
class BadA:
    def hello():
        print("Hello, ")

a = BadA()
a.hello()

will output

Error caught correctly   
Traceback (most recent call last):                
  File "example.py", line 30, in <module>         
    a.hello()            
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/decorators.py", line 104, in universal
    _args, _kwargs, _ = enforcer.validate_inputs(parameters)                                         
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/enforcers.py", line 71, in validate_inputs
    binded_arguments = self.signature.bind(*args, **kwargs)                                          
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/inspect.py", line 2933, in bind
    return args[0]._bind(args[1:], kwargs)        
  File "/usr/local/Cellar/python3/3.6.1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/inspect.py", line 2854, in _bind
    raise TypeError('too many positional arguments') from None                                       
TypeError: too many positional arguments    

Union Fails for Nested Types

In the current iteration of the dev branch, Union type hints are not enforced correctly in certain situations (generally ones where there is nesting involved). The following example does not throw an error.

@runtime_validation
def test_func(x: typing.Union[float, typing.List[str]]) -> int:
    return 5
test_func([1, 2, 3, 4])

I'm working on debugging it as well.

It's impossible to install v 0.3.5 (`dev` branch)

It's caused by import pypandoc in setup.py.
It's external dependency, and to install enforce user must firstly manually install pypandoc and it's dependencies (which are huge, by the way), because pip install git+https://github.com/RussBaz/enforce.git@dev won't run.

Do you really need that? It looks like not really. Goal which you want to achieve may be done better way.

from setuptools import setup
from pathlib import Path

import pypandoc

ROOT = Path('.')
README_PATH = ROOT / 'README.md'

with README_PATH.open(encoding='utf-8') as f:
    LONG_DESCRIPTION = pypandoc.convert_text(f.read(), 'rst', format='md')

setup(
    name='enforce',

    version='0.3.5',

    description='Python 3.5+ library for integration testing and data validation through configurable and optional runtime type hint enforcement.',
    long_description=LONG_DESCRIPTION,
   ...

How to use Generics?

In mypy you can use this

import typing


def returns_dict() -> typing.Mapping:
    return dict()


returns_dict()

And it validates just fine.

But with enforce I get a strange result

import enforce
import typing


@enforce.runtime_validation
def returns_dict() -> typing.Mapping:
    return dict


returns_dict()

Seemingly, the generic type handling is different in enforce.

Traceback (most recent call last):                
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/nodes.py", line 539, in preprocess_data
    enforcer = data.__enforcer__                  
AttributeError: type object 'dict' has no attribute '__enforcer__'                                   

During handling of the above exception, another exception occurred:                                  

Traceback (most recent call last):                
  File "test.py", line 10, in <module>            
    returns_dict()       
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/decorators.py", line 118, in universal
    return enforcer.validate_outputs(result)      
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/enforcers.py", line 96, in validate_outputs
    if not self.validator.validate(output_data, 'return'):                                           
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/validator.py", line 26, in validate
    validation_result = visit(validation_tree)    
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/utils.py", line 17, in visit
    stack.append(last.send(last_result))          
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/nodes.py", line 64, in validate
    clean_data = self.preprocess_data(validator, data)                                               
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/nodes.py", line 541, in preprocess_data
    return GenericProxy(data)                     
  File "/Users/XXX/enforce-test/env/lib/python3.6/site-packages/enforce/enforcers.py", line 130, in __init__
    raise TypeError('Only generics can be wrapped in GenericProxy')                                  
TypeError: Only generics can be wrapped in GenericProxy    

What's the best way to get a similar behavior to mypy?

0.3.0 Release Plan

  • Add support for Generics
  • Refactor settings and add new options (#4, #10, #14, #20)
    • BREAKING CHANGE: updated config syntax - dictionary with configuration options is now passed to a config function. Reset to default functionality was also added.
    • Default type checking mode is set to invariant - but perhaps should be covariant? (issubclass is covariant)
    • Also covariant, contravariant and bivariant modes are available
  • Add support for __subclasscheck__, __subclasshook__ and ABCs (#21, #23)
  • __no_type_check__ is now respected (#26)
  • Fixed tests which were not cleaning up after themselves after changing the global settings (#24)
  • Add updated README
  • Update Wiki page for settings
  • Upload 0.3.0 to PyPI

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.