SkillAgentSearch skills...

Pydecor

Easy peasy Python decorators

Install / Use

/learn @mplanchard/Pydecor
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

PyDecor

.. image:: https://travis-ci.org/mplanchard/pydecor.svg?branch=master :target: https://travis-ci.org/mplanchard/pydecor

.. image:: https://readthedocs.org/projects/pydecor/badge/?version=latest :target: https://pydecor.readthedocs.io/

Easy-peasy Python decorators!

  • GitHub: https://github.com/mplanchard/pydecor
  • PyPI: https://pypi.python.org/pypi/pydecor
  • Docs: https://pydecor.readthedocs.io/
  • Contact: msplanchard @ gmail or @msplanchard on Twitter

Summary

Decorators are great, but they're hard to write, especially if you want to include arguments to your decorators, or use your decorators on class methods as well as functions. I know that, no matter how many I write, I still find myself looking up the syntax every time. And that's just for simple function decorators. Getting decorators to work consistently at the class and method level is a whole 'nother barrel of worms.

PyDecor aims to make function decoration easy and straightforward, so that developers can stop worrying about closures and syntax in triply nested functions and instead get down to decorating!

.. contents:: Table of Contents

Quickstart

Install pydecor::

pip install pydecor

Use one of the ready-to-wear decorators:

.. code:: python

# Memoize a function

from pydecor import memoize


@memoize()
def fibonacci(n):
    """Compute the given number of the fibonacci sequence"""
    if n < 2:
        return n
    return fibonacci(n - 2) + fibonacci(n - 1)

print(fibonacci(150))

.. code:: python

# Intercept an error and raise a different one

from flask import Flask
from pydecor import intercept
from werkzeug.exceptions import InternalServerError


app = Flask(__name__)


@app.route('/')
@intercept(catch=Exception, reraise=InternalServerError,
           err_msg='The server encountered an error rendering "some_view"')
def some_view():
    """The root view"""
    assert False
    return 'Asserted False successfully!'


client = app.test_client()
response = client.get('/')

assert response.status_code == 500
assert 'some_view'.encode() in resp.data

Use a generic decorator to run your own functions @before, @after, or @instead of another function, like in the following example, which sets a User-Agent header on a Flask response:

.. code:: python

from flask import Flask, make_response
from pydecor import Decorated, after


app = Flask(__name__)

Decorated instances are passed to your functions and contain

information about the wrapped function, including its args,

kwargs, and result, if it's been called.

def set_user_agent(decorated: Decorated):
    """Sets the user-agent header on a result from a view"""
    resp = make_response(decorated.result)
    resp.headers.set('User-Agent', 'my_applicatoin')
    return resp


@app.route('/')
@after(set_user_agent)
def index_view():
    return 'Hello, world!'


client = app.test_client()
response = client.get('/')
assert response.headers.get('User-Agent') == 'my_application'

Or make your own decorator with construct_decorator

.. code:: python

from flask import reques
from pydecor import Decorated, construct_decorator
from werkzeug.exceptions import Unauthorized


def check_auth(_decorated: Decorated, request):
    """Theoretically checks auth.

    It goes without saying, but this is example code. You should
    not actually check auth this way!
    """
    if request.host != 'localhost':
        raise Unauthorized('locals only!')


authed = construct_decorator(before=check_auth)


app = Flask(__name__)


@app.route('/')
# Any keyword arguments provided to any of the generic decorators are
# passed directly to your callable.
@authed(request=request)
def some_view():
    """An authenticated view"""
    return 'This is sensitive data!'

Why PyDecor?

  • It's easy!

    With PyDecor, you can go from this:

    .. code:: python

    from functools import wraps from flask import request from werkzeug.exceptions import Unauthorized from my_pkg.auth import authorize_request

    def auth_decorator(request=None): """Check the passed request for authentication"""

      def decorator(decorated):
    
          @wraps(decorated)
          def wrapper(*args, **kwargs):
              if not authorize_request(request):
                raise Unauthorized('Not authorized!')
              return decorated(*args, **kwargs)
          return wrapper
    
      return decorator
    

    @auth_decorator(request=requst) def some_view(): return 'Hello, World!'

    to this:

    .. code:: python

    from flask import request from pydecor import before from werkzeug.exceptions import Unauthorized from my_pkg.auth import authorize_request

    def check_auth(_decorated, request=request): """Ensure the request is authorized""" if not authorize_request(request): raise Unauthorized('Not authorized!')

    @before(check_auth, request=request) def some_view(): return 'Hello, world!'

    Not only is it less code, but you don't have to remember decorator syntax or mess with nested functions. Full disclosure, I had to look up a decorator sample to be sure I got the first example's syntax right, and I just spent two weeks writing a decorator library.

  • It's fast!

    PyDecor aims to make your life easier, not slower. The decoration machinery is designed to be as efficient as is reasonable, and contributions to speed things up are always welcome.

  • Implicit Method Decoration!

    Getting a decorator to "roll down" to methods when applied to a class is a complicated business, but all of PyDecor's decorators provide it for free, so rather than writing:

    .. code:: python

    from pydecor import log_call

    class FullyLoggedClass(object):

      @log_call(level='debug')
      def some_function(self, *args, **kwargs):
          return args, kwargs
    
      @log_call(level='debug')
      def another_function(self, *args, **kwargs):
          return None
    
      ...
    

    You can just write:

    .. code:: python

    from pydecor import log_call

    @log_call(level='debug') class FullyLoggedClass(object):

      def some_function(self, *args, **kwargs):
          return args, kwargs
    
      def another_function(self, *args, **kwargs):
          return None
    
      ...
    

    PyDecor ignores special methods (like __init__) so as not to interfere with deep Python magic. By default, it works on any methods of a class, including instance, class and static methods. It also ensures that class attributes are preserved after decoration, so your class references continue to behave as expected.

  • Consistent Method Decoration!

    Whether you're decorating a class, an instance method, a class method, or a static method, you can use the same passed function. self and cls variables are stripped out of the method parameters passed to the provided callable, so your functions don't need to care about where they're used.

  • Lots of Tests!

    Seriously. Don't believe me? Just look. We've got the best tests. Just phenomenal.

Installation

pydecor 2.0 and forward supports only Python 3.6+!

If you need support for an older Python, use the most recent 1.x release.

To install pydecor, simply run::

pip install -U pydecor

To install the current development release::

pip install --pre -U pydecor

You can also install from source to get the absolute most recent code, which may or may not be functional::

git clone https://github.com/mplanchard/pydecor pip install ./pydecor

Details

Provided Decorators


This package provides generic decorators, which can be used with any function to provide extra utility to decorated resources, as well as prête-à-porter (ready-to-wear) decorators for immediate use.

While the information below is enough to get you started, I highly recommend checking out the decorator module docs_ to see all the options and details for the various decorators!

Generics


* ``@before`` - run a callable before the decorated function executes

  * called with an instance of `Decorated` and any provided kwargs

* ``@after`` - run a callable after the decorated function executes

  * called with an instance of `Decorated` and any provided kwargs

* ``@instead`` - run a callable in place of the decorated function

  * called with an instance of `Decorated` and any provided kwargs

* ``@decorate`` - specify multiple callables to be run before, after, and/or
  instead of the decorated function

  * callables passed to ``decorate``'s ``before``, ``after``, or ``instead``
    keyword arguments will be called with the same default function signature
    as described for the individual decorators, above. Extras will be
    passed to all provided callables.

* ``construct_decorator`` - specify functions to be run ``before``, ``after``,
  or ``instead`` of decorated functions. Returns a reusable decorator.

The callable passed to a generic decorator is expected to handle at least one
positional argument, which will be an instance of `Decorated`. `Decorated`
objects provide the following interface:

**Attributes:**

* `args`: a tuple of any positional arguments with which the decorated
  callable was called
* `kwargs`: a dict of any keyword arguments with which the decorated
  callable was called
* `wrapped`: a reference to the decorated callable
* `result`: when the _wrapped_ function has been called, its return value is
  stored here

**Methods**

* `__call__(*args, **kwargs)`: a shortcut to
  `decorated.wrapped(*args, **kwargs)`, calling an instance of `Decorated`
  calls t

Related Skills

View on GitHub
GitHub Stars32
CategoryDevelopment
Updated1y ago
Forks5

Languages

Python

Security Score

80/100

Audited on Jun 18, 2024

No findings