Some useful Python decorators for cleaner software development.
Project description
pedantic-python-decorators
This packages includes many decorators that will make you write cleaner Python code.
Getting Started
This package requires Python 3.6.1 or later. There are multiple options for installing this package.
Option 1: Installing with pip from Pypi
Run pip install pedantic
.
Option 2: Installing with pip and git
- Install Git if you don't have it already.
- Run
pip install git+https://github.com/LostInDarkMath/pedantic-python-decorators.git@master
Option 3: Offline installation using wheel
- Download the latest release here by clicking on
pedantic-python-decorators-x.y.z-py-none-any.whl
. - Execute
pip install pedantic-python-decorators-x.y.z-py3-none-any.whl
.
The @pedantic decorator - Type checking at runtime
The @pedantic
decorator does the following things:
- The decorated function can only be called by using keyword arguments. Positional arguments are not accepted.
- The decorated function must have type annotations.
- Each time the decorated function is called, pedantic checks that the passed arguments and the return value of the function matches the given type annotations.
As a consequence, the arguments are also checked for
None
, becauseNone
is only a valid argument, if it is annotated viatyping.Optional
. - If the decorated function has a docstring which lists the arguments, the docstring is parsed and compared with the type annotations. In other words, pedantic ensures that the docstring is everytime up-to-date. Currently, only docstrings in the Google style are supported.
In a nutshell:
@pedantic
raises an PedanticException
if one of the following happened:
- The decorated function is called with positional arguments.
- The function has no type annotation for their return type or one or more parameters do not have type annotations.
- A type annotation is incorrect.
- A type annotation misses type arguments, e.g.
typing.List
instead oftyping.List[int]
. - The documented arguments do not match the argument list or their type annotations.
Minimal example
from typing import Union, List
from pedantic import pedantic, pedantic_class
@pedantic
def get_sum_of(values: List[Union[int, float]]) -> Union[int, float]:
return sum(values)
@pedantic_class
class MyClass:
def __init__(self, x: float, y: int) -> None:
self.x = x
self.y = y
def print_sum(self) -> None:
print(get_sum_of(values=[self.x, self.y]))
m = MyClass(x=3.14, y=2)
m.print_sum()
The @validate decorator
As the name suggests, with @validate
you are able to validate the values that are passed to the decorated function.
That is done in a highly customizable way.
But the highest benefit of this decorator is that it makes it extremely easy to write decoupled easy testable, maintainable and scalable code.
The following example shows the decoupled implementation of a configurable algorithm with the help of @validate
:
import os
from dataclasses import dataclass
from pedantic import validate, ExternalParameter, overrides, Validator, ValidationError
@dataclass(frozen=True)
class Configuration:
iterations: int
max_error: float
class ConfigurationValidator(Validator):
@overrides(Validator)
def validate(self, value: Configuration) -> Configuration:
if value.iterations < 1 or value.max_error < 0:
raise ValidationError(f'Invalid configuration: {value}')
return value
class ConfigFromEnvVar(ExternalParameter):
""" Reads the configuration from environment variables. """
@overrides(ExternalParameter)
def load_value(self) -> Configuration:
return Configuration(
iterations=int(os.environ['iterations']),
max_error=float(os.environ['max_error']),
)
class ConfigFromFile(ExternalParameter):
""" Reads the configuration from a config file. """
@overrides(ExternalParameter)
def load_value(self) -> Configuration:
with open(file='config.csv', mode='r') as file:
content = file.readlines()
return Configuration(
iterations=int(content[0].strip('\n')),
max_error=float(content[1]),
)
# choose your configuration source here:
@validate(ConfigFromEnvVar(name='config', validators=[ConfigurationValidator()]), strict=False)
# @validate(ConfigFromFile(name='config', validators=[ConfigurationValidator()]), strict=False)
# with strict_mode = True (which is the default)
# you need to pass a Parameter for each parameter of the decorated function
# @validate(Parameter(name='value') ConfigFromFile(name='config', validators=[ConfigurationValidator()]))
def my_algorithm(value: float, config: Configuration) -> float:
"""
This method calculates something that depends on the given value with considering the configuration.
Note how well this small piece of code is designed:
- Fhe function my_algorithm() need a Configuration but has no knowledge where this come from.
- Furthermore, it need does not care about parameter validation.
- The ConfigurationValidator doesn't now anything about the creation of the data.
- The @validate decorator is the only you need to change, if you want a different configuration source.
"""
print(value)
print(config)
return value
if __name__ == '__main__':
# we can call the function with a config like there is no decorator.
# This makes testing extremely easy: no config files, no environment variables or stuff like that
print(my_algorithm(value=2, config=Configuration(iterations=3, max_error=4.4)))
os.environ['iterations'] = '12'
os.environ['max_error'] = '3.1415'
# but we also can omit the config and load it implicitly by our custom Parameters
print(my_algorithm(value=42.0))
List of all decorators in this package
- @count_calls
- @deprecated
- @does_same_as_function
- @for_all_methods
- @mock
- @overrides
- @pedantic
- @pedantic_require_docstring
- @pedantic_class
- @pedantic_class_require_docstring
- @rename_kwargs
- @require_kwargs
- @timer
- @timer_class
- @trace
- @trace_class
- @trace_if_returns
- @unimplemented
- @validate
Dependencies
Outside the Python standard library, the following dependencies are used:
To use the FlaskParameter
class or subclasses, you obviously need to have Flask
installed.
Contributing
Feel free to contribute by submitting a pull request :)
Acknowledgments
Risks and side effects
The usage of decorators may affect the performance of your application.
For this reason, I would highly recommend you to disable the decorators if your code runs in a productive environment.
You can disable pedantic
by set an environment variable:
export ENABLE_PEDANTIC=0
You can also disable or enable the environment variables in your project by calling a method:
from pedantic import enable_pedantic, disable_pedantic
enable_pedantic()
Don't forget to check out the documentation. Happy coding!
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.