Package: pedantic
pedantic-python-decoratorsThis packages includes many decorators that will make you write cleaner Python code.Getting StartedThis package requires Python 3.11 or later.
There are multiple options for installing this package.Option 1: Installing with pip fromPypiRunpip install pedantic.Option 2: Installing with conda fromconda-forgeRunconda install -c conda-forge pedanticOption 3: Installing with pip and gitInstallGitif you don't have it already.Runpip install git+https://github.com/LostInDarkMath/pedantic-python-decorators.git@masterOption 4: Offline installation using wheelDownload thelatest release hereby clicking onpedantic-python-decorators-x.y.z-py-none-any.whl.Executepip install pedantic-python-decorators-x.y.z-py3-none-any.whl.The@pedanticdecorator - Type checking at runtimeThe@pedanticdecorator does the following things:The decorated function can only be called by using keyword arguments. Positional arguments are not accepted.The decorated function must havetype 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 forNone, becauseNoneis only a valid argument, if it is annotated viatyping.Optional.In a nutshell:@pedanticraises anPedanticExceptionif 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.Listinstead oftyping.List[int].Minimal examplefrompedanticimportpedantic@pedanticdefget_sum_of(values:list[int|float])->int:returnsum(values)get_sum_of(values=[0,1.2,3,5.4])# this raises the following runtime error:# Type hint of return value is incorrect: Expected type <class 'int'> but 10.0 of type <class 'float'> was the return value which does not match.The@validatedecoratorAs the name suggests, with@validateyou 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:importosfromdataclassesimportdataclassfrompedanticimportvalidate,ExternalParameter,overrides,Validator,Parameter,Min,ReturnAs@dataclass(frozen=True)classConfiguration:iterations:intmax_error:floatclassConfigurationValidator(Validator):@overrides(Validator)defvalidate(self,value:Configuration)->Configuration:ifvalue.iterations<1orvalue.max_error<0:self.raise_exception(msg=f'Invalid configuration:{value}',value=value)returnvalueclassConfigFromEnvVar(ExternalParameter):""" Reads the configuration from environment variables. """@overrides(ExternalParameter)defhas_value(self)->bool:return'iterations'inos.environand'max_error'inos.environ@overrides(ExternalParameter)defload_value(self)->Configuration:returnConfiguration(iterations=int(os.environ['iterations']),max_error=float(os.environ['max_error']),)classConfigFromFile(ExternalParameter):""" Reads the configuration from a config file. """@overrides(ExternalParameter)defhas_value(self)->bool:returnos.path.isfile('config.csv')@overrides(ExternalParameter)defload_value(self)->Configuration:withopen(file='config.csv',mode='r')asfile:content=file.readlines()returnConfiguration(iterations=int(content[0].strip('\n')),max_error=float(content[1]),)# choose your configuration source here:@validate(ConfigFromEnvVar(name='config',validators=[ConfigurationValidator()]),strict=False,return_as=ReturnAs.KWARGS_WITH_NONE)# @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', validators=[Min(5, include_boundary=False)]),#     ConfigFromFile(name='config', validators=[ConfigurationValidator()]),# )defmy_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 doesn't care about parameter validation.- The ConfigurationValidator doesn't know 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)returnvalueif__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 thatprint(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 Parametersprint(my_algorithm(value=42.0))List of all decorators in this package@count_calls@deprecated@does_same_as_function@frozen_dataclass@frozen_type_safe_dataclass@for_all_methods@in_subprocess@mock@overrides@pedantic@pedantic_class@rename_kwargs@require_kwargs@retry@timer@timer_class@trace@trace_class@trace_if_returns@unimplemented@validateList of all mixins in this packageGenericMixinWithDecoratedMethodsDependenciesThere are no hard dependencies. But if you want to use some advanced features you need to install the following packages:multiprocessif you want to use the@in_subprocessdecoratorflaskif you want to you the request validators which are designed forFlask(see unit tests for examples):FlaskParameter(abstract class)FlaskJsonParameterFlaskFormParameterFlaskPathParameterFlaskGetParameterFlaskHeaderParameterGenericFlaskDeserializerContributingFeel free to contribute by submitting a pull request :)AcknowledgmentsRathaustreppeAran-Feyuser395760Risks and side effectsThe 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 disablepedanticby set an environment variable:export ENABLE_PEDANTIC=0You can also disable or enable the environment variables in your project by calling a method:frompedanticimportenable_pedantic,disable_pedanticenable_pedantic()Issues with compiled Python codeThis package isnotcompatible with compiled source code (e.g. withNuitka).
That's because it uses theinspectmodule from the standard library which will raise errors likeOSError: could not get source codein case of compiled source code.Don't forget to check out thedocumentation.
Happy coding!