Giter VIP home page Giter VIP logo

runtype's Introduction

alt text

Runtype is a collection of run-time type utilities for Python.

It is:

๐Ÿƒ Fast! Uses an internal typesystem for maximum performance.

๐Ÿง  Smart! Supports typing, forward-references, constraints, auto-casting, and more.

โš™๏ธ Configurative! Write your own type system, and use it with dataclass and dispatch.


Modules

  • โญ validation - Provides a smarter alternative to isinstance and issubclass, with support for the typing module, and type constraints.

  • โญ dataclass - Adds run-time type validation to the built-in dataclass.

    • Improves dataclass ergonomics.
    • Supports most mypy constructs, like typing and forward-references (foo: 'Bar').
    • Supports automatic value casting, Pydantic-style. (Optional, off by default)
    • Supports types with constraints. (e.g. String(max_length=10))
    • Supports optional sampling for faster validation of big lists and dicts.
    • Twice faster than Pydantic-v1 with pure Python (read here)
  • โญ dispatch - Provides fast multiple-dispatch for functions and methods, via a decorator.

    • Dispatch on multiple arguments
    • Full specificity resolution
    • Supports mypy, by utilizing the @overload decorator
    • Inspired by Julia.
  • โญ type utilities - Provides a set of classes to implement your own type-system.

    • Supports generics, constraints, phantom types
    • Used by runtype itself, to emulate the Python type-system.

Docs

Read the docs here: https://runtype.readthedocs.io/

Install

pip install runtype

No dependencies.

Requires Python 3.8 or up.

codecov

Examples

Validation (Isa & Subclass)

Use isa and issubclass as a smarter alternative to the builtin isinstance & issubclass -

from runtype import isa, issubclass

assert isa({'a': 1}, dict[str, int])        # == True
assert not isa({'a': 'b'}, dict[str, int])  # == False

assert issubclass(dict[str, int], typing.Mapping[str, int])     # == True
assert not issubclass(dict[str, int], typing.Mapping[int, str]) # == False

Dataclasses

from runtype import dataclass

@dataclass(check_types='cast')  # Cast values to the target type, when applicable
class Person:
    name: str
    birthday: datetime = None   # Implicit optional
    interests: list[str] = []   # The list is copied for each instance


print( Person("Beetlejuice") )
#> Person(name='Beetlejuice', birthday=None, interests=[])
print( Person("Albert", "1955-04-18T00:00", ['physics']) )
#> Person(name='Albert', birthday=datetime.datetime(1955, 4, 18, 0, 0), interests=['physics'])
print( Person("Bad", interests=['a', 1]) )
# TypeError: [Person] Attribute 'interests' expected value of type list[str]. Instead got ['a', 1]
#     Failed on item: 1, expected type str

Multiple Dispatch

Runtype dispatches according to the most specific type match -

from runtype import multidispatch as md

@md
def mul(a: list, b: list):
    return [mul(i, j) for i, j in zip(a, b, strict=True)]
@md
def mul(a: list, b: Any):
    return [ai*b for ai in a]
@md
def mul(a: Any, b: list):
    return [bi*b for bi in b]
@md
def mul(a: Any, b: Any):
    return a * b

assert mul("a", 4)         == "aaaa"        # Any, Any
assert mul([1, 2, 3], 2)   == [2, 4, 6]     # list, Any
assert mul([1, 2], [3, 4]) == [3, 8]        # list, list

Dispatch can also be used for extending the dataclass builtin __init__:

@dataclass(frozen=False)
class Point:
    x: int = 0
    y: int = 0
    
    @md
    def __init__(self, points: list | tuple):
        self.x, self.y = points

    @md
    def __init__(self, points: dict):
        self.x = points['x']
        self.y = points['y']
    
# Test constructors
p0 = Point()                         # Default constructor
assert p0 == Point(0, 0)             # Default constructor
assert p0 == Point([0, 0])           # User constructor
assert p0 == Point((0, 0))           # User constructor
assert p0 == Point({"x": 0, "y": 0}) # User constructor

Benchmarks

Runtype beats its competition handily. It is significantly faster than both beartype and plum, and in some cases is even faster than regular Python code.

See the benchmarks page in the documentation for detailed benchmarks.

alt text

alt text

License

Runtype uses the MIT license.

Contribute

If you like Runtype and want to see it grow, you can help by:

  • Reporting bugs or suggesting features

  • Submitting pull requests (better to ask me first)

  • Writing about runtype in a blogpost or even a tweet

runtype's People

Contributors

erezsh 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

runtype's Issues

Importing runtype breaks (at least) get_type_hints

Is runtype somehow messing with typing, typing_extensions or pydantic?

A thing that looks relevant in my logs is:

/home/admin/.pyenv/versions/3.8.10/lib/python3.8/typing.py:1264: in get_type_hints
    value = _eval_type(value, globalns, localns)
/home/admin/.pyenv/versions/3.8.10/lib/python3.8/typing.py:270: in _eval_type
    return t._evaluate(globalns, localns)
E   TypeError: _evaluate() missing 1 required positional argument: '_'

Even just importing runtype.validation triggers that problem, so I'd expect it does some monkey patching on import.

`json` method doesn't work with list

As I wanted to add you to apischema benchmark, I've noticed an issue with serialization, which simply doesn't work with list.

from runtype import dataclass

@dataclass
class Bar:
    baz: int

@dataclass
class Foo:
    bars: list[Bar]

assert Foo([Bar(0)]).json() == {"bars": [Bar(baz=0)]}

As you can see, Bar instances are not serialized.

runtype.isa([40, 2], tuple[int, int]) == True

Hello,

Test with runtype 0.2.3:

>>> import runtype
>>> runtype.isa([40, 2], tuple[int, int])
True

Expected behavior:

>>> import runtype
>>> runtype.isa([40, 2], tuple[int, int])
False

Remark: runtype does agree that a tuple is not a list:

>>> runtype.isa((40, 2), list[int])
False

Support Union for is_subtype

Hi!

Thanks for creating this project! is_subtype should probably be part of typing or typing_extensions.

Currently, is_subtype does not work for Unions:

>>> import runtype
>>> runtype.is_subtype(str, str | None)
True
>>> runtype.is_subtype(str | None, str | None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../python3.10/site-packages/runtype/validation.py", line 30, in is_subtype
    return ct1 <= ct2
  File ".../python3.10/site-packages/runtype/pytypes.py", line 130, in __le__
    return issubclass(self.kernel, other.kernel)
TypeError: issubclass() arg 1 must be a class

Does it make sense to support this? I can try to add support for this feature.

In my project, I wanted to check that some Pydantic classes implement some Protocols. I cannot inherit from those Protocols, as both Pydantic and Protocol use (different) metaclasses. So I was comparing the __annotations__ in the Protocol with the ones in the pydantic implementation using is_subtype.

Thanks!

Using dataclass and multiple dispatch with constructor

Thank you for putting this package together. It is very useful.

One problem I ran into is using dataclass with multiple dispatch on the constructor. Here is an example:

Example

from runtype import Dispatch 
from runtype import dataclass

dp = Dispatch()

@dataclass 
class SomeType:
    
    x: int = 1
    y: int = 1

@dataclass
class MyClass:
    
    x: int = 0
    y: int = 0
    
    @dp
    def __init__(self, some_type : SomeType):
        self.x = some_type.x
        self.y = some_type.y

Error

 MyClass()
Traceback (most recent call last):

  File "/home/dfish/anaconda3/lib/python3.8/site-packages/runtype/dispatch.py", line 84, in find_function_cached
    return self._cache[sig]

KeyError: (<class '__main__.MyClass'>,)


During handling of the above exception, another exception occurred:

Traceback (most recent call last):

  File "/tmp/ipykernel_54810/2409861197.py", line 1, in <module>
    MyClass()

  File "/home/dfish/anaconda3/lib/python3.8/site-packages/runtype/dispatch.py", line 36, in dispatched_f
    f = root.find_function_cached(args)

  File "/home/dfish/anaconda3/lib/python3.8/site-packages/runtype/dispatch.py", line 86, in find_function_cached
    f = self.find_function(args)

  File "/home/dfish/anaconda3/lib/python3.8/site-packages/runtype/dispatch.py", line 72, in find_function
    raise DispatchError(f"Function '{self.name}' not found for signature {self.get_arg_types(args)}")

DispatchError: Function '__init__' not found for signature (<class '__main__.MyClass'>,)
 

It would be nice to use dataclass and multiple dispatch together, but its not clear to me if that is possible.

Dispatch and Runtype Dataclasses

In #24 you mentioned how multiple dispatch works fine for constructors. To demonstrate, you introduced a Color class that exhibits this behavior...

from runtype import Dispatch

dp = Dispatch()

class Color:
    @dp
    def __init__(self, hex: str):
        self.hex = hex

    @dp
    def __init__(self, r: int, g:int, b: int):
        self.hex = '#%02x%02x%02x' % (r,g,b)


a = Color(10, 20, 30)
b = Color('#abc')

print(a.hex, b.hex) # Prints: #0a141e #abc

Experimenting with your example, I tried making the Color class into a dataclasses.dataclass, and it still worked great! However, when I made it into a runtype.dataclass, I received the following error...

Traceback (most recent call last):
  File "C:\Work\temp-runtype\main.py", line 20, in <module>
    a = Color(10, 20, 30)
  File "C:\Users\owner\AppData\Local\pypoetry\Cache\virtualenvs\temp-runtype-7V6D2ZSe-py3.10\lib\site-packages\runtype\dispatch.py", line 37, in dispatched_f
    return f(*args, **kw)
  File "C:\Work\temp-runtype\main.py", line 17, in __init__
    self.hex = '#%02x%02x%02x' % (r,g,b)
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'hex'

A curious thing to discover from runtype's own dataclass decorator. Is this a bug, or have I missed something?

Add support for Annotated in `is_subtype` (transparently ignoring the annotations)

In my project I am using pydantic and use its Field(...) configuration e.g. to define alias names for keys within Annotated, e.g.

x: Annotated[int, Field(alias="y")]

but I need to check is_subtype on the wrapped types (I don't use the annotation for any type-domain related info).

It would be great if Annotated could be transparently ignored.

I understand there are many "semantics" one could give to checking Annotated types, but as the annotation is quite arbitrary and tool specific, I think that simply ignoring the annotation and recursing down the actual type would be the "right" default behavior.

A more conservative solution I would also be happy with would be to have this as some keyword argument to is_subtype, called e.g. ignore_annotations to have that behavior and do with them whatever you believe would make more sense as the default.

But in my case, the annotation object (pydantic.FieldInfo) does not have even equality, so this would not work for me to check that the annotations must all be equal or something like that. Thats why I think it does not make any sense to try doing anything with the annotations but ignoring them, as it probably would not work in the general case.

No support for type Literal error when using typing_extensions >= 4.6.0

This worked up to typing_extensions version 4.5.0:

from runtype import is_subtype
from typing_extensions import Literal
is_subtype(Literal[1], Literal[1, 2])

Now with typing_extensions 4.6.0 and upward (and Python 3.8) I get this:

Traceback (most recent call last):
  File "/local/home/a.pirogov/.cache/pypoetry/virtualenvs/metador-core-AXME58BY-py3.8/lib/python3.8/site-packages/runtype/pytypes.py", line 541, in to_canon
    return self.cache[t]
KeyError: typing_extensions.Literal[1]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/local/home/a.pirogov/.cache/pypoetry/virtualenvs/metador-core-AXME58BY-py3.8/lib/python3.8/site-packages/runtype/pytypes.py", line 544, in to_canon
    res = _type_cast_mapping[t]
KeyError: typing_extensions.Literal[1]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/local/home/a.pirogov/.cache/pypoetry/virtualenvs/metador-core-AXME58BY-py3.8/lib/python3.8/site-packages/runtype/validation.py", line 28, in is_subtype
    ct1 = type_caster.to_canon(t1)
  File "/local/home/a.pirogov/.cache/pypoetry/virtualenvs/metador-core-AXME58BY-py3.8/lib/python3.8/site-packages/runtype/pytypes.py", line 546, in to_canon
    res = self._to_canon(t)
  File "/local/home/a.pirogov/.cache/pypoetry/virtualenvs/metador-core-AXME58BY-py3.8/lib/python3.8/site-packages/runtype/pytypes.py", line 532, in _to_canon
    raise NotImplementedError("No support for type:", t)
NotImplementedError: ('No support for type:', typing_extensions.Literal[1])

is_subtype does not work for Literals

>>> is_subtype(Literal[1], Literal[1,2])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/admin/.cache/pypoetry/virtualenvs/metador-core-XpPben8F-py3.8/lib/python3.8/site-packages/runtype/validation.py", line 25, in is_subtype
    return t1 <= t2
TypeError: '<=' not supported between instances of 'OneOf' and 'OneOf'

Expected: True

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.