Decorator which checks whether the function is called with the correct type of parameters
Project description
Strong Typing
Decorator which checks at Runtime whether the function is called with the correct type of parameters.
And raises TypeMisMatch if the used parameters in a function call where invalid.
Included decorators
from strongtyping.strong_typing import | description |
---|---|
match_typing | decorator for a function |
match_class_typing | decorator for a class |
setter | property decorator for a setter function |
getter_setter | property decorator for get and setter function |
from strongtyping.docstring_typing import | description |
---|---|
match_docstring | decorator for a function |
match_class_docstring | decorator for a class |
setter | property decorator for a setter function |
getter_setter | property decorator for get and setter function |
Configuration
description | |
---|---|
severity_level | set global severity level |
Additional features
from strongtyping.type_namedtuple import | description |
---|---|
typed_namedtuple | an extension of the original namedtuple |
Booster feature | description |
---|---|
strongtpying_module | a Cython package to speed up the type checking |
The problem:
- Highlighting
- Some IDE's will/can highlight that one of the parameters in a function call doesn't match but you can execute the function.
- Exception??
- When the call raise an Exception then we know what to do but sometimes we don't get an Exception only a weird result.
def multipler(a: int, b: int):
return a * b
product = multipler(3, 4)
# >>> 12
product_2 = multipler('Hello', 'World') # Will be highlighted in some IDE's
# >>> TypeError
product_3 = multipler('Hello', 4)
# >>> 'HelloHelloHelloHello'
# No Exception but the result isn’t really what we expect
Now we can say that we will check the types in the function body to prevent this.
def multipler(a: int, b: int):
if isinstance(a, int) and isinstance(b, int):
return a * b
...
But when your function needs a lot of different parameters with different types you have to create a lot of noising code.
And why should we then use typing in our parameters??
My solution:
I created a decorator called @match_typing which will check at runtime if the parameters which will be used when
calling this function is from the same type as you have defined.
Here are some examples from my tests
# more imports
from strongtyping.strong_typing import match_typing
@match_typing
def func_a(a: str, b: int, c: list):
...
func_a('1', 2, [i for i in range(5)])
# >>> True
func_a(1, 2, [i for i in range(5)])
# >>> will raise a TypeMismatch Exception
@match_typing
def func_e(a: List[Union[str, int]], b: List[Union[str, int, tuple]]):
return f'{len(a)}-{len(b)}'
func_e([1, '2', 3, '4'], [5, ('a', 'b'), '10'])
# >>> '4-3'
func_e([5, ('a', 'b'), '10'], [1, '2', 3, datetime.date])
# >>> will raise a TypeMismatch Exception
I love python and his freedom but with the new option of adding type hints I wanted to get rid of writing
if isinstance(value, whatever)
in my programs.In a bigger project, it happened that some developers used a tiny IDE and others a more advanced one which highlighted typing issues. And there the trouble began, we had a bug and after a long debugging session we found out that the issue was a wrong type of an argument, it doesn't crash the program but the output was not what anyone of us had expected.
And that only encouraged me even more to tackle this problem.
Getting Started
- normal decorator
from strongtyping.strong_typing import match_typing
@match_typing
def foo_bar(a: str, b: int, c: list):
...
- class method decorator
from strongtyping.strong_typing import match_typing
class Foo:
...
@match_typing
def foo_bar(self, a: int):
...
- use a mix of typed and untyped parameters but then only the typed parameters are checked on runtime
from strongtyping.strong_typing import match_typing
@match_typing
def foo_bar(with_type_a: str, without_type_a, with_type_b: list, without_type_b):
...
# no exception
foo_bar('hello', 'world', [1, 2, 3], ('a', 'b'))
# will raise an exception
foo_bar(123, 'world', [1, 2, 3], ('a', 'b'))
- add your own exception
from strongtyping.strong_typing import match_typing
class SomeException(Exception):
pass
@match_typing(excep_raise=SomeException)
def foo_bar(with_type_a: str, without_type_a, with_type_b: list, without_type_b):
...
- enable internal cache with cache_size = 1
from strongtyping.strong_typing import match_typing
class MyClass:
pass
@match_typing(cache_size=1)
def foo_bar(a: tuple, b: MyClass):
...
- disable Exception
- You can also disable the raise of an Exception and get a warning instead this means your function will
execute even when the parameters are wrong use only when you know what you're doing
- You can also disable the raise of an Exception and get a warning instead this means your function will
from strongtyping.strong_typing import match_typing
@match_typing(excep_raise=None)
def multipler(a: int, b: int):
return a * b
print(multipler('Hello', 4))
"""
/StrongTyping/strongtyping/strong_typing.py:208: RuntimeWarning: Incorrect parameters: a: <class 'int'>
warnings.warn(msg, RuntimeWarning)
HelloHelloHelloHello
"""
At the current state, it will work with
- builtin types like: str, int, tuple etc
- from typing:
- List
- Tuple
- Union also nested ( Tuple[Union[str, int], Union[list, tuple]] )
- Any
- Dict
- Set
- Type
- Iterator
- Callable
- Generator
- Literal
- from types:
- FunctionType
- MethodType
- with string types representation like
from strongtyping.strong_typing import match_typing
class A:
@match_typing
def func_a(self, a: 'A'):
...
match_class_typing
- this decorator will cover each class function automatically with the match_typing decorator
from strongtyping.strong_typing import match_class_typing
@match_class_typing
class Dummy:
attr = 100
def a(self, val: int):
return val * .25
def b(self):
return 'b'
def c(self):
return 'c'
def _my_secure_func(self, val: Union[int, float], other: 'Dummy'):
return val * other.attr
- this decorator supports also disabling of raising Exception and internal caching
from strongtyping.strong_typing import match_class_typing
@match_class_typing(excep_raise=None)
class Dummy:
attr = 100
def a(self, val: int):
return val * 3
def b(self):
return 'b'
def c(self):
return 'c'
def _my_secure_func(self, val: Union[int, float], other: 'Dummy'):
return val * other.attr
- single class methods inside of a allready decorated class can be overwritten with the match_typing decorator
from strongtyping.strong_typing import match_class_typing
from strongtyping.strong_typing import match_typing
@match_class_typing
class Dummy:
attr = 100
@match_typing(excep_raise=None) # this decorator will be used
def a(self, val: int):
return val * 3
def b(self):
return 'b'
def c(self):
return 'c'
def _my_secure_func(self, val: Union[int, float], other: 'Dummy'):
return val * other.attr
setter
this decorator can replace your @foo.setter from property and check your typing
- this is an extension of easy_property
from strongtyping.strong_typing import getter
from strongtyping.strong_typing import setter
class Dummy:
attr = 100
val = 'foo'
@getter
def b(self):
return self.val
@setter
def b(self, val: str):
self.val = val
d = Dummy()
d.b == 'foo' # will raise AttributeError
d.b = 'bar' # works like a charm
d.b = 1 # will raise TypeMisMatch
getter_setter
this decorator can replace @propery from property and check your typing
- this is an extension of easy_property
from strongtyping.strong_typing import getter_setter
class Dummy:
attr = 100
@getter_setter # here you will have all in one place (DRY)
def c(self, val: int = None):
if val is not None:
self.attr = val
return self.attr
d = Dummy()
d.c == 100 # works like a charm
d.c = 1 # works like a charm
d.c = 'foobar' # will raise TypeMisMatch
reST docstrings
When working with docstrings in reST style format use the decorator match_docstring
from strongtyping.docstring_typing import match_docstring
@match_docstring
def func_a(a):
"""
:param a:
:type a: list
...
"""
@match_docstring
def func_a(a, b):
"""
:param int a: foo
:vartype b: str
...
"""
@match_docstring
def func_a(a, b):
"""
:parameter int a: foo
:argument str b: bar
...
"""
At the current state, it will work with basically everything which is written here https://gist.github.com/jesuGMZ/d83b5e9de7ccc16f71c02adf7d2f3f44
- extended with support for
- Iterator
- Callable
- Generator
- FunctionType
- MethodType
please check tests/test_typing to see what is supported and if something is missing feel free to create an issue.
match_class_docstring
- this decorator will cover each class function automatically with the match_docstring decorator
from strongtyping.docstring_typing import match_class_docstring
@match_class_docstring
class Dummy:
attr = 100
def a(self, val: int):
"""
:param int val: foo
"""
return val * .25
def b(self):
return 'b'
def c(self):
return 'c'
def _my_secure_func(self, val, other):
"""
:param val: foo
:type val: int or float
:param other: Dummy
:return:
"""
return val * other.attr
- this decorator supports also disabling of raising Exception and internal caching
from strongtyping.docstring_typing import match_class_docstring
@match_class_docstring(excep_raise=None)
class Dummy:
attr = 100
def a(self, val: int):
"""
:param int val: foo
"""
return val * 5
def b(self):
return 'b'
def c(self):
return 'c'
def _my_secure_func(self, val, other):
"""
:param val: foo
:type val: int or float
:param other: this class
:type other: Dummy
:return:
"""
return val * other.attr
- single class methods inside of a allready decorated class can be overwritten with the match_docstring decorator
from strongtyping.docstring_typing import match_class_docstring
from strongtyping.docstring_typing import match_docstring
@match_class_docstring
class Other:
attr = 100
def a(self, val: int):
"""
:param int val: foo
"""
return val * 2
def b(self):
return 'b'
def c(self):
return 'c'
@match_docstring(excep_raise=None) # this decorator will be used
def _my_secure_func(self, other):
"""
:param other: instance of same class
:type other: Other
:return:
"""
return 2 * other.attr
docstring_typing_setter
this decorator can replace your @foo.setter from property and check your typing
- this is an extension of easy_property
from strongtyping.docstring_typing import getter
from strongtyping.docstring_typing import setter
class Dummy:
attr = 100
val = 'foo'
@getter
def b(self):
return self.val
@setter
def b(self, val):
"""
:parameter int val: foo
"""
self.val = val
d = Dummy()
d.b == 'foo' # will raise AttributeError
d.b = 'bar' # works like a charm
d.b = 1 # will raise TypeMisMatch
docstring_typing_getter_setter
this decorator can replace @propery from property and check your typing
- this is an extension of easy_property
from strongtyping.docstring_typing import getter_setter
class Dummy:
attr = 100
@getter_setter # here you will have all in one place (DRY)
def c(self, val=None):
"""
:parameter int val: foo
"""
if val is not None:
self.attr = val
return self.attr
d = Dummy()
d.c == 100 # works like a charm
d.c = 1 # works like a charm
d.c = 'foobar' # will raise TypeMisMatch
typed_namedtuple
- typed_namedtuple is only available from python >= 3.8
- from my side it was only logical to create an own version of namedtuple with typing support
- for typing use the reST-style
from strongtyping.type_namedtuple import typed_namedtuple
Dummy = typed_namedtuple('Dummy', 'spell:str, mana:int or str,effect:list')
Dummy(mana='Lumos', spell=5, effect='Makes light') # will raise a TypeError
Dummy(spell='Lumos', mana=5, effect=['Makes light', ]) # works like a charm
- the same happens also with wrong default values (the typed_namedtuple needs the exact same length)
from strongtyping.type_namedtuple import typed_namedtuple
# will raise a TypeError
Dummy = typed_namedtuple('Dummy', ['spell:str', 'mana:int', 'effect:list'], defaults=[0, '', ''])
- and also when you try to replace an attribute with a wrong type
from strongtyping.type_namedtuple import typed_namedtuple
Dummy = typed_namedtuple('Dummy', ['spell:str', 'mana:int', 'effect:list'])
d = Dummy('Lumos', mana=5, effect=['Makes light', ])
d._replace(effect=b'Makes light') # will raise a TypeError
- it is also possible to use the typing.NamedTuple way for instantiating
from strongtyping.type_namedtuple import typed_namedtuple
Dummy = typed_namedtuple('Dummy', [('spell', str), ('mana', int), ('effect', Union[list, tuple])])
Dummy('Papyrus Reparo', 10, {1, 2, 3}) # will raise a TypeError
# when using this way you will also have the __annototaions__
print(Dummy.__annotations__)
# {'spell': <class 'str'>, 'mana': <class 'int'>, 'effect': typing.Union[list, tuple]}
- the docstring will also display the types of the parameter in the reST-style
from strongtyping.type_namedtuple import typed_namedtuple
Dummy = typed_namedtuple('Dummy', 'spell:str, mana:int or str,effect:list')
print(help(Dummy))
"""
class Dummy(builtins.tuple)
| Dummy(*args, **kwargs)
|
| Dummy(spell, mana, effect)
| :type spell: str
| :type mana: int or str
| :type effect: list
...
"""
strongtpying_module
-
A package written by my own in Cython to speed up the time for checking the parameters
with this package you can achieve a boost by the factor 3 and higher -
you only need to install this package via
pip install strongtyping-modules
-
for a detailed information please checkout the readme from strongtyping_modules
severity_level
- to set the project wide settings add in your .env file
- the options are: 'enabled', 'warning', 'disable'
ST_SEVERITY='disable'
- or place this somewhere in your project
from strongtyping.config import set_severity_level
from strongtyping.config import SEVERITY_LEVEL
set_severity_level(SEVERITY_LEVEL.WARNING)
- you can also override the project wide setting for particular classes or functions
from strongtyping.config import SEVERITY_LEVEL
from strongtyping.strong_typing import match_class_typing
from strongtyping.strong_typing import match_typing
@match_class_typing(severity=SEVERITY_LEVEL.WARNING)
class OtherDummy:
...
@match_typing(severity=SEVERITY_LEVEL.ENABLED)
def a(value: int):
...
Package
Tested for Versions
- 3.7, 3.8, 3.9
Installing
- pip install strongtyping
Versioning
- For the versions available, see the tags on this repository.
Authors
- Felix Eisenmenger
License
- This project is licensed under the MIT License - see the LICENSE.md file for details
Special thanks
- Thanks to Ruud van der Ham for helping me to improve my code and for his easy_property package
- Thanks to Dan Bader who puts this package into PyCoder’s Weekly Issue #428 (July 7, 2020)
- And all how gave me Feedback in the Pythonista Cafe
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file strongtyping-2.0.0a0.tar.gz
.
File metadata
- Download URL: strongtyping-2.0.0a0.tar.gz
- Upload date:
- Size: 26.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.1 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.0 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.8.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | fd608a5e8fd171ad0862fa8d114f2c8414aa863ea5f64814e176f9618dfa2bfc |
|
MD5 | cbcaf9424e78638643efa89087851ba3 |
|
BLAKE2b-256 | f5b0a593f7d407bdafc4948b07f3572b4bd6bd82ebaf84d49f5624a90be8fdab |
File details
Details for the file strongtyping-2.0.0a0-py3-none-any.whl
.
File metadata
- Download URL: strongtyping-2.0.0a0-py3-none-any.whl
- Upload date:
- Size: 29.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.1 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.0 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.8.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6f4302aeaddc73fd43ac97fe1488882252886100b082527a03478bbafb916f9b |
|
MD5 | 3703563a92b8f3ba8702e64ecf025428 |
|
BLAKE2b-256 | b7ed5367a3a365cdf3d1e1435b7059c770f8d00536e275b29cdc45be8d9db540 |