Giter VIP home page Giter VIP logo

pytypes's Introduction

Build Status

pytypes

Welcome to the pytypes project

pytypes is a typing toolbox w.r.t. PEP 484 (PEP 526 on the road map, later also 544 if it gets accepted).

Its main features are currently

  • @typechecked decorator for runtime typechecking with support for stubfiles and type comments
  • @override decorator that asserts existence of a type-compatible parent method
  • @annotations decorator to turn type info from stubfiles or from type comments into __annotations__
  • @typelogged decorator observes function and method calls at runtime and generates stubfiles from acquired type info
  • service functions to apply these decorators module wide or even globally, i.e. runtime wide
  • typechecking can alternatively be done in decorator-free manner (friendlier for debuggers)
  • all the above decorators work smoothly with OOP, i.e. with methods, static methods, class methods and properties, even if classes are nested
  • converter for stubfiles to Python 2.7 compliant form
  • lots of utility functions regarding types, e.g. a Python 2.7 compliant and actually functional implementation of get_type_hints
  • full Python 2.7 support for all these features

An additional future goal will be integration with the Java typing system when running on Jython. Along with this, some generator utilities to produce type-safe Java bindings for Python frameworks are planned.

In wider sense, PEP 484-style type annotations can be used to build type safe interfaces to allow also other programming languages to call into Python code (kind of reverse FFI). In this sense the project name refers to 'ctypes', which provides Python-bindings of C.

Python 2.7, 3.5-3.8

All described features of pytypes were carefully implemented such that they are equally workable on CPython 3.5, 3.6, 2.7 and on Jython 2.7.1 (other interpreters might work as well, but were not yet tested). For Python 2.7, pytypes fully supports type-annotations via type comments. It also supports Python 2.7-style type annotations in Python 3.5-code to allow easier 2.7/3.5 multi-version development. Python 3.7 and 3.8 are mostly supported, but some bugs still need to be fixed.

Why write another runtime typecheck decorator?

There have been earlier approaches for runtime-typechecking. However, most of them predate PEP 484 or lack some crucial features like support of Python 2.7 or support of stubfiles. Also, none of them features a typechecking override decorator. There were separate approaches for override decorators, but these usually don't consider PEP 484 at all. So we decided that it's time for a new runtime typechecking framework, designed to support PEP 484 from the roots, including its extensive features like (Python 2.7-style-)type comments and stub files.

Quick manual

Typechecking

pytypes provides a rich set of utilities for runtime typechecking.

@typechecked decorator

Decorator applicable to functions, methods, properties and classes. Asserts compatibility of runtime argument and return values of all targeted functions and methods w.r.t. PEP 484-style type annotations of these functions and methods. This supports stubfiles and type comments and is thus workable on Python 2.7.

Disabling typechecking

Running Python with the '-O' flag, which also disables assert statements, turns off typechecking completely. Alternatively, one can modify the flag pytypes.checking_enabled.

Note that this must be done right after import of pytypes, because it affects the way how @typechecked decorator works. For modules that were imported with this flag disabled, typechecking cannot be turned on later on within the same runtime.

Usage Python 2

from pytypes import typechecked

@typechecked
def some_function(a, b, c):
    # type: (int, str, List[Union[str, float]]) -> int
    return a+len(b)+len(c)

Usage Python 3

from pytypes import typechecked

@typechecked
def some_function(a: int, b: str, c: List[Union[str, float]]) -> int:
    return a+len(b)+len(c)

Overriding methods in type-safe manner

The decorators in this section allow type-safe method overriding.

@override decorator

Decorator applicable to methods only. For a version applicable also to classes or modules use auto_override. Asserts that for the decorated method a parent method exists in its mro. If both the decorated method and its parent method are type annotated, the decorator additionally asserts compatibility of the annotated types. Note that the return type is checked in contravariant manner. A successful check guarantees that the child method can always be used in places that support the parent method's signature. Use pytypes.check_override_at_runtime and pytypes.check_override_at_class_definition_time to control whether checks happen at class definition time or at "actual runtime".

The following rules apply for override checking:

  • a parent method must exist
  • the parent method must have call-compatible signature (e.g. same number of args)
  • arg types of parent method must be more or equal specific than arg types of child
  • return type behaves contravariant - parent method must have less or equal specific return type than child

Usage Example

from pytypes import override

class some_baseclass():
    def some_method1(self, a: int) -> None: ...
    def some_method2(self, a: int) -> None: ...
    def some_method3(self, a: int) -> None: ...
    def some_method4(self) -> int: ...

class some_subclass(some_baseclass):
    @override
    def some_method1(self, a: float) -> None: ...

    @override
    def some_method2(self, a: str) -> None: ...

    @override
    def some_metd3(self, a: int) -> None: ...

    @override
    def some_method4(self) -> float: ...
  • some_method1: override check passes
  • some_method2: override check fails because type is not compatible
  • some_method3: override check fails because of typo in method name
  • some_method4: override check fails because return type must be more or equal specific than parent

@auto_override decorator

Decorator applicable to methods and classes. Works like override decorator on type annotated methods that actually have a type annotated parent method. Has no effect on methods that do not override anything. In contrast to plain override decorator, auto_override can be applied easily on every method in a class or module. In contrast to explicit override decorator, auto_override is not suitable to detect typos in spelling of a child method's name. It is only useful to assert compatibility of type information (note that return type is contravariant). Use pytypes.check_override_at_runtime and pytypes.check_override_at_class_definition_time to control whether checks happen at class definition time or at "actual runtime".

The following rules apply, if a parent method exists:

  • the parent method must have call-compatible signature (e.g. same number of args)
  • arg types of parent method must be more or equal specific than arg types of child
  • return type behaves contravariant - parent method must have less or equal specific return type than child

Compared to ordinary override decorator, the rule “a parent method must exist” is not applied here. If no parent method exists, auto_override silently passes.

Provide info from type comments and stubfiles as __annotations__ for other tools

@annotations decorator

Decorator applicable to functions, methods, properties and classes. Methods with type comment will have type hints parsed from that string and get them attached as __annotations__ attribute. Methods with either a type comment or ordinary type annotations in a stubfile will get that information attached as __annotations__ attribute (also a relevant use case in Python 3). Behavior in case of collision with previously (manually) attached __annotations__ can be controlled using the flags pytypes.annotations_override_typestring and pytypes.annotations_from_typestring.

Type logging

@typelogged decorator

Decorator applicable to functions, methods, properties and classes. It observes function and method calls at runtime and can generate stubfiles from acquired type info.

Disabling typelogging

One can disable typelogging via the flag pytypes.typelogging_enabled.

Note that this must be done right after import of pytypes, because it affects the way how @typelogged decorator works. For modules that were imported with this flag disabled, typelogging cannot be turned on later on within the same runtime.

Usage example with decorator

Assume you run a file ./script.py like this:

from pytypes import typelogged

@typelogged
def logtest(a, b, c=7, *var, **kw): return 7, a, b

@typelogged
class logtest_class(object):
    def logmeth(self, b): return 2*b

    @classmethod
    def logmeth_cls(cls, c): return len(c)

    @staticmethod
    def logmeth_static(c): return len(c)

    @property
    def log_prop(self): return self._log_prop

    @log_prop.setter
    def log_prop(self, val): self._log_prop = val

logtest(3, 2, 5, 6, 7, 3.1, y=3.2, x=9)
logtest(3.5, 7.3, 5, 6, 7, 3.1, y=3.2, x=9)
logtest('abc', 7.3, 5, 6, 7, 3.1, y=2, x=9)
lcs = logtest_class()
lcs.log_prop = (7.8, 'log')
lcs.log_prop
logtest_class.logmeth_cls('hijk')
logtest_class.logmeth_static(range(3))

pytypes.dump_cache()

Usage example with profiler

Alternatively you can use the TypeLogger profiler:

from pytypes import TypeLogger

def logtest(a, b, c=7, *var, **kw): return 7, a, b

class logtest_class(object):
    def logmeth(self, b): return 2*b

    @classmethod
    def logmeth_cls(cls, c): return len(c)

    @staticmethod
    def logmeth_static(c): return len(c)

    @property
    def log_prop(self): return self._log_prop

    @log_prop.setter
    def log_prop(self, val): self._log_prop = val

with TypeLogger():
    logtest(3, 2, 5, 6, 7, 3.1, y=3.2, x=9)
    logtest(3.5, 7.3, 5, 6, 7, 3.1, y=3.2, x=9)
    logtest('abc', 7.3, 5, 6, 7, 3.1, y=2, x=9)
    lcs = logtest_class()
    lcs.log_prop = (7.8, 'log')
    lcs.log_prop
    logtest_class.logmeth_cls('hijk')
    logtest_class.logmeth_static(range(3))

Note that this will produce more stubs, i.e. also for indirectly used modules, because the profiler will handle every function call. To scope a specific module at a time use pytypes.typelogged on that module or its name. This should be called on a module after it is fully loaded. To use it inside the scoped module (e.g. for __main__) apply it right after all classes and functions are defined.

Output

Any of the examples above will create the following file in ./typelogger_output:

script.pyi:

from typing import Tuple, Union

def logtest(a: Union[float, str], b: float, c: int, *var: float, **kw: Union[float, int]) -> Union[Tuple[int, float, float], Tuple[int, str, float]]: ...

class logtest_class(object):
    def logmeth(self, b: int) -> int: ...

    @classmethod
    def logmeth_cls(cls, c: str) -> int: ...

    @staticmethod
    def logmeth_static(c: range) -> int: ...

    @property
    def log_prop(self) -> Tuple[float, str]: ...

    @log_prop.setter
    def log_prop(self, val: Tuple[float, str]) -> None: ...

Use pytypes.dump_cache(python2=True) to produce a Python 2.7 compliant stubfile.

Writing typelog at exit

By default, pytypes performs pytypes.dump_cache() at exit, i.e. writes typelog as a Python 3 style stubfile. Use pytypes.dump_typelog_at_exit to control this behavior. Use pytypes.dump_typelog_at_exit_python2 to write typelog as a Python 2 style stubfile.

Global mode and module wide mode

Note that global mode is experimental.

The pytypes decorators @typechecked, @auto_override, @annotations and @typelogged can be applied module wide by explicitly calling them on a module object or a module name contained in sys.modules. In such a case, the decorator is applied to all functions and classes in that module and recursively to all methods, properties and inner classes too.

Warning: If A decorator is applied to a partly imported module, only functions and classes that were already defined are affected. After the module imported completely, the decorator is applied to the remaining functions and classes. In the meantime, internal code of that module can circumvent the decorator, e.g. can make module-internal calls that are not typechecked.

Global mode via profilers

The pytypes decorators @typechecked and @typelogged have corresponding profiler implementations TypeChecker and TypeLogger. You can conveniently install them globally via enable_global_typechecked_profiler() and enable_global_typelogged_profiler().

Alternatively you can apply them in a with-context:

from pytypes import TypeChecker

def agnt_test(v):
    # type: (str) -> int
    return 67

with TypeChecker():
    agnt_test(12)

One glitch is to consider in case you want to catch TypeCheckError (i.e. ReturnTypeError or InputTypeError as well) and continue execution afterwards. The TypeChecker would be suspended unless you call restore_profiler, e.g.:

from pytypes import TypeChecker, restore_profiler

def agnt_test(v):
    # type: (str) -> int
    return 67

with TypeChecker():
    try:
        agnt_test(12)
    except TypeCheckError:
        restore_profiler()
        # handle error....

Note that the call to restore_profiler must be performed by the thread that raised the error.

Alternatively you can enable pytypes.warning_mode = True to raise warnings rather than errors. (This only helps if you don't use filterwarnings("error") or likewise.)

Global mode via decorators

The pytypes decorators @typechecked, @auto_override, @annotations and @typelogged can be applied globally to all loaded modules and subsequently loaded modules. Modules that were loaded while typechecking or typelogging was disabled will not be affected. Apart from that this will affect every module in the way described above. Note that we recommend to use the profilers explained in the previous section if global typechecking or typelogging is required. Use this feature with care as it is still experimental and can notably slow down your python runtime. In any case, it is intended for debugging and testing phase only.

  • To apply @typechecked globally, use pytypes.set_global_typechecked_decorator
  • To apply @auto_override globally, use pytypes.set_global_auto_override_decorator
  • To apply @annotations globally, use pytypes.set_global_annotations_decorator
  • To apply @typelogged globally, use pytypes.set_global_typelogged_decorator

Warning: If the module that performs the ``pytypes.set_global_xy_decorator``-call is not yet fully imported, the warning regarding module-wide decorators (see above) applies to that module in the same sense. I.e. functions and classes that were not yet defined, will be covered only once the module-import has fully completed.

OOP support

All the above decorators work smoothly with OOP. You can safely apply @typechecked, @annotations and @typelogged on methods, abstract methods, static methods, class methods and properties. @override is – already by semantics – only applicable to methods, @auto_override is additionally applicable to classes and modules.

pytypes also takes care of inner classes and resolves name space properly. Make sure to apply decorators from pytypes on top of @staticmethod, @classmethod, @property or @abstractmethod rather than the other way round. This is because OOP support involves some special treatment internally, so OOP decorators must be visible to pytypes decorators. This also applies to old-style classes.

No @override on __init__

For now, @override cannot be applied to __init__, because __init__ typically extends the list of initialization parameters and usually uses super to explicitly serve a parent's signature. The purpose of @override is to avoid typos and to guarantee that the child method can always be used as a fill in for the parent in terms of signature and type information. Both aspects are hardly relevant for __init__:

  • a typo is unlikely and would show up quickly for various reasons
  • when creating an instance the caller usually knows the exact class to instantiate and thus its signature

For special cases where this might be relevant, @typechecked can be used to catch most errors.

Utilities

Utility functions described in this section can be directly imported from the pytypes module. Only the most important utility functions are listed here.

get_type_hints(func)

Resembles typing.get_type_hints, but is also workable on Python 2.7 and searches stubfiles for type information. Also on Python 3, this takes type comments into account if present.

get_types(func)

Works like get_type_hints, but returns types as a sequence rather than a dictionary. Types are returned in declaration order of the corresponding arguments.

check_argument_types(cllable=None, call_args=None, clss=None, caller_level=0)

This function mimics typeguard syntax and semantics. It can be applied within a function or method to check argument values to comply with type annotations. It behaves similar to @typechecked except that it is not a decorator and does not check the return type. A decorator less way for argument checking yields less interference with some debuggers.

check_return_type(value, cllable=None, clss=None, caller_level=0)

This function works like check_argument_types, but applies to the return value. Because it is impossible for pytypes to automatically figure out the value to be returned in a function, it must be explicitly provided as the value-parameter.

is_of_type(obj, cls, bound_Generic=None)

Works like isinstance, but supports PEP 484 style types from typing module.

If cls contains unbound TypeVar s and bound_Generic is provided, this function attempts to retrieve corresponding values for the unbound TypeVar s from bound_Generic.

is_subtype(subtype, supertype, bound_Generic=None)

Works like issubclass, but supports PEP 484 style types from typing module.

If subclass or superclass contains unbound TypeVar s and bound_Generic is provided, this function attempts to retrieve corresponding values for the unbound TypeVar s from bound_Generic.

deep_type(obj, depth=None, max_sample=None)

Tries to construct a type for a given value. In contrast to type(...), deep_type does its best to fit structured types from typing as close as possible to the given value. E.g. deep_type((1, 2, 'a')) will return Tuple[int, int, str] rather than just tuple. Supports various types from typing, but not yet all. Also detects nesting up to given depth (uses pytypes.default_typecheck_depth if no value is given). If a value for max_sample is given, this number of elements is probed from lists, sets and dictionaries to determine the element type. By default, all elements are probed. If there are fewer elements than max_sample, all existing elements are probed.

type_str(tp, assumed_globals=None, update_assumed_globals=None, implicit_globals=None, bound_Generic=None)

Generates a nicely readable string representation of the given type. The returned representation is workable as a source code string and would reconstruct the given type if handed to eval, provided that globals/locals are configured appropriately (e.g. assumes that various types from typing have been imported). Used as type-formatting backend of ptypes' code generator abilities in modules typelogger and stubfile_2_converter. If tp contains unbound TypeVar s and bound_Generic is provided, this function attempts to retrieve corresponding values for the unbound TypeVar s from bound_Generic.

dump_cache(path=default_typelogger_path, python2=False, suffix=None)

Writes cached observations by @typelogged into stubfiles.

Files will be created in the directory provided as 'path'; overwrites existing files without notice. Uses 'pyi2' suffix if 'python2' flag is given else 'pyi'. Resulting files will be Python 2.7 compliant accordingly.

get_Generic_itemtype(sq, simplify=True)

Retrieves the item type from a PEP 484 generic or subclass of such. sq must be a typing.Tuple or (subclass of) typing.Iterable or typing.Container. Consequently this also works with typing.List, typing.Set and typing.Dict. Note that for typing.Dict and mapping types in general, the key type is regarded as item type. For typing.Tuple all contained types are returned as a typing.Union. If simplify == True some effort is taken to eliminate redundancies in such a union.

get_Mapping_key_value(mp)

Retrieves the key and value types from a PEP 484 mapping or subclass of such. mp must be a (subclass of) typing.Mapping.

get_arg_for_TypeVar(typevar, generic)

Retrieves the parameter value of a given TypeVar from a Generic. Returns None if the generic does not contain an appropriate value. Note that the TypeVar is compared by instance and not by name. E.g. using a local TypeVar T would yield different results than using typing.T despite the equal name.

resolve_fw_decl(in_type, module_name=None, globs=None, level=0, search_stack_depth=2)

Resolves forward references in in_type.

globs should be a dictionary containing values for the names that must be resolved in in_type. If globs is not provided, it will be created by __globals__ from the module named module_name, plus __locals__ from the last search_stack_depth stack frames, beginning at the calling function. This is to resolve cases where in_type and/or types it fw-references are defined inside a function.

To prevent walking the stack, set search_stack_depth=0. Ideally provide a proper globs for best efficiency. See util.get_function_perspective_globals for obtaining a globs that can be cached. util.get_function_perspective_globals works like described above.

get_orig_class(obj, default_to__class__=False)

Robust way to access obj.__orig_class__. Compared to a direct access this has the following advantages:

  1. It works around python/typing#658.

  2. It prevents infinite recursion when wrapping a method (obj is self or cls) and either

    • the object's class defines __getattribute__ or
    • the object has no __orig_class__ attribute and the object's class defines __getattr__.

    See discussion at pull request 53.

If default_to__class__ is True it returns obj.__class__ as final fallback. Otherwise, AttributeError is raised in failure case (default behavior).

Python 2.7 compliant stubfiles

Currently pytypes uses the python runtime, i.e. import, eval, dir and inspect to parse stubfiles and type comments. A runtime independent parser for stubfiles is a desired future feature, but is not yet available. This means that conventional PEP 484 stubfiles would not work on Python 2.7. To resolve this gap, pytypes features a converter script that can convert conventional stubfiles into Python 2.7 compliant form. More specifically it converts parameter annotations into type comments and converts ... syntax into pass.

As of this writing it does not yet support stubfiles containing the @overload decorator. Also, it does not yet convert type annotations of attributes and variables.

'pyi2' suffix

pytypes uses the suffix 'pyi2' for Python 2.7 compliant stubfiles, but does not require it. Plain 'pyi' is also an acceptable suffix (as far as pytypes is concerned), because Python 2.7 compliant stubfiles can also be used in Python 3.

The main purpose of 'pyi2' suffix is to avoid name conflicts when conventional stubfiles and Python 2.7 compliant stubfiles coexist for the same module. In that case the pyi2 file will override the pyi file when running on Python 2.7.

stubfile_2_converter

Run stubfile_2_converter.py to leverage pytypes' stubfile converter capabilities:

python3 -m pytypes.stubfile_2_converter [options/flags] [in_file]

Use python3 -m pytypes.stubfile_2_converter -h to see detailed usage.

By default the out file will be created in the same folder as the in file, but with 'pyi2' suffix.

Next steps

Contributors

pytypes was created in 2016/17 by Stefan Richthofer.

Contributors (no specific order, names as provided on github)

License

pytypes is released under Apache 2.0 license. A copy is provided in the file LICENSE.


Copyright 2017, 2018, 2021 Stefan Richthofer

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at


Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Contact

[email protected]

pytypes's People

Contributors

agronholm avatar cclauss avatar dbarnett avatar elcombato avatar kentzo avatar lubieowoce avatar mitar avatar qria avatar rover-debug avatar sjjessop avatar stewori 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

pytypes's Issues

Exception: Parameterized generics cannot be used with class or instance checks

Reproduction:

import typing
import pytypes
from pytypes import type_util

type_util._issubclass(typing.List[typing.Dict[str, str]], typing.List[dict])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../pytypes/type_util.py", line 1769, in _issubclass
    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
  File ".../pytypes/type_util.py", line 1799, in _issubclass_2
    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
  File ".../pytypes/type_util.py", line 1345, in _issubclass_Generic
    _recursion_check):
  File ".../pytypes/type_util.py", line 1769, in _issubclass
    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
  File ".../pytypes/type_util.py", line 1796, in _issubclass_2
    _recursion_check)
  File ".../pytypes/type_util.py", line 1140, in _issubclass_Mapping_covariant
    return issubclass(subclass, superclass)
  File "/usr/lib/python3.6/typing.py", line 1148, in __subclasscheck__
    raise TypeError("Parameterized generics cannot be used with class "
TypeError: Parameterized generics cannot be used with class or instance checks

Using pytypes-1.0b5.

get_Generic_parameters should return Any if no parmeters

If I understand correctly, the following class Bar has all parameters set to Any:

from typing import *

A = TypeVar('A')

class Foo(Generic[A]):
    pass

class Bar(Foo):
    pass

But pytypes.type_util.get_Generic_parameters(Bar, Foo) fails with:

TypeError: Bar has no proper parameters defined by Foo.

But it should return:

(typing.Any,)

From documentation:

Using a generic class without specifying type parameters assumes Any for each position. In the following example, MyIterable is not generic but implicitly inherits from Iterable[Any]:

from typing import Iterable

class MyIterable(Iterable): # Same as Iterable[Any]

stub file load works only across single file

Problem presentation

The way the stub file implementation is done it will only work when all imports can be resolved using the Python files. A simple example when this is not the case is type aliases. For example imagine:

# util.pyi
from typing import Union

MagicParam = Union[int, float]
# magic.pyi
from .util import MagicParam

def a() -> MagicParam: ...

Loading magic.pyi will break with an import error of MagicParam as that is not defined inside the corresponding util.py. And given it's a type alias it should not be. As is used solely by the typing system.

The above example works with mypy, following the priority list described in PEP-561 resolution order.

Proposed solution

When loading stub files start with a clean import system, that first searches for pyi files, and if not present fallback to py. I have a prof of concept level of this here https://github.com/gaborbernat/prefer-stubs-python-import. If you think this request is reasonable I can make a PR for it. Thanks!

typechecked methods of generics do not check typed iterators.

I think this might be an impossible issue to fix because Python iterators (and all other standard collections) can hold elements of multiple types. Feel free to close without much consideration.

If I have a class that extends generic and has a method that takes an iterator, I cannot type check that the iterator has all elements of the desired type. This arises in the use case of creating a typed list which supports extend.

from typing import TypeVar, Generic, Iterator
from pytypes import typechecked

T = TypeVar('T')

class TypList(Generic[T], list):

    @typechecked
    def append(self, obj: T) -> None:
        super().append(obj)

    @typechecked
    def extend(self, iterable: Iterator[T]) -> None:
        super().extend(iterable)

    @typechecked
    def insert(self, index: int, obj: T) -> None:
        super().insert(index, obj)

class IntList(TypList[int]):
    ...

il = IntList() 

il.append(1) # No error, as expected
il.append("a") # InputTypeError as expected

il.extend(iter([1, 2, 3])) # No error, as expected
il.extend(iter(["a", "b", "c"])) # No error, somewhat unexpected

print(il) # [1, 1, 2, 3, 'a', 'b', 'c']  <-- Ideally would only hold ints

In this case, a perfectly reasonable workaround is the check the contents of iterable manually, not using the decorator.

Gradual typing with Any?

Using pytypes-1.0b5 I get:

$ python
Python 3.6.5 (default, Mar 31 2018, 05:34:57) 
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pytypes
>>> import typing
>>> T = typing.TypeVar('T')
>>> pytypes.is_subtype(T, typing.Any)
True
>>> pytypes.is_subtype(typing.Any, T)
False

Does pytypes support gradual typing? Is there a way to do verify an is-consistent-with relationship with its API?

Background: I'm trying to replace this method with a call to pytypes.is_subtype (using Python typing types):
https://github.com/apache/beam/blob/a843a08439eddcf10f140767761f8e8c1f88d715/sdks/python/apache_beam/typehints/typehints.py#L1112-L1120

Surprising issubclass result

Why the following returns false?

import typing
from pytypes import type_util

type_util._issubclass(typing.List[float], typing.List)

Isn't this the same as type_util._issubclass(typing.List[float], typing.List[typing.Any])? And then float is an instance of typing.Any? But this is false as well:

type_util._issubclass(typing.List[float], typing.List[typing.Any])

Issues type checking empty lists

>>> import typing
>>> from pytypes import type_util
>>> type_util._isinstance([], typing.Sequence)
True
>>> type_util._isinstance([], typing.Sequence[int])
False

False there is surprising. I understand why it happens: because internally isinstance is converted to issubclass and Sequence[Any] is not a subclass of Sequence[int].

An issue with forward declarations and recursive type

Example:

import typing

from pytypes import type_util

Container = typing.Union[
    typing.List['Data'],
]

Data = typing.Union[
    Container,
    str, bytes, bool, float, int, dict,
]

type_util._issubclass(typing.List[float], Container)
Traceback (most recent call last):
  File "<redacted>/lib/python3.6/site-packages/pytypes/type_util.py", line 1387, in _issubclass_2
    return issubclass(subclass, superclass)
TypeError: Forward references cannot be used with issubclass().

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 14, in <module>
    type_util._issubclass(typing.List[float], Container)
TypeError: Invalid type declaration: float, _ForwardRef('Data')

How to check if a type is a combination of some other types

In one part of my code, where I am deciding how to convert a value to JSON, I do:

any(is_subclass(structural_type, typ) for typ in (str, int, float, bool))

What I realized is that I would be OK with all possible Union combinations of these as well. So if structural_type is Union[str, int] that should also pass this check. Do you have a suggestion how to do so, or a helper function which would already do this? So I wonder about complicated case like Union[str, Union[int, bool]] or something like that.

Checking generators throws an error

from pytypes import type_util
value = (i for i in range(10))
type_util._isinstance(value, int)

I would just assume it would be False, but it is:

TypeError: <code object <genexpr> at 0x7f5bc05418a0, file "test.py", line 2> is not a module, class, method, or function.

This one is even trickier:

from pytypes import type_util

class Foo:
    def bar(self):
        value = (i for i in range(10))
        type_util._isinstance(value, int)

Foo().bar()

more tests

There is a lot of code in pytypes.type_utils. It would be great to see some tests to help understand it better, along with some docstrings (and type annotations!) on important functions. I was really surprised to see that there are no tests for pytypes.is_subtype .

Python 3.7 support

Compatibility with the new upcoming typing module is not yet established. I'm working on this as far as time allows, help welcome.
928fd62 was the first milestone of this process, enabling the test suite to complete without a crash (not speaking of failing tests). Now, still most tests are failing. Work to do...

TypeError: isinstance expected 2 arguments, got 1

Running Usage example with decorator in readme gave following error for me.

Traceback (most recent call last):
  File "/tmp/tmp.py", line 31, in <module>
    pytypes.dump_cache()
NameError: name 'pytypes' is not defined
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/Users/qria/.venvs/jupyter2/lib/python3.6/site-packages/pytypes/typelogger.py", line 316, in _dump_module
    assumed_glbls, implicit_globals, assumed_typevars)
  File "/Users/qria/.venvs/jupyter2/lib/python3.6/site-packages/pytypes/typelogger.py", line 755, in dump
    assumed_globals, implicit_globals, assumed_typevars)
  File "/Users/qria/.venvs/jupyter2/lib/python3.6/site-packages/pytypes/typelogger.py", line 680, in dump
    assumed_globals, implicit_globals))
  File "/Users/qria/.venvs/jupyter2/lib/python3.6/site-packages/pytypes/typelogger.py", line 653, in _stub_src_annotations
    assumed_globals, implicit_globals), ' ...']
  File "/Users/qria/.venvs/jupyter2/lib/python3.6/site-packages/pytypes/typelogger.py", line 625, in _declaration
    if annotated else self._signature(), ':'))
  File "/Users/qria/.venvs/jupyter2/lib/python3.6/site-packages/pytypes/typelogger.py", line 597, in _annotated_signature
    tp_lst = _prepare_arg_types_list(combine_argtype(self.arg_type_observations),
  File "/Users/qria/.venvs/jupyter2/lib/python3.6/site-packages/pytypes/typelogger.py", line 149, in combine_argtype
    assert isinstance(is_Tuple(observations[0]))
TypeError: isinstance expected 2 arguments, got 1

I don't know much about this codebase but maybe you've intended assert is_Tuple(observations[0])?

versions:

pytypes==1.0b4

get_Generic_parameters on Python 3.7

Per your comment ilevkivskyi/typing_inspect#35 (comment)

Using the latest master of pytypes (1449eff)

import typing as tp

import pytypes


T = tp.TypeVar("T")
T1 = tp.TypeVar("T1")
T2 = tp.TypeVar("T2")


class D0(tp.Dict[T1, T2]):
    def show_types(self) -> None:
        gt = pytypes.get_Generic_type(self)
        print(gt, pytypes.get_Generic_parameters(gt, D0))


class D1(D0[int, T]):
    pass


class D2(D1[str]):
    pass


D0[int, str]().show_types()
D1[str]().show_types()
D2().show_types()

works as expected on Python 3.6, producing:

__main__.D0[int, str] (<class 'int'>, <class 'str'>)
__main__.D1[str] (<class 'int'>, <class 'str'>)
__main__.D2 (<class 'int'>, <class 'str'>)

but crashes on Python 3.7:

__main__.D0[int, str] (<class 'int'>, <class 'str'>)
Traceback (most recent call last):
  File "pytypes10.py", line 26, in <module>
    D1[str]().show_types()
  File "pytypes10.py", line 14, in show_types
    print(gt, pytypes.get_Generic_parameters(gt, D0))
  File "venv-pytypes/lib/python3.7/site-packages/pytypes/type_util.py", line 327, in get_Generic_parameters
    res = _select_Generic_superclass_parameters(tp, generic_supertype)
  File "venv-pytypes/lib/python3.7/site-packages/pytypes/type_util.py", line 1319, in _select_Generic_superclass_parameters
    prms = _find_Generic_super_origin(subclass, superclass_origin)
  File "venv-pytypes/lib/python3.7/site-packages/pytypes/type_util.py", line 1256, in _find_Generic_super_origin
    if not bs.__origin__ is None:
AttributeError: type object 'D0' has no attribute '__origin__'

Maybe deserving a separate bug:

Changing

-class D1(D0[int, T]):
+class D1(D0[int, T1]):
    pass

causes the following erroneous output (running on Python 3.6):

__main__.D0[int, str] (<class 'int'>, <class 'str'>)
__main__.D1[str] (<class 'str'>, <class 'str'>)
__main__.D2 (<class 'str'>, <class 'str'>)

TypeVars should definitely be independent between classes (verified using reveal_type and mypy).

Tuple[(int, ...)] cannot be parsed on Python 2.7

Having a type comment like Tuple[(int, ...)] cannot be parsed on Python 2.7:

.../pytypes/typecomment_parser.py", line 225, in _funcsigtypesfromstring
    argTp = eval(argString, globals)
  File "<string>", line 1
    (Tuple[(int, ...)])

Error when checking a tuple against a typing.List

>>> from pytypes import type_util
>>> import typing
>>> type_util._isinstance((), typing.List)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: issubclass() arg 2 must be a class or tuple of classes
>>> type_util._isinstance((), typing.List[typing.Any])

Typecheck agent: Incorrect reaction on `None` annotation of constructor return type

The following code:

from pytypes import TypeChecker

class A:
    def __init__(self) -> None:
        pass

with TypeChecker():
    A()

fails as follows:

$ python3 Test.py 
Traceback (most recent call last):
  File "Test.py", line 8, in <module>
    A()
  File "Test.py", line 5, in __init__
    pass
pytypes.exceptions.ReturnTypeError: 
  __main__.A.__init__
  returned incompatible type:
Expected: NoneType
Received: Tuple[A]

If None annotation to A.__init_() is removed, the fail disappears.

Fault in typecheck agent on checking return type in presence of self or classmethod's cls

The following trivial code:

from configparser import ConfigParser
from pytypes import TypeChecker

with TypeChecker():
    ConfigParser()

fails as follows:

$ python3 Test.py
/usr/local/lib/python3.6/dist-packages/pytypes/type_util.py:2399: UserWarning: the system profiling hook has changed unexpectedly
  warn('the system profiling hook has changed unexpectedly')
Traceback (most recent call last):
  File "Test.py", line 5, in <module>
    ConfigParser()
  File "/usr/lib/python3.6/configparser.py", line 612, in __init__
    self._proxies[default_section] = SectionProxy(self, default_section)
  File "/usr/lib/python3.6/configparser.py", line 1223, in __init__
    for conv in parser.converters:
  File "/usr/lib/python3.6/configparser.py", line 1181, in converters
    return self._converters
  File "/usr/local/lib/python3.6/dist-packages/pytypes/type_util.py", line 2446, in __call__
    _check_caller_type(True, None, arg, caller_level=self._caller_level_shift+1)
  File "/usr/local/lib/python3.6/dist-packages/pytypes/type_util.py", line 2247, in _check_caller_type
    orig_clss = call_args[0].__orig_class__
  File "/usr/lib/python3.6/configparser.py", line 1306, in __getitem__
    return self._data[key]
KeyError: 0

Normalize Union output of type_str

Union types are equivalent (but not identical) when they differ in the order of arguments. Currently, type_str prints out Union type with arguments in the same order as it is stored in the type. But I think it might be better (or at least optional with argument) to output it in a canonical and reproducible way (probably just sort it). This would allow one to output descriptions of strings without being surprised by random replacements of types with equivalent types. See here for more information: python/typing#559

So, my suggestion would be to add to type_str an argument canonical which would always output description with everything in the same order, no matter if you are outputting for equivalent but not identical types.

pytypes.always_check_parent_types is inconvenient for __init__. Add special treatment(?)

from pytypes import typechecked

@typechecked
class Class1():
    def __init__(self, arg1: str) -> None:
        pass


class Class2(Class1):
    def __init__(self, arg1: str, arg2: str) -> None:
        Class1.__init__(self, arg1)


Class2("arg1", "arg2")

Calling Class1.__init__(self, arg1) causes the following exception:

pytypes.exceptions.InputTypeError:
  __main__.Class2.__init__
  called with incompatible types:
Expected: Tuple[str, str]
Received: Tuple[str]

OrderedDict is incompatible with Mapping

The following code:

from collections import OrderedDict
from typing import Mapping

from pytypes import TypeChecker

def f() -> Mapping[int, str]:
    return OrderedDict()

with TypeChecker():
    f()

fails as follows:

$ python3 Test.py 
Traceback (most recent call last):
  File "Test.py", line 11, in <module>
    f()
  File "Test.py", line 8, in f
    return OrderedDict()
pytypes.exceptions.ReturnTypeError: 
  __main__.f
  returned incompatible type:
Expected: Mapping[int, str]
Received: OrderedDict

Empty tuple is incompatible with Tuple[Any, ...]

The following code:

from typing import Any, Tuple

from pytypes import TypeChecker

def f() -> Tuple[Any, ...]:
    return ()

with TypeChecker():
    f()
    print("OK")

fails as follows:

$ python3 Test.py
Traceback (most recent call last):
  File "Test.py", line 9, in <module>
    f()
  File "Test.py", line 6, in f
    return ()
pytypes.exceptions.ReturnTypeError: 
  __main__.f
  returned incompatible type:
Expected: Tuple[Any]
Received: Tuple[]

NoneType' object is not iterable in resolve_fw_decl

>>> import typing
>>> from pytypes import type_util
>>> T = typing.TypeVar('T')
>>> class Foo(typing.Generic[T]):
...   pass
... 
>>> type_util.resolve_fw_decl(Foo)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not iterable

Jython: ImportError: No module named pkg_resources

This is probably a Jython issue, but I'd like to have a fallback, since pytypes used to work with Jython apart from this.
As far as I understand, this is about some functionality of setuptools.
I didn't explicitly run setup.py on Jython, so something might be ill-configured.
However, I also didn't do such a thing on CPython, but there it runs out of the box.

How to fix it?

Iterator and Generator annotations are not processed correctly

The following code:

from typing import Iterator

from pytypes import TypeChecker

def f() -> Iterator[str]:
    yield 'abc'

with TypeChecker():
    x = tuple(c for c in f())
    print("OK")

fails as follows:

$ python3 Test.py
Traceback (most recent call last):
  File "Test.py", line 9, in <module>
    x = tuple(c for c in f())
  File "Test.py", line 9, in <genexpr>
    x = tuple(c for c in f())
  File "Test.py", line 6, in f
    yield 'abc'
pytypes.exceptions.ReturnTypeError: 
  __main__.f
  returned incompatible type:
Expected: Iterator[str]
Received: str

Also a simple call to f(), not wrapped in generator expression and tuple, fails in a slightly different manner:

$ python3 Test.py
Exception ignored in: <generator object f at 0x7fe74e4b7db0>
Traceback (most recent call last):
  File "Test.py", line 5, in f
    def f() -> Iterator[str]:
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post23-py3.6.egg/pytypes/type_util.py", line 2580, in __call__
    _check_caller_type(True, None, arg, caller_level=self._caller_level_shift+1)
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post23-py3.6.egg/pytypes/type_util.py", line 2431, in _check_caller_type
    prop_getter, force_exception=True)
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post23-py3.6.egg/pytypes/typechecker.py", line 718, in _checkfuncresult
    _raise_typecheck_error(msg, True, check_val, tpch, resSig, func)
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post23-py3.6.egg/pytypes/type_util.py", line 2336, in _raise_typecheck_error
    raise pytypes.ReturnTypeError(msg)
pytypes.exceptions.ReturnTypeError: 
  __main__.f
  returned incompatible type:
Expected: Iterator[str]
Received: NoneType
OK

Similar stacks occur if Iterator[str] is replaced with Generator[str, None, None].

Allow one to specify module to get_type_hints

Currently it is trying to guess the module, but this is not always possible. For example, it does not work for me if I call get_type_hints while the module is being defined and module does not yet exist.

A workaround for me is that currently I set im_class attribute on the function I want to get type hints for, but I think it might be cleaner if there would be an optional argument get_type_hints.

_issubclass and bound_typevars argument

import typing
from pytypes import type_util

T = typing.TypeVar('T', covariant=True)

class L(typing.List[T]):
    pass

C = typing.TypeVar('T', bound=L)

type_util._issubclass(L[float], C)  # False
type_util._issubclass(L[float], C, bound_typevars={})  # True

Why does bound_typevar change result from False to True?

Also, why code never checks if superclass is in bound_typevars? I think it just relays on catch-all except. I think such approach might hide some bugs?

Found likely typo in pytypes.typechecker._typeinspect_func

Hi! while investigating a PR, I found this in checker_tp: (pytypes.typechecker._typeinspect_func:781)

if prop or prop_getter:
    slf = True
    util._warn_argname('property using non-idiomatic self argname',
            func0, slf, clsm)
    check_args = args_kw[1:] # omit self
 check_args = args_kw # <--- will always overwrite the `check_args` from the previous line!

I don't know enough about the logic involved to fix it, but I'm guessing it was intended to be this:

if prop or prop_getter:
    slf = True
    util._warn_argname('property using non-idiomatic self argname',
            func0, slf, clsm)
    check_args = args_kw[1:] # omit self
else:
    check_args = args_kw

(That also fixes some bugs I hit because of "non-idiomatic self" in @properties, but that's for another time)

error with @autoclass concerning default arguments' type

I would like to provide an example showing how pytypes can be used with autoclass.
However the following code fails with a strange error:

from autoclass import autoclass, Boolean
from pytypes import typechecked
from numbers import Real, Integral
from typing import Optional

@typechecked
@autoclass
class HouseConfiguration(object):
    def __init__(self,
                 name: str,
                 surface: Real,
                 nb_floors: Optional[Integral] = 1,
                 with_windows: Boolean = False):
        pass

    # -- overriden setter for surface for custom validation
    @setter_override
    def surface(self, surface):
        assert surface > 0
        self._surface = surface

t = HouseConfiguration('test', 12, 2)  # error

The error received is :

Expected: Tuple[str, Real, Union[Integral, NoneType], Boolean]
Received: Tuple[str, int, int, int]

While this works:

t = HouseConfiguration('test', 12, nb_floors=2)

So it seems that this does not have anything to do with autoclass: somehow positional arguments / default values in the constructor signature are not handled properly.

TypeChecker does not work

The following trivial script fails with TypeError: 'TypeChecker' object does not support indexing:

from pytypes import TypeChecker

with TypeChecker():
    pass

Python 3.5.5.
Surely some kind of basic testing would've been nice? This works in typeguard btw.

Deploy to pypi?

This library looks quite useful; it would be awesome to have it pip-installable!

List[function] is incompatible with List[Callable]

The following code:

from typing import Callable, List

from pytypes import TypeChecker

def n() -> None:
    pass

def f() -> List[Callable[[], None]]:
    return [n]

with TypeChecker():
    f()
    print("OK")

fails as follows:

$ python3 Test.py
Traceback (most recent call last):
  File "Test.py", line 12, in <module>
    f()
  File "Test.py", line 9, in f
    return [n]
pytypes.exceptions.ReturnTypeError: 
  __main__.f
  returned incompatible type:
Expected: List[Callable[[], NoneType]]
Received: List[function]

Frozenset seems to not be equal to set?

>>> import typing
>>> from pytypes import type_util
>>> type_util._isinstance(frozenset({1, 2, 'a', None, 'b'}), typing.AbstractSet[typing.Union[str, int, None]])
False
>>> type_util._isinstance({1, 2, 'a', None, 'b'}, typing.AbstractSet[typing.Union[str, int, type(None)]])
True
>>> issubclass(frozenset, typing.AbstractSet)
True

is_of_type([], List[int]) throws an exception in 3.7

Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import typing
>>> import pytypes
>>> pytypes.is_of_type([], typing.List[int])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "D:\.virtualenvs\pytypes\lib\site-packages\pytypes\type_util.py", line 1904, in _isinstance
    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
  File "D:\.virtualenvs\pytypes\lib\site-packages\pytypes\type_util.py", line 1769, in _issubclass
    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
  File "D:\.virtualenvs\pytypes\lib\site-packages\pytypes\type_util.py", line 1799, in _issubclass_2
    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
  File "D:\.virtualenvs\pytypes\lib\site-packages\pytypes\type_util.py", line 1310, in _issubclass_Generic
    bound_typevars_readonly, follow_fwd_refs, _recursion_check):
  File "D:\.virtualenvs\pytypes\lib\site-packages\pytypes\type_util.py", line 1769, in _issubclass
    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
  File "D:\.virtualenvs\pytypes\lib\site-packages\pytypes\type_util.py", line 1799, in _issubclass_2
    bound_typevars_readonly, follow_fwd_refs, _recursion_check)
  File "D:\.virtualenvs\pytypes\lib\site-packages\pytypes\type_util.py", line 1293, in _issubclass_Generic
    if subclass.__origin__ is None:
AttributeError: type object 'Empty' has no attribute '__origin__'

This seems to be related to #32. I tested the above in python 3.6.5 and it worked fine, so it's only an issue in python 3.7.

Empty dict is incompatible with Mapping

The following code:

from typing import List, Mapping

from pytypes import TypeChecker

def f() -> List[Mapping[int, str]]:
    return [{}]

with TypeChecker():
    f()
    print("OK")

fails as follows:

$ python3 Test.py
Traceback (most recent call last):
  File "Test.py", line 9, in <module>
    f()
  File "Test.py", line 6, in f
    return [{}]
pytypes.exceptions.ReturnTypeError: 
  __main__.f
  returned incompatible type:
Expected: List[Mapping[int, str]]
Received: List[Empty[Dict]]

Error when running is_of_type on defaultdict

>>> import typing
>>> import collections
>>> import pytypes
>>> pytypes.is_of_type(collections.defaultdict(list), typing.Dict[str, typing.List])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Parameterized generics cannot be used with class or instance checks

I would hope to get True instead.

check_argument_types broken on CPython 3.6.3

Just noticed that with CPyhton 3.6.3 tests related to check_argument_types started to fail.
I lastly tested with CPython 3.6.1, so not sure about CPython 3.6.2. Will test that one as soon as I find time...

difference in is_subtype vs subclass for int and float

Is this intended behavior?

#Python 3.6.2 | packaged by conda-forge | (default, Jul 23 2017, 23:01:38)
#Type 'copyright', 'credits' or 'license' for more information
#IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import pytypes

In [2]: issubclass(int, float)
Out[2]: False

In [3]: pytypes.is_subtype(int, float)
Out[3]: True

pytypes does not preserve the methods' signatures

from pytypes import typechecked
from inspect import signature

class Foo:
    def __init__(self, foo: str = 'hello'):
        self.foo = foo

s = signature(Foo.__init__)
print(s.parameters)
# > OrderedDict([('self', <Parameter "self">), ('foo', <Parameter "foo:str='hello'">)])
# parameter names, type hints and default values are preserved

Whereas

@typechecked
class Foo:
    def __init__(self, foo: str):
        self.foo = foo

s = signature(Foo.__init__)
print(s.parameters)
# > OrderedDict([('args', <Parameter "*args">), ('kw', <Parameter "**kw">)])
# everything has been removed ! 

This is annoying as it does not allow other libraries to automatically discover what is required to build an object. For example with parsyfiles.

The solution is to use a signature-preserving decorator library in pytypes, I personally use decorator but you can also use wrapt I think.

Is there a method to list all subclasses of a type, taking into account both parametrized, half-parametrized and not-parametrized generics ?

Ideally such a method would provide parameters to specify if the caller wishes to

  • only look at subclasses with the same status (subclasses with exactly the same response to is_generic() as the parent, and in case of a generic parent, with the same list of parameters)
  • or to extend to half- (if the parent is a non-parametrized generic, subclasses that are still generic and with at least one remaining parameter would be returned),
  • or to extend to entirely- parametrized subclasses

List[int] is incompatible with List[Union[float]]

The following code:

from typing import cast, List

from pytypes import TypeChecker

def f() -> List[float]:
    return cast(List[float], [2])

with TypeChecker():
    f()
    print("OK")

fails as follows:

$ python3 Test.py
Traceback (most recent call last):
  File "Test.py", line 9, in <module>
    f()
  File "Test.py", line 6, in f
    return cast(List[float], [2])
pytypes.exceptions.ReturnTypeError: 
  __main__.f
  returned incompatible type:
Expected: List[float]
Received: List[int]

README.rst contains utf-8 characters

So this means package cannot be installed on systems which do not have current local set to utf-8. In Python 3, you get an error like:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 8634: ordinal not in range(128)

One option would be to do open(readme_path, encoding='utf8').read(), but this would not work on Python 2, where you will have to use io.open.

Other is that you remove all unicode characters from the README.

Generator expression is not considered Iterable

The following code:

from typing import Iterable

from pytypes import TypeChecker

def f(a: Iterable[str]) -> None:
    pass

with TypeChecker():
    f((c for c in 'abc'))
    print("OK")

fails as follows:

$ python3 Test.py
Traceback (most recent call last):
  File "Test.py", line 9, in <module>
    f((c for c in 'abc'))
  File "Test.py", line 5, in f
    def f(a: Iterable[str]) -> None:
pytypes.exceptions.InputTypeError: 
  __main__.f
  called with incompatible types:
Expected: Tuple[Iterable[str]]
Received: Tuple[Generator]

set() is an instance of typing.Sized

>>> import typing
>>> from pytypes import type_util
>>> type_util._isinstance(set(), typing.Sized)
False
>>> isinstance(set(), typing.Sized)
True

The first False is surprising. I would expect it to be True like isinstance is.

Python >= 3.6.1 (maybe others too but not 3.6.0 or 3.5): Typecheck agent fails on re.compile()

The following code:

import re
from pytypes import TypeChecker

with TypeChecker():
    re.compile('a')

fails as follows:

$ python3 Test.py 
/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post20-py3.6.egg/pytypes/type_util.py:2532: UserWarning: the system profiling hook has changed unexpectedly
  warn('the system profiling hook has changed unexpectedly')
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post20-py3.6.egg/pytypes/typecomment_parser.py", line 52, in _get_typestrings
    srclines = inspect.getsourcelines(obj)[0]
  File "/usr/lib/python3.6/inspect.py", line 955, in getsourcelines
    lines, lnum = findsource(object)
  File "/usr/lib/python3.6/inspect.py", line 768, in findsource
    file = getsourcefile(object)
  File "/usr/lib/python3.6/inspect.py", line 684, in getsourcefile
    filename = getfile(object)
  File "/usr/lib/python3.6/inspect.py", line 666, in getfile
    'function, traceback, frame, or code object'.format(object))
TypeError: <slot wrapper '__and__' of 'int' objects> is not a module, class, method, function, traceback, frame, or code object

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "Test.py", line 5, in <module>
    re.compile('a')
  File "/usr/lib/python3.6/re.py", line 233, in compile
    return _compile(pattern, flags)
  File "/usr/lib/python3.6/re.py", line 302, in _compile
    if not (flags & DEBUG):
  File "/usr/lib/python3.6/enum.py", line 803, in __and__
    def __and__(self, other):
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post20-py3.6.egg/pytypes/type_util.py", line 2563, in __call__
    _check_caller_type(False, caller_level=self._caller_level_shift+1)
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post20-py3.6.egg/pytypes/type_util.py", line 2416, in _check_caller_type
    cllable, clss = _find_typed_base_method(cllable, clss)
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post20-py3.6.egg/pytypes/type_util.py", line 2131, in _find_typed_base_method
    if has_type_hints(util._actualfunc(fmeth)):
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post20-py3.6.egg/pytypes/type_util.py", line 657, in has_type_hints
    return _has_type_hints(func0)
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post20-py3.6.egg/pytypes/type_util.py", line 686, in _has_type_hints
    tpStr = _get_typestrings(func, False)
  File "/usr/local/lib/python3.6/dist-packages/pytypes-1.0b5.post20-py3.6.egg/pytypes/typecomment_parser.py", line 54, in _get_typestrings
    srclines = inspect.getsourcelines(getattr(obj.__class__, obj.__name__))[0]
AttributeError: type object 'wrapper_descriptor' has no attribute '__and__'

Support for non-parametrized generic types

from typing import Dict
from pytypes import typechecked

@typechecked
def foo(a: Dict):
    pass

foo({'name': 'test2', 'surface': 1})

Raises

   pytypes.exceptions.InputTypeError: 
     autoclass.tests.test_readme.foo
     called with incompatible types:
   Expected: Tuple[Dict]
   Received: Tuple[Dict[str, Union[str, int]]]

While it should not.
Note that it also makes PyCharm 2017.2.4 and 2017.3 crash when executed in the python terminal :

During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.2.3\helpers\pydev\_pydev_bundle\pydev_console_utils.py", line 251, in add_exec
    more = self.do_add_exec(code_fragment)
  File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.2.3\helpers\pydev\pydevconsole.py", line 123, in do_add_exec
    command.run()
  File "C:\Program Files\JetBrains\PyCharm Community Edition 2017.2.3\helpers\pydev\pydevconsole.py", line 82, in run
    self.more = self.interpreter.runsource(text, '<input>', symbol)
  File "C:\Miniconda3\envs\tools\lib\code.py", line 75, in runsource
    self.runcode(code)
  File "C:\Miniconda3\envs\tools\lib\code.py", line 95, in runcode
    self.showtraceback()
  File "C:\Miniconda3\envs\tools\lib\code.py", line 149, in showtraceback
    sys.excepthook(ei[0], ei[1], last_tb)
  File "C:\Miniconda3\envs\tools\lib\site-packages\pytypes\util.py", line 816, in _pytypes_excepthook
    traceback.print_exception(exctype, value, tb, _calc_traceback_limit(tb))
  File "C:\Miniconda3\envs\tools\lib\site-packages\pytypes\util.py", line 760, in _calc_traceback_limit
    if tb2.tb_next.tb_frame.f_code.co_filename.split(os.sep)[-2] == 'pytypes' and not \
IndexError: list index out of range

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.