Package: decorator-args
This mini-library is far from being revolutionary or essential but its features may come in handy for some.
It is a chaotic set of ideas and features that I don’t really feel to be rock solid but I think it isn’t an
unforgivable guilt to release it to the wild. In worst case you can open a “Please destruct this code/library”
issue on its github page.ContentsInstallationUsageProblem to solveHow this library helpsLibrary interfaceMain “entrypoint”Helpers: convenience APIImplementing your decorators in a “twisted” wayWhen your decorator is a bound instance/class/static methodWhen your decorator is a bound __call__ magic (instance)methodInstallationpipinstalldecorator-argsAlternatively you can download the distribution from the following places:https://pypi.python.org/pypi/decorator-args#downloadshttps://github.com/pasztorpisti/decorator-args/releasesUsageProblem to solveThe following code snippet shows two examples:Applying a decorator calledarglesswithout any arguments.Applying a decorator calledargfulthat receives arguments before applying it.#1@arglessdefdecorated_function():...#2@argful('arg1_value',arg2='arg2_value')defdecorated_function():...This library tries to make it easier to implement decorators that receive arguments (likeargful_decoratorabove).
Besides this it has some related extra features to offer (optional and keyword-only decorator args).How this library helpsThe previously usedargfuldecorator can be implemented in countless ways but the two most standard ways to do it
without this library looks like this:“Inception-style” implementation as a function:defargful(arg1,arg2='arg2_default'):# TODO: Validate and pre-process decorator args as early as possible for easier debuggingdefdecorate(decoratable):@functools.wraps(decoratable)defwrapper(*args,**kwargs):# TODO: Manipulate the input and output of the wrapped# decoratable object and use arg1 and arg2 if you want...returndecoratable(*args,**kwargs)returnwrapperreturndecorateImplementation as a class:classargful(object):def__init__(self,arg1,arg2='arg2_default'):# TODO: Validate and pre-process decorator args as early as possible for easier debuggingself.arg1=arg1self.arg2=arg2def__call__(self,decoratable):@functools.wraps(decoratable)defwrapper(*args,**kwargs)# TODO: Manipulate the input and output of the wrapped# decoratable object and use self.arg1 and self.arg2 if you want...returndecoratable(*args,**kwargs)returnwrapperThedecorator_args.decorator_argsdecorator provided by this library can remove a level of indirection from the
“Inception-style” implementation seen in example #1 making the code simpler and more readable:fromdecorator_argsimportdecorator_args@decorator_argsdefargful(decoratable,arg1,arg2='arg2_default'):@functools.wraps(decoratable)defwrapper(*args,**kwargs):# TODO: Manipulate the input and output of the wrapped# decoratable object and use arg1 and arg2 if you want...returndecoratable(*args,**kwargs)returnwrapperAt the same time this library offers the following extra features:It can force keyword-only argument passing for your decorator. In some cases this is desirable because it can make
code easier to read and understand:# Decorator implementation with keyword-only decorator arguments:@decorator_args(keyword_only=True)defargful(decoratable,arg1,arg2='arg2_default'):...# This would fail with a ``TypeError('This decorator receives only keyword arguments')``@argful('arg1_value',arg2='arg2_value')defdecorated_function():...# This is OK because all args are passed as keyword args@argful(arg1='arg1_value',arg2='arg2_value')defdecorated_function():...If your decorator doesn’t have required arguments and you use theoptionalfeature of this library than you can
apply your decorator without an argument list when you don’t want to pass any args to it:# Decorator implementation with optional argument list:# Note that our decorator doesn't have required args other than the decoratable object:@decorator_args(optional=True)defargful(decoratable,arg1='arg1_default',arg2='arg2_default'):...# This works because of using `optional=True` above:@argfuldefdecorated_function():...# This would work even without `optional=True` in our decorator implementation:@argful()defdecorated_function():...# Of course passing actual args also works as expected:@argful('arg1_value','arg2_value')defdecorated_function():...Library interfaceThe library offers adecorator_args.decorator_argsdecorator that is the main “workhorse” of the library and a
set of other decorators that are just convenience helpers around the previously mentioned main decorator.
Syntax-wise the arguments of these decorators are optional and keyword-only.Main “entrypoint”decorator_args.decorator_args(*, keyword_only=False, optional=False, is_decoratable_object=None)The main decorator of the library. All other decorators are just convenience helpers based on this one.keyword_only: Makes the arguments of your decorator keyword-only. Passing any positional arguments to your
decorator will result in aTypeErrorwith an appropriate error message.optional:optional=Trueallows you to write@your_decoratorinstead of@your_decorator().
When you apply your decorator without passing any args to it you can omit the empty brackets
that specify the empty decorator argument list.is_decoratable_object: This argument can be used only whenkeyword_only=Falseandoptional=True.
When the argument list of your decorator is optional and you apply your decorator by passing only a single
positional argument to the decorator this library has hard time to decide whether that single positional argument
is an optional decorator argument or a decoratable object. This decision is made by the library functiondecorator_args.default_is_decoratable_object(obj)function which returnsTrueif the given single
positional argument is a function, method, or class. This default behavior is good in most of the cases when
your decorator receives only simple arguments like integers, strings, bools, etc… However if your decorator
can receive a single positional argument that can be a function, method, or class, then the default behavior
isn’t suitable. There are several workarounds to this problem, one of them is providing your ownis_decoratable_object(obj)implementation through the currently documented decorator argument. You probably
have additional info to make an accurate distinction between decorator arguments and decoratable objects to
provide a workingis_decoratable_object(obj)implementation.In such pathological edge-cases you can also use the following workarounds besides the previously documented
customis_decoratable_object(obj)implementation:When you apply your decorator with only a single argument that is a function/method/class you can
pass the argument as a keyword-argument. This way it will be detected as a decorator argument for sure.
This is however just a dirty hack that still leaves chance for the users of your decorator to make an
error. This can result in long debugging sessions.You can make your optional arguments keyword-only withkeyword_only=True.
This completely eliminates the problem.Don’t make the argument list of this decorator optional. With a required decorator argument list this
problem isn’t present.Helpers: convenience APIThe convenience API provides a set of decorators that are just “wrappers” around the maindecorator_args.decorator_argsdecorator. These convenience decorators just bind some of the main decorator
arguments to some constants.decorator_args.optional_decorator_args(*, keyword_only=False, is_decoratable_object=None)Works just like the maindecorator_args.decorator_argsdecorator withoptional=True.decorator_args.keyword_only_decorator_args(*, optional=False)Works just like the maindecorator_args.decorator_argsdecorator withkeyword_only=True.decorator_args.optional_keyword_only_decorator_args()Works just like the maindecorator_args.decorator_argsdecorator withoptional=Trueandkeyword_only=True.Implementing your decorators in a “twisted” wayThe tricky implementation of the library ensures that the decorators provided by this library can be applied to your
decorators even in some exotic cases:When your decorator is a bound instance/class/static methodWhen your decorator is a bound __call__ magic (instance)methodWhen your decorator is a bound instance/class/static methodclassAnyClass(object):@decorator_argsdefdecorator_when_bound(self,decoratable,arg1,arg2):...# It is important to apply @decorator_args after @classmethod!@decorator_args@classmethoddefdecorator_when_bound_2(cls,decoratable,arg1,arg2):...# It is important to apply @decorator_args after @statimethod!@decorator_args@staticmethoddefdecorator_when_bound_3(decoratable,arg1,arg2):...any_class_instance=AnyClass()decorator_with_args=any_class_instance.decorator_when_bounddecorator_with_args_2=AnyClass.decorator_when_bound_2decorator_with_args_3a=any_class_instance.decorator_when_bound_3decorator_with_args_3b=AnyClass.decorator_when_bound_3When your decorator is a bound __call__ magic (instance)methodclassAnyClass(object):@decorator_argsdef__call__(self,decoratable,arg1,arg2):...# Because of the syntactic sugar provided by python it is as simple as:decorator_with_args=AnyClass()