Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/zeekay/decorum
Python decorator helper library.
https://github.com/zeekay/decorum
Last synced: about 2 months ago
JSON representation
Python decorator helper library.
- Host: GitHub
- URL: https://github.com/zeekay/decorum
- Owner: zeekay
- License: mit
- Created: 2011-05-13T22:05:57.000Z (over 13 years ago)
- Default Branch: master
- Last Pushed: 2017-04-01T02:27:36.000Z (over 7 years ago)
- Last Synced: 2024-10-12T10:26:56.798Z (2 months ago)
- Language: Python
- Homepage:
- Size: 43 KB
- Stars: 14
- Watchers: 2
- Forks: 2
- Open Issues: 3
-
Metadata Files:
- Readme: README.rst
- Changelog: CHANGES
- Contributing: CONTRIBUTING.rst
- License: LICENSE
- Authors: AUTHORS
Awesome Lists containing this project
README
=======
Decorum
=======.. image:: https://travis-ci.org/zeekay/decorum.png?branch=master
:target: https://travis-ci.org/zeekay/decorumDecorum is a library which aims to make it easier to write flexible and simple
decorators:* use decorators just like you need: don't bother with parentheses, pass
options when you need them.* write decorators just like you think: they get initialized with or without
options, they wrap a function, they are callables.* test decorators: it's really easy, you have no excuse!
Inherit from Decorum
====================In order to write your own decorators, just subclass ``decorum.Decorum``:
.. code:: pycon
>>> from decorum import Decorum
>>> class empty_decorator(Decorum):
... """Just inherit from Decorum's builtins."""Of course you will want to customize decorator's behaviour! There are only
three methods to be aware of:* ``init()`` setups the decorator itself;
* ``wraps()`` decorates the function;
* ``call()`` runs the wrapped function and returns result.
Here is a simple decorator to illustrate Decorum usage:
.. code:: pycon
>>> from __future__ import print_function
>>> class verbose_decorator(Decorum):
... """Print info about decoration process."""
... def init(self, *args, **kwargs):
... print('Initializing decorator with args={} and kwargs={}'
... .format(args, kwargs))
... return super(verbose_decorator, self).init(*args, **kwargs)
...
... def wraps(self, f):
... print('Wrapping function "{}"'.format(f.__name__))
... return super(verbose_decorator, self).wraps(f)
...
... def call(self, *args, **kwargs):
... print('Running wrapped function with args={} and kwargs={}'
... .format(args, kwargs))
... return super(verbose_decorator, self).call(*args, **kwargs)Then you can use it as any decorator:
.. code:: pycon
>>> @verbose_decorator
... def foo(x):
... print(x)
Initializing decorator with args=() and kwargs={}
Wrapping function "foo">>> foo('bar')
Running wrapped function with args=('bar',) and kwargs={}
bar.. note::
You can also use ``decorum.decorator`` to turn classes into decorators.
.. code:: pycon
>>> from decorum import decorator
>>> @decorator
... class noop:
... """Override init(), wraps() or call() if you like.""">>> @noop
... def foo():
... """Do nothing."""The result is a class that inherits from the original class and Decorum:
.. code:: pycon
>>> isinstance(foo, noop)
True
>>> isinstance(foo, Decorum)
TrueDon't bother with parentheses
=============================Decorum lets you write decorators with and without arguments in a unified way.
Then your decorator can be used with or without arguments, called or not, and
it will work the same way:.. code:: pycon
>>> @verbose_decorator
... def foo(x):
... print(x)
Initializing decorator with args=() and kwargs={}
Wrapping function "foo"Is identical to:
.. code:: pycon
>>> @verbose_decorator()
... def foo(x):
... print(x)
Initializing decorator with args=() and kwargs={}
Wrapping function "foo"Initialize decorator with options
=================================To implement a decorator that accepts a custom options, just change ``init()``:
.. code:: pycon
>>> class configurable_decorator(Decorum):
... def init(self, custom_option='default', *args, **kwargs):
... print('Initializing decorator with custom_option="{}"'
... .format(custom_option))
... # Remember the option, typically for use in wraps() or call().
... self.custom_option = custom_option
... # Call super().init() with remaining arguments.
... return super(configurable_decorator, self).init(*args, **kwargs)As we used a keyword argument, it is optional:
.. code:: pycon
>>> @configurable_decorator
... def foo(x):
... print(x)
Initializing decorator with custom_option="default">>> foo.custom_option
'default'And we can pass this option when decorating a function. Either as positional
argument..... code:: pycon
>>> @configurable_decorator('positional')
... def foo(x):
... print(x)
Initializing decorator with custom_option="positional">>> foo.custom_option
'positional'... or keyword argument:
.. code:: pycon
>>> @configurable_decorator(custom_option='keyword')
... def foo(x):
... print(x)
Initializing decorator with custom_option="keyword">>> foo.custom_option
'keyword'Of course, you cannot pass arguments that are not declared as ``init()``
options:.. code:: pycon
>>> @configurable_decorator(wrong_option=True) # doctest: +ELLIPSIS
... def foo(x):
... print(x)
Traceback (most recent call last):
...
TypeError: init() got an unexpected keyword argument 'wrong_option'.. note::
In most cases, ``init()`` should accept additional arguments and and proxy
them to parent via ``super(...).init(*args, **kwargs)``. This way, options
of ancestor classes are supported.As an example, ``Decorum`` base class declares ``assigned`` keyword argument
in ``init()`` (see section `wrap function <#wrap-function>`_ below).Wrap function
=============The ``wraps()`` method allows you to handle the decorated function. It receives
the function to decorate as single positional argument, and returns a callable
(typically ``self``).In most cases, ``wraps()`` function will ``return super(..., self).wraps(f)``.
By default, the base ``Decorum.wraps()`` will try to keep assign certain
attributes to the wrapped function for you, namely ``__doc__`` and
``__name__``. This feature uses ``functools.wraps``... code:: pycon
>>> class identity(Decorum):
... """Noop decorator: does nothing!""">>> @identity
... def my_function():
... """My function's docstring.""">>> print(my_function.__name__)
my_function
>>> print(my_function.__doc__)
My function's docstring.The optional ``assigned`` keyword argument can be used to specify which
attributes of the original function are assigned directly to the matching
attributes on the wrapper function. This defaults to
``functools.WRAPPER_ASSIGNMENTS``. You can specify ``False`` or ``None`` to
disable this... code:: pycon
>>> @identity(assigned=None)
... def my_function():
... """My function's docstring.""">>> print(my_function.__name__)
identity
>>> print(my_function.__doc__)
Noop decorator: does nothing!Run decorated function
======================``Decorum.call()`` method receives ``*args`` and ``**kwargs`` as input. It runs
the wrapped function, and returns the result.Here is a simple decorator that repeats the result of decorated function:
.. code:: pycon
>>> class repeat(Decorum):
... def call(self, *args, **kwargs):
... result = super(repeat, self).call(*args, **kwargs)
... return ' '.join([result] * 2)>>> @repeat
... def parrot(word):
... return word>>> parrot('hello')
'hello hello'Test decorators
===============Decorum makes it easy to test custom decorators.
Assert a decorator has expected behaviour
-----------------------------------------Decorators are defined as classes, so you have fine-grained control over what
you test. And your tests can focus on what you customized.Let's check ``repeat`` decorator from the section before. Since we just
overrode ``call()``, let's focus on it:.. code:: pycon
>>> decorator = repeat(lambda x: x.upper())
>>> result = decorator.call('input')
>>> assert result == 'INPUT INPUT'That's quite useful to unit test decorators.
Assert a function has been decorated
------------------------------------Decorators are instances of ``Decorum`` or subclasses. So you can inspect
decorated functions.Let's inspect ``my_function`` from the examples above:
.. code:: pycon
>>> assert isinstance(my_function, Decorum)
>>> assert isinstance(my_function, identity)Known limitations
=================Decorum has little known limitations:
* your decorator's ``init()`` cannot receive a single positional argument that
is a callable. Decorum will try trigger ``wraps()`` instead of ``init()`` in
such a case.About
=====Decorum project is free software, published under MIT license.
* PyPI: https://pypi.python.org/pypi/Decorum
* Code repository: https://github.com/zeekay/decorum
* Bugtracker: https://github.com/zeekay/decorum/issues
* Continuous integration: https://travis-ci.org/zeekay/decorum