A pure python (no special compiler required) type enforcer for type annotations. Enforce types in python functions and methods.
Make sure you have Python 3.9.x (or higher) installed on your system. You can download it here. For older python versions (3.7 | 3.8), you should use type_enforced==0.0.16.
python>=3.10
|
require python>=3.10
pip install type_enforced
import type_enforced
@type_enforced.Enforcer(enabled=True)
def my_fn(a: int , b: [int, str] =2, c: int =3) -> None:
pass
enabled=True
by default if not specified. You can set enabled=False
to disable type checking for a specific function, method, or class. This is useful for a production vs debugging environment or for undecorating a single method in a larger wrapped class.type_enforcer
contains a basic Enforcer
wrapper that can be used to enforce many basic python typing hints. Technical Docs Here.
type_enforcer
currently supports many single and multi level python types. This includes class instances and classes themselves. For example, you can force an input to be an int
, a number [int, float]
, an instance of the self defined MyClass
, or a even a vector with list[int]
. Items like typing.List
, typing.Dict
, typing.Union
and typing.Optional
are supported.
You can pass union types to validate one of multiple types. For example, you could validate an input was an int or a float with [int, float]
, [int | float]
or even typing.Union[int, float]
.
Nesting is allowed as long as the nested items are iterables (e.g. typing.List
, dict
, ...). For examle, you could validate that a list is a vector with list[int]
or possibly typing.List[int]
.
Variables without an annotation for type are not enforced.
Note: Type Enforced does not support __future__.annotations
. If you call from __future__ import annotations
in your file, type enforced will not work as expected.
str
, list
, int
, dict
, ...),
separated list (e.g. [int, float]
)|
separated list (e.g. [int | float]
)dict[str]
or list[int,float]
)
list
, set
, tuple
or dict
dict
keys are not validated, only valuesdict[dict[int]]
list[set[str]]
typing
(package) functions and methods including:
List
, Set
, Dict
, Tuple
Union
Optional
Sized
list
, tuple
, dict
, set
, str
, bytes
, bytearray
, memoryview
, range
Nested types
aboveLiteral
Literal['a', 'b']
will require any passed values that are equal (==
) to 'a'
or 'b'
.
Literal
.Callable
staticmethod
, classmethod
, types.FunctionType
, types.BuiltinFunctionType
, types.MethodType
, types.BuiltinMethodType
, types.GeneratorType
Constraint
validation.
>>> import type_enforced
>>> @type_enforced.Enforcer
... def my_fn(a: int , b: [int, str] =2, c: int =3) -> None:
... pass
...
>>> my_fn(a=1, b=2, c=3)
>>> my_fn(a=1, b='2', c=3)
>>> my_fn(a='a', b=2, c=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/conmak/development/personal/type_enforced/type_enforced/enforcer.py", line 85, in __call__
self.__check_type__(assigned_vars.get(key), value, key)
File "/home/conmak/development/personal/type_enforced/type_enforced/enforcer.py", line 107, in __check_type__
self.__exception__(
File "/home/conmak/development/personal/type_enforced/type_enforced/enforcer.py", line 34, in __exception__
raise TypeError(f"({self.__fn__.__qualname__}): {message}")
TypeError: (my_fn): Type mismatch for typed variable `a`. Expected one of the following `[<class 'int'>]` but got `<class 'str'>` instead.
import type_enforced
import typing
@type_enforced.Enforcer
def my_fn(
a: dict[dict[int, float]], # Note: dict keys are not validated, only values
b: list[typing.Set[str]] # Could also just use set
) -> None:
return None
my_fn(a={'i':{'j':1}}, b=[{'x'}]) # Success
my_fn(a={'i':{'j':'k'}}, b=[{'x'}]) # Error:
# TypeError: (my_fn): Type mismatch for typed variable `a[i][j]`. Expected one of the following `[<class 'int'>]` but got `<class 'str'>` instead.
Type enforcer can be applied to methods individually:
import type_enforced
class my_class:
@type_enforced.Enforcer
def my_fn(self, b:int):
pass
You can also enforce all typing for all methods in a class by decorating the class itself.
import type_enforced
@type_enforced.Enforcer
class my_class:
def my_fn(self, b:int):
pass
def my_other_fn(self, a: int, b: [int, str]):
pass
You can also enforce types on staticmethod
s and classmethod
s if you are using python >= 3.10
. If you are using a python version less than this, classmethod
s and staticmethod
s methods will not have their types enforced.
import type_enforced
@type_enforced.Enforcer
class my_class:
@classmethod
def my_fn(self, b:int):
pass
@staticmethod
def my_other_fn(a: int, b: [int, str]):
pass
Dataclasses are suported too.
import type_enforced
from dataclasses import dataclass
@type_enforced.Enforcer
@dataclass
class my_class:
foo: int
bar: str
You can skip enforcement if you add the argument enabled=False
in the Enforcer
call.
enabled=False
for an entire class or simply disable a specific method in a larger wrapped class.Note: Method level wrapper enabled
values take precedence over class level wrappers.
import type_enforced
@type_enforced.Enforcer
class my_class:
def my_fn(self, a: int) -> None:
pass
@type_enforced.Enforcer(enabled=False)
def my_other_fn(self, a: int) -> None:
pass
Type enforcer can enforce constraints for passed variables. These constraints are vaildated after any type checks are made.
To enforce basic input values are integers greater than or equal to zero, you can use the Constraint class like so:
import type_enforced
from type_enforced.utils import Constraint
@type_enforced.Enforcer()
def positive_int_test(value: [int, Constraint(ge=0)]) -> bool:
return True
positive_int_test(1) # Passes
positive_int_test(-1) # Fails
positive_int_test(1.0) # Fails
To enforce a GenericConstraint:
import type_enforced
from type_enforced.utils import GenericConstraint
CustomConstraint = GenericConstraint(
{
'in_rgb': lambda x: x in ['red', 'green', 'blue'],
}
)
@type_enforced.Enforcer()
def rgb_test(value: [str, CustomConstraint]) -> bool:
return True
rgb_test('red') # Passes
rgb_test('yellow') # Fails
Type enforcer can enforce class instances and classes. There are a few caveats between the two.
To enforce a class instance, simply pass the class itself as a type hint:
import type_enforced
class Foo():
def __init__(self) -> None:
pass
@type_enforced.Enforcer
class my_class():
def __init__(self, object: Foo) -> None:
self.object = object
x=my_class(Foo()) # Works great!
y=my_class(Foo) # Fails!
Notice how an initialized class instance Foo()
must be passed for the enforcer to not raise an exception.
To enforce an uninitialized class object use typing.Type[classHere]
on the class to enforce inputs to be an uninitialized class:
import type_enforced
import typing
class Foo():
def __init__(self) -> None:
pass
@type_enforced.Enforcer
class my_class():
def __init__(self, object_class: typing.Type[Foo]) -> None:
self.object = object_class()
y=my_class(Foo) # Works great!
x=my_class(Foo()) # Fails
import type_enforced
from type_enforced.utils import WithSubclasses
class Foo:
pass
class Bar(Foo):
pass
class Baz:
pass
@type_enforced.Enforcer
def my_fn(custom_class: WithSubclasses(Foo)):
pass
print(WithSubclasses.get_subclasses(Foo)) # Prints: [<class '__main__.Foo'>, <class '__main__.Bar'>]
my_fn(Foo()) # Passes as expected
my_fn(Bar()) # Passes as expected
my_fn(Baz()) # Raises TypeError as expected