{ "info": { "author": "Matthew Planchard", "author_email": "msplanchard@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: POSIX :: Linux", "Programming Language :: Python" ], "description": "PyDecor\n=======\n\n.. image:: https://travis-ci.org/mplanchard/pydecor.svg?branch=master\n :target: https://travis-ci.org/mplanchard/pydecor\n\n.. image:: https://readthedocs.org/projects/pydecor/badge/?version=latest\n :target: https://pydecor.readthedocs.io/\n\nEasy-peasy Python decorators!\n\n* GitHub: https://github.com/mplanchard/pydecor\n* PyPI: https://pypi.python.org/pypi/pydecor\n* Docs: https://pydecor.readthedocs.io/\n* Contact: ``msplanchard`` ``@`` ``gmail`` or @msplanchard on Twitter\n\n\nSummary\n-------\n\nDecorators are great, but they're hard to write, especially if you want\nto include arguments to your decorators, or use your decorators on\nclass methods as well as functions. I know that, no matter how many I write,\nI still find myself looking up the syntax every time. And that's just for\nsimple function decorators. Getting decorators to work consistently at the\nclass and method level is a whole 'nother barrel of worms.\n\nPyDecor aims to make function decoration easy and straightforward, so that\ndevelopers can stop worrying about closures and syntax in triply nested\nfunctions and instead get down to decorating!\n\n\nIMPORTANT: Upcoming Backwards Incompatible Changes\n--------------------------------------------------\n\nVersion 2.0.0 will make some changes to the call signatures for functions\npassed to ``@before``, ``@after``, ``@instead``, ``@decorate``, and\n``construct_decorator``, as well as to the call signatures to the\ndecorators themselves.\n\nSpecifically, rather than defaulting the call signature to some subset\nof decorated function args, kwargs, result, and the decorated function\nitself and allowing overrides with keyword arguments\nto the decorator like ``pass_params``, all functions passed to ``@before``,\n``@after``, and ``@instead`` will receive an immutable ``Decorated``\nobject, which will have ``args``, ``kwargs``, ``wrapped``, and ``result``\nattributes, and which will support direct calls as though it were the\ndecorated function/method/class. The aim of this is to make writing functions\nto pass to the decorators more intuitive, but it will require some minor\nre-writing of passed functions.\n\nYou can experiment with this syntax and prepare for the cut-over right away\nby passing ``_use_future_syntax=True`` to any of your generic decorators\n(``@after``, ``@before``, etc.) or to ``construct_decorator``. See the below\nsnippet to illustrate basic use of the new ``Decorated`` object:\n\n.. code:: python\n\n from pydecor import after, Decorated\n\n def after_func(decorated: Decorated, extra_kwarg=None):\n \"\"\"A function to be called after the decorated function\"\"\"\n assert decorated.args == ('foo', )\n assert decorated.kwargs == {'bar': 'bar'}\n assert decorated.result == 'baz'\n assert extra_kwarg == 'extra_kwarg'\n\n\n @after(after_func, extra_kwarg='extra_kwarg')\n def some_function('foo', bar='bar'):\n \"\"\"A function that returns 'baz'\"\"\"\n return 'baz'\n\nAll of the builtin non-generic decorators (``@memoize``, ``@intercept``,\nand ``@log_call``) are already using the future syntax, so feel free\nto look at those for more examples.\n\nSee the API docs for more information.\n\n\n.. contents:: Table of Contents\n\n\nQuickstart\n----------\n\nInstall ``pydecor``::\n\n pip install pydecor\n\nUse one of the ready-to-wear decorators:\n\n.. code:: python\n\n # Memoize a function\n\n from pydecor import memoize\n\n\n @memoize()\n def fibonacci(n):\n \"\"\"Compute the given number of the fibonacci sequence\"\"\"\n if n < 2:\n return n\n return fibonacci(n - 2) + fibonacci(n - 1)\n\n print(fibonacci(150))\n\n\n.. code:: python\n\n # Intercept an error and raise a different one\n\n from flask import Flask\n from pydecor import intercept\n from werkzeug.exceptions import InternalServerError\n\n\n app = Flask(__name__)\n\n\n @app.route('/')\n @intercept(catch=Exception, reraise=InternalServerError,\n err_msg='The server encountered an error rendering \"some_view\"')\n def some_view():\n \"\"\"The root view\"\"\"\n assert False\n return 'Asserted False successfully!'\n\n\n client = app.test_client()\n response = client.get('/')\n\n assert response.status_code == 500\n assert 'some_view'.encode() in resp.data\n\n\nUse a generic decorator to run your own functions ``@before``, ``@after``,\nor ``@instead`` of another function, like in the following example,\nwhich sets a User-Agent header on a Flask response:\n\n.. code:: python\n\n from flask import Flask, make_response\n from pydecor import after\n\n\n app = Flask(__name__)\n\n\n def set_user_agent(view_result):\n \"\"\"Sets the user-agent header on a result from a view\"\"\"\n resp = make_response(view_result)\n resp.headers.set('User-Agent', 'my_applicatoin')\n return resp\n\n\n @app.route('/')\n @after(set_user_agent)\n def index_view():\n return 'Hello, world!'\n\n\n client = app.test_client()\n response = client.get('/')\n assert response.headers.get('User-Agent') == 'my_application'\n\n\nOr make your own decorator with ``construct_decorator``\n\n.. code:: python\n\n from flask import request\n from pydecor import construct_decorator\n from werkzeug.exceptions import Unauthorized\n\n\n def check_auth(request):\n \"\"\"Theoretically checks auth\n\n It goes without saying, but this is example code. You should\n not actually check auth this way!\n \"\"\"\n if request.host != 'localhost':\n raise Unauthorized('locals only!')\n\n\n authed = construct_decorator(before=check_auth)\n\n\n app = Flask(__name__)\n\n\n @app.route('/')\n @authed(request=request)\n def some_view():\n \"\"\"An authenticated view\"\"\"\n return 'This is sensitive data!'\n\n\nWhy PyDecor?\n------------\n\n* **It's easy!**\n\n With PyDecor, you can go from this:\n\n .. code:: python\n\n from functools import wraps\n from flask import request\n from werkzeug.exceptions import Unauthorized\n from my_pkg.auth import authorize_request\n\n def auth_decorator(request=None):\n \"\"\"Check the passed request for authentication\"\"\"\n\n def decorator(decorated):\n\n @wraps(decorated)\n def wrapper(*args, **kwargs):\n if not authorize_request(request):\n raise Unauthorized('Not authorized!')\n return decorated(*args, **kwargs)\n return wrapper\n\n return decorated\n\n @auth_decorator(request=requst)\n def some_view():\n return 'Hello, World!'\n\n to this:\n\n .. code:: python\n\n from flask import request\n from pydecor import before\n from werkzeug.exceptions import Unauthorized\n from my_pkg.auth import authorize_request\n\n def check_auth(request=request):\n \"\"\"Ensure the request is authorized\"\"\"\n if not authorize_request(request):\n raise Unauthorized('Not authorized!')\n\n @before(check_auth, request=request)\n def some_view():\n return 'Hello, world!'\n\n Not only is it less code, but you don't have to remember decorator\n syntax or mess with nested functions. Full disclosure, I had to look\n up a decorator sample to be sure I got the first example's syntax right,\n and I just spent two weeks writing a decorator library.\n\n* **It's fast!**\n\n PyDecor aims to make your life easier, not slower. The decoration machinery\n is designed to be as efficient as is reasonable, and contributions to\n speed things up are always welcome.\n\n* **Implicit Method Decoration!**\n\n Getting a decorator to \"roll down\" to methods when applied to a class is\n a complicated business, but all of PyDecor's decorators provide it for\n free, so rather than writing:\n\n .. code:: python\n\n from pydecor import log_call\n\n class FullyLoggedClass(object):\n\n @log_call(level='debug')\n def some_function(self, *args, **kwargs):\n return args, kwargs\n\n @log_call(level='debug')\n def another_function(self, *args, **kwargs):\n return None\n\n ...\n\n You can just write:\n\n .. code:: python\n\n from pydecor import log_call\n\n @log_call(level='debug')\n class FullyLoggedClass(object):\n\n def some_function(self, *args, **kwargs):\n return args, kwargs\n\n def another_function(self, *args, **kwargs):\n return None\n\n ...\n\n PyDecor ignores special methods (like ``__init__``) so as not to interfere\n with deep Python magic. By default, it works on any methods of a class,\n including instance, class and static methods. It also ensures that class\n attributes are preserved after decoration, so your class references\n continue to behave as expected.\n\n* **Consistent Method Decoration!**\n\n Whether you're decorating a class, an instance method, a class method, or\n a static method, you can use the same passed function. ``self`` and ``cls``\n variables are stripped out of the method parameters passed to the provided\n callable, so your functions don't need to care about where they're used.\n\n* **Lots of Tests!**\n\n Seriously. Don't believe me? Just look. We've got the best tests. Just\n phenomenal.\n\n\nInstallation\n------------\n\nSupported Python versions are 2.7 and 3.4+\n\nTo install `pydecor`, simply run::\n\n pip install -U pydecor\n\nTo install the current development release::\n\n pip install --pre -U pydecor\n\nYou can also install from source to get the absolute most recent\ncode, which may or may not be functional::\n\n git clone https://github.com/mplanchard/pydecor\n pip install ./pydecor\n\n\n\nDetails\n-------\n\nProvided Decorators\n*******************\n\nThis package provides generic decorators, which can be used with any\nfunction to provide extra utility to decorated resources, as well\nas pr\u00eate-\u00e0-porter (ready-to-wear) decorators for immediate use.\n\nWhile the information below is enough to get you started, I highly\nrecommend checking out the `decorator module docs`_ to see all the\noptions and details for the various decorators!\n\nGenerics\n~~~~~~~~\n\n* ``@before`` - run a callable before the decorated function executes\n\n * by default called with no arguments other than extras\n\n* ``@after`` - run a callable after the decorated function executes\n\n * by default called with the result of the decorated function and any\n extras\n\n* ``@instead`` - run a callable in place of the decorated function\n\n * by default called with the args and kwargs to the decorated function,\n along with a reference to the function itself\n\n* ``@decorate`` - specify multiple callables to be run before, after, and/or\n instead of the decorated function\n\n * callables passed to ``decorate``'s ``before``, ``after``, or ``instead``\n keyword arguments will be called with the same default function signature\n as described for the individual decorators, above. Extras will be\n passed to all provided callables\n\n* ``construct_decorator`` - specify functions to be run ``before``, ``after``,\n or ``instead``. Returns a reusable generator.\n\n * in addition to ``before``, ``after``, and ``instead``, which receive\n callables, ``before_opts``, ``after_opts``, and ``instead_opts`` dicts\n may be passed to ``construct_decorator``, and they will apply in the same\n way as their respective decorator parameters\n\nEvery generic decorator takes any number of keyword arguments, which will be\npassed directly into the provided callable, unless ``unpack_extras`` is False\n(see below), so, running the code below prints \"red\":\n\n.. code:: python\n\n from pydecor import before\n\n def before_func(label=None):\n print(label)\n\n @before(before_func, label='red')\n def red_function():\n pass\n\n red_function()\n\nEvery generic decorator takes the following keyword arguments:\n\n* ``pass_params`` - if True, passes the args and kwargs, as a tuple and\n a dict, respectively, from the decorated function to the provided callable\n* ``pass_decorated`` - if True, passes a reference to the decorated function\n to the provided callable\n* ``implicit_method_decoration`` - if True, decorating a class implies\n decorating all of its methods. **Caution:** you should probably leave this\n on unless you know what you are doing.\n* ``instance_methods_only`` - if True, only instance methods (not class or\n static methods) will be automatically decorated when\n ``implicit_method_decoration`` is True\n* ``unpack_extras`` - if True, extras are unpacked into the provided callable.\n If False, extras are placed into a dictionary on ``extras_key``, which\n is passed into the provided callable.\n* ``extras_key`` - the keyword to use when passing extras into the provided\n callable if ``unpack_extras`` is False\n* ``_use_future_syntax`` - See the note at the top on backwards incompatible\n changes in version 2.0.0.\n\nThe ``construct_decorator`` function can be used to combine ``@before``,\n``@after``, and ``@instead`` calls into one decorator, without having to\nworry about unintended stacking effects. Let's make a\ndecorator that announces when we're starting an exiting a function:\n\n.. code:: python\n\n from pydecor import construct_decorator\n\n def before_func(decorated_func):\n print('Starting decorated function '\n '\"{}\"'.format(decorated_func.__name__))\n\n def after_func(decorated_result, decorated_func):\n print('\"{}\" gave result \"{}\"'.format(\n decorated_func.__name__, decorated_result\n ))\n\n my_decorator = construct_decorator(\n before=before_func,\n after=after_func,\n before_opts={'pass_decorated': True},\n after_opts={'pass_decorated': True},\n )\n\n @my_decorator()\n def this_function_returns_nothing():\n return 'nothing'\n\nAnd the output?\n\n.. code::\n\n Starting decorated function \"this_function_returns_nothing\"\n \"this_function_returns_nothing\" gave result \"nothing\"\n\n\nMaybe a more realistic example would be useful. Let's say we want to add\nheaders to a Flask response.\n\n.. code:: python\n\n\n from flask import Flask, Response, make_response\n from pydecor import construct_decorator\n\n\n def _set_app_json_header(response):\n # Ensure the response is a Response object, even if a tuple was\n # returned by the view function.\n response = make_response(response)\n response.headers.set('Content-Type', 'application/json')\n return response\n\n\n application_json = construct_decorator(after=_set_app_json_header)\n\n\n # Now you can decorate any Flask view, and your headers will be set.\n\n app = Flask(__name__)\n\n # Note that you must decorate \"before\" (closer to) the function than the\n # app.route() decoration, because the route decorator must be called on\n # the \"finalized\" version of your function\n\n @app.route('/')\n @application_json()\n def root_view():\n return 'Hello, world!'\n\n client = app.test_client()\n response = app.get('/')\n\n print(response.headers)\n\n\nThe output?\n\n..code::\n\n Content-Type: application/json\n Content-Length: 13\n\n\nPr\u00eate-\u00e0-porter (ready-to-wear)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``intercept`` - catch the specified exception and optionally re-raise and/or\n call a provided callback to handle the exception\n* ``log_call`` - automatically log the decorated function's call signature and\n results\n* ``memoize`` - memoize a function's call and return values for re-use. Can\n use any cache in ``pydecor.caches``, which all have options for automatic\n pruning to keep the memoization cache from growing too large.\n\n**More to come!!** See Roadmap_ for more details on upcoming features\n\n\nCaches\n******\n\nThree caches are provided with ``pydecor``. These are designed to be passed\nto the ``@memoization`` decorator if you want to use something other than\nthe default ``LRUCache``, but they are perfectly functional for use elesewhere.\n\nAll caches implement the standard dictionary interface.\n\n\nLRUCache\n~~~~~~~~\n\nA least-recently-used cache. Both getting and setting of key/value pairs\nresults in their having been considered most-recently-used. When the cache\nreaches the specified ``max_size``, least-recently-used items are discarded.\n\nFIFOCache\n~~~~~~~~~\n\nA first-in, first-out cache. When the cache reaches the specified ``max_size``,\nthe first item that was inserted is discarded, then the second, and so on.\n\nTimedCache\n~~~~~~~~~~\n\nA cache whose entries expire. If a ``max_age`` is specified, any entries older\nthan the ``max_age`` (in seconds) will be considered invalid, and will be\nremoved upon access.\n\n\nStacking\n********\n\nGeneric and convenience decorators may be stacked! You can stack multiple\nof the same decorator, or you can mix and match. Some gotchas are listed\nbelow.\n\nGenerally, staciking works just as you might expect, but some care must be\ntaken when using the ``@instead`` decorator, or ``@intercept``, which\nuses ``@instead`` under the hood.\n\nJust remember that ``@instead`` replaces everything that comes before. So,\nif long as ``@instead`` calls the decorated function, it's okay to stack it.\nIn these cases, it will be called *before* any decorators specified below\nit, and those decorators will be executed when it calls the decorated function.\n``@intercept`` behaves this way.\n\nIf an ``@instead`` decorator does *not* call the decorated function and\ninstead replaces it entirely, it **must** be specified first (at the bottom\nof the stacked decorator pile), otherwise the decorators below it will not\nexecute.\n\nFor ``@before`` and ``@after``, it doesn't matter in what order the decorators\nare specified. ``@before`` is always called first, and then ``@after``.\n\n\nClass Decoration\n****************\n\nClass decoration is difficult, but PyDecor aims to make it as easy and\nintuitive as possible!\n\nBy default, decorating a class applies that decorator to all of that class'\nmethods (instance, class, and static). The decoration applies to class and\nstatic methods whether they are referenced via an instance or via a class\nreference. \"Extras\" specified at the class level persist across calls to\ndifferent methods, allowing for things like a class level memoization\ndictionary (there's a very basic test in the test suite\nthat demonstrates this pattern, and a convenient memoization decorator\nis scheduled for the next release!).\n\nIf you'd prefer that the decorator not apply to class and static methods,\nset the ``instance_methods_only=True`` when decorating the class.\n\nIf you want to decorate the class itself, and *not* its methods, keep in\nmind that the decorator will be triggered when the class is instantiated,\nand that, if the decorator replaces or alters the return, that return will\nreplace the instantiated class. With those caveats in mind, setting\n``implicit_method_decoration=False`` when decorating a class enables that\nfuncitonality.\n\n.. note::\n\n Class decoration, and in particular the decoration of class and static\n methods, is accomplished through some pretty deep, complicated magic.\n The test suite has a lot of tests trying to make sure that everything\n works as expected, but please report any bugs you find so that I\n can resolve them!\n\n\nMethod Decoration\n*****************\n\nDecorators can be applied to static, class, or instance methods directly, as\nwell. If combined with ``@staticmethod`` or ``@classmethod`` decorators,\nthose decorators should always be at the \"top\" of the decorator stack\n(furthest from the function).\n\nWhen decorating instance methods, ``self`` is removed from the parameters\npassed to the provided callable.\n\nWhen decorating class methods, ``cls`` is removed from the parameters passed\nto the provided callable.\n\nCurrently, the class and instance references *do not* have to be named\n``\"cls\"`` and ``\"self\"``, respectively, in order to be removed. However,\nthis is not guaranteed for future releases, so try to keep your naming\nstandard if you can (just FYI, ``\"self\"`` is the more likely of the two to\nwind up being required).\n\nExamples\n--------\n\nBelow are some examples for the generic and standard decorators. Please\ncheck out the API Docs for more information, and also check out the\nconvenience decorators, which are all implemented using the\n``before``, ``after``, and ``instead`` decorators from this library.\n\nUpdate a Function's Args or Kwargs\n**********************************\n\nFunctions passed to ``@before`` can either return None, in which case nothing\nhappens to the decorated functions parameters, or they can return a tuple\nof args (as a tuple) and kwargs (as a dict), in which case those parameters\nare used in the decorated function. In this example, we sillify a very\nserious function.\n\n.. note::\n Because kwargs are mutable, they can be updated even if the function\n passed to before doesn't return anything.\n\n.. code:: python\n\n from pydecor import before\n\n def spamify_func(args, kwargs):\n \"\"\"Mess with the function arguments\"\"\"\n args = tuple(['spam' for _ in args])\n kwargs = {k: 'spam' for k in kwargs}\n return args, kwargs\n\n\n @before(spamify_func, pass_params=True)\n def serious_function(serious_string, serious_kwarg='serious'):\n \"\"\"A very serious function\"\"\"\n print('A serious arg: {}'.format(serious_string))\n print('A serious kwarg: {}'.format(serious_kwarg))\n\n serious_function('Politics', serious_kwarg='Religion')\n\nThe output?\n\n.. code::\n\n A serious arg: spam\n A serious kwarg: spam\n\nDo Something with a Function's Return Value\n*******************************************\n\nFunctions passed to ``@after`` receive the decorated function's return value\nby default. If ``@after`` returns None, the return value is sent back\nunchanged. However, if ``@after`` returns something, its return value is\nsent back as the return value of the function.\n\nIn this example, we ensure that a function's return value has been thoroughly\nspammified.\n\n.. code:: python\n\n from pydecor import after\n\n def spamify_return(result):\n \"\"\"Spamify the result of a function\"\"\"\n return 'spam-spam-spam-spam-{}-spam-spam-spam-spam'.format(result)\n\n\n @after(spamify_return)\n def unspammed_function():\n \"\"\"Return a non-spammy value\"\"\"\n return 'beef'\n\n print(unspammed_function())\n\nThe output?\n\n.. code::\n\n spam-spam-spam-spam-beef-spam-spam-spam-spam\n\n\nDo Something Instead of a Function\n**********************************\n\nFunctions passed to ``@instead`` by default receive the args and kwargs of\nthe decorated function, along with a reference to that function. But, they\ndon't *have* to receive anything. Maybe you want to skip a function when\na certain condition is True, but you don't want to use ``pytest.skipif``,\nbecause ``pytest`` can't be a dependency of your production code for\nwhatever reason.\n\n\n.. code:: python\n\n from pydecor import instead\n\n def skip(args, kwargs, decorated, when=False):\n if when:\n pass\n else:\n return decorated(*args, **kwargs)\n\n\n @instead(skip, when=True)\n def uncalled_function():\n print(\"You won't see me (you won't see me)\")\n\n\n uncalled_function()\n\nThe output?\n\n(There is no output, because the function was skipped)\n\n\nAutomatically Log Function Calls and Results\n********************************************\n\nMaybe you want to make sure your functions get logged without having to\nbother with the logging boilerplate each time. ``@log_call`` tries to\nautomatically get a logging instance corresponding to the module\nin which the decoration occurs (in the same way as if you made a call\nto ``logging.getLogger(__name__)``, or you can pass it your own, fancy,\ncustom, spoiler-bedecked logger instance.\n\n.. code:: python\n\n from logging import getLogger, StreamHandler\n from sys import stdout\n\n from pydecor import log_call\n\n\n # We're just getting a logger here so we can see the output. This isn't\n # actually necessary for @log_call to work!\n log = getLogger(__name__)\n log.setLevel('DEBUG')\n log.addHandler(StreamHandler(stdout))\n\n\n @log_call()\n def get_schwifty(*args, **kwargs):\n \"\"\"Get schwifty in heeeeere\"\"\"\n return \"Gettin' Schwifty\"\n\n\n get_schwifty('wubba', 'lubba', dub='dub')\n\n\nAnd the output?\n\n.. code::\n\n get_schwifty(*('wubba', 'lubba'), **{'dub': 'dub'}) -> Gettin' Schwifty\n\n\nIntercept an Exception and Re-raise a Custom One\n************************************************\n\nAre you a put-upon library developer tired of constantly having to re-raise\ncustom exceptions so that users of your library can have one nice try/except\nlooking for your base exception? Let's make that easier:\n\n.. code:: python\n\n from pydecor import intercept\n\n\n class BetterException(Exception):\n \"\"\"Much better than all those other exceptions\"\"\"\n\n\n @intercept(catch=RuntimeError, reraise=BetterException)\n def sometimes_i_error(val):\n \"\"\"Sometimes, this function raises an exception\"\"\"\n if val > 5:\n raise RuntimeError('This value is too big!')\n\n\n for i in range(7):\n sometimes_i_error(i)\n\n\nThe output?\n\n.. code::\n\n Traceback (most recent call last):\n File \"/Users/Nautilus/Library/Preferences/PyCharm2017.1/scratches/scratch_1.py\", line 88, in \n sometimes_i_error(i)\n File \"/Users/Nautilus/Documents/Programming/pydecor/pydecor/decorators.py\", line 389, in wrapper\n return fn(**fkwargs)\n File \"/Users/Nautilus/Documents/Programming/pydecor/pydecor/functions.py\", line 58, in intercept\n raise_from(new_exc, context)\n File \"\", line 2, in raise_from\n __main__.BetterException: This value is too big!\n\n\nIntercept an Exception, Do Something, and Re-raise the Original\n***************************************************************\n\nMaybe you don't *want* to raise a custom exception. Maybe the original\none was just fine. All you want to do is print a special message before\nre-raising the original exception. PyDecor has you covered:\n\n.. code:: python\n\n from pydecor import intercept\n\n\n def print_exception(exc):\n \"\"\"Make sure stdout knows about our exceptions\"\"\"\n print('Houston, we have a problem: {}'.format(exc))\n\n\n @intercept(catch=Exception, handler=print_exception, reraise=True)\n def assert_false():\n \"\"\"All I do is assert that False is True\"\"\"\n assert False, 'Turns out, False is not True'\n\n\n assert_false()\n\nAnd the output:\n\n.. code::\n\n Houston, we have a problem: Turns out, False is not True\n Traceback (most recent call last):\n File \"/Users/Nautilus/Library/Preferences/PyCharm2017.1/scratches/scratch_1.py\", line 105, in \n assert_false()\n File \"/Users/Nautilus/Documents/Programming/pydecor/pydecor/decorators.py\", line 389, in wrapper\n return fn(**fkwargs)\n File \"/Users/Nautilus/Documents/Programming/pydecor/pydecor/functions.py\", line 49, in intercept\n return decorated(*decorated_args, **decorated_kwargs)\n File \"/Users/Nautilus/Library/Preferences/PyCharm2017.1/scratches/scratch_1.py\", line 102, in assert_false\n assert False, 'Turns out, False is not True'\n AssertionError: Turns out, False is not True\n\n\nIntercept an Exception, Handle, and Be Done with It\n***************************************************\n\nSometimes an exception isn't the end of the world, and it doesn't need to\nbubble up to the top of your application. In these cases, maybe just handle\nit and don't re-raise:\n\n.. code:: python\n\n from pydecor import intercept\n\n\n def let_us_know_it_happened(exc):\n \"\"\"Just let us know an exception happened (if we are reading stdout)\"\"\"\n print('This non-critical exception happened: {}'.format(exc))\n\n\n @intercept(catch=ValueError, handler=let_us_know_it_happened)\n def resilient_function(val):\n \"\"\"I am so resilient!\"\"\"\n val = int(val)\n print('If I get here, I have an integer: {}'.format(val))\n\n\n resilient_function('50')\n resilient_function('foo')\n\nOutput:\n\n.. code::\n\n If I get here, I have an integer: 50\n This non-critical exception happened: invalid literal for int() with base 10: 'foo'\n\nNote that the function does *not* continue running after the exception is\nhandled. Use this for short-circuiting under certain conditions rather\nthan for instituting a ``try/except:pass`` block. Maybe one day I'll figure\nout how to make this work like that, but as it stands, the decorator surrounds\nthe entire function, so it does not provide that fine-grained level of control.\n\n\nRoadmap\n-------\n\n1.2.0\n*****\n\nMore Pr\u00eate-\u00e0-porter Decorators\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n* ``export`` - add the decorated item to ``__all__``\n* ``skipif`` - similar to py.test's decorator, skip the function if a\n provided condition is True\n\nLet me know if you've got any idea for other decorators that would\nbe nice to have!\n\n\nTyping Stubfiles\n~~~~~~~~~~~~~~~~\n\nRight now type hints are provided via rst-style docstring specification.\nAlthough this format is supported by PyCharm, it does not conform to the\ntype-hinting standard defined in `PEP 484`_.\n\nIn order to better conform with the new standard (and to remain compatible\nwith Python 2.7), stubfiles will be added for the ``1.1.0`` release,\nand docstring hints will be removed so that contributors don't have\nto adjust type specifications in two places.\n\nBuild-process Updates\n~~~~~~~~~~~~~~~~~~~~~\n\nA more automated build process, because remembering all the steps to push a\nnew version is a pain. This is marked as scheduled for a patch release,\nbecause it does not affect users at all, so a minor version bump would\nlead people on to thinking that some new functionality had been added, when\nit hadn't.\n\n\n2.0.0\n*****\n\n* Use of immutable ``Decorated`` object to pass information about the\n deprecated function\n* Deprecation of ``pass_params``, ``pass_kwargs``, ``pass_decorated``,\n ``pass_result``, ``unapck_extras``, and ``extras_key`` keyword\n arguments to all decorators.\n* Better organization of documentation\n\n\nContributing\n------------\n\nContributions are welcome! If you find a bug or if something doesn't\nwork the way you think it should, please `raise an issue `_.\nIf you know how to fix the bug, please `open a PR! `_\n\nI absolutely welcome any level of contribution. If you think the docs\ncould be better, or if you've found a typo, please open up a PR to improve\nand/or fix them.\n\nContributor Conduct\n*******************\n\nThere is a ``CODE_OF_CONDUCT.md`` file with details, based on one of GitHub's\ntemplates, but the upshot is that I expect everyone who contributes to this\nproject to do their best to be helpful, friendly, and patient. Discrimination\nof any kind will not be tolerated and will be promptly reported to GitHub.\n\nOn a personal note, Open Source survives because of people who are willing to\ncontribute their time and effort for free. The least we can do is treat them\nwith respect.\n\nTests\n*****\n\nTests are fairly easy to run, with few dependencies. You'll need Python 2.7,\n3.4, and 3.6 installed on your system to run the full suite, as well as tox_\nin whatever environment or virtual environment you're using. From there, you\nshould just be able to run ``tox``. The underlying test suite is `py.test`_,\nand any extra arguments passed to tox get sent along. For example, to\nsend stdout/stderr to the console and stop on the first failure,\n``tox -- -sx``. You can also run `py.test`_ directly. If you do, make sure\nthe deps specified in ``tox.ini`` are installed to your virtualenv, and\ninstall the package in development mode with ``pip install -e .``.\n\nPRs that cause tests to fail will not be merged until tests pass.\n\nAny new functionality is expected to come with appropriate tests. That being\nsaid, the test suite is fairly complex, with lots of mocking and\nparametrization. Don't feel as though you have to follow this pattern when\nwriting new tests! A bunch of simpler tests are just as good. If you have any\nquestions, feel free to reach out to me via email at ``msplanchard`` ``@``\n``gmail`` or on Twitter as @msplanchard.\n\n\nCredits and Links\n-----------------\n\n* This project was started using my generic `project template`_\n* Tests are run with pytest_ and tox_\n* Mocking in Python 2.7 tests uses the `mock backport`_\n* Python 2/3 compatible exception raising via six_\n* The `typing backport`_ is used for Python2.7-3.4-compatible type definitions\n* Documentation built with sphinx_\n* Coverage information collected with coverage_\n* Pickling of objects provided via dill_\n\n.. _`project template`: https://github.com/mplanchard/python_skeleton\n.. _pytest:\n.. _`py.test`: https://docs.pytest.org/en/latest/\n.. _tox: http://tox.readthedocs.org/\n.. _sphinx: http://www.sphinx-doc.org/en/stable/\n.. _coverage: https://coverage.readthedocs.io/en/coverage-4.4.1/\n.. _`mock backport`: https://mock.readthedocs.io/en/latest/#\n.. _`pep 484`: https://www.python.org/dev/peps/pep-0484/\n.. _six: https://pythonhosted.org/six/\n.. _`typing backport`: https://pypi.org/project/typing/\n.. _docs: https://pydecor.readthedocs.io/en/latest/\n.. _`decorator module docs`:\n https://pydecor.readthedocs.io/en/latest/pydecor.decorators.html\n.. _issues: https://github.com/mplanchard/pydecor/issues\n.. _PRs: https://github.com/mplanchard/pydecor/pulls\n.. _dill: https://pypi.python.org/pypi/dill\n\n\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/mplanchard/pydecor", "keywords": "decorators,python2,python3", "license": "", "maintainer": "", "maintainer_email": "", "name": "pydecor", "package_url": "https://pypi.org/project/pydecor/", "platform": "", "project_url": "https://pypi.org/project/pydecor/", "project_urls": { "Homepage": "https://github.com/mplanchard/pydecor" }, "release_url": "https://pypi.org/project/pydecor/1.1.3/", "requires_dist": [ "dill", "six", "typing" ], "requires_python": "", "summary": "Easy peasy Python decorators", "version": "1.1.3" }, "last_serial": 3146773, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "0ee0756a362f7b39f8a8b10ace52c70a", "sha256": "9a8ffc49159bf83cb129e95599b406355b4c59850910d8b827b4ea54432aafab" }, "downloads": -1, "filename": "pydecor-0.1.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "0ee0756a362f7b39f8a8b10ace52c70a", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 28147, "upload_time": "2017-06-13T00:22:05", "url": "https://files.pythonhosted.org/packages/05/20/51646422c59ac04e6c76cb1012687e8ad8ef98571689621b1dbcd9a77425/pydecor-0.1.0-py2.py3-none-any.whl" } ], "0.1.0.dev1": [ { "comment_text": "", "digests": { "md5": "1c90280fa266518eebf65da8a80ec0eb", "sha256": "2ff6addcdcd39fc6f412232f324e31a0f71fa8eb85d9e84b04651ea9d2ebb1d5" }, "downloads": -1, "filename": "pydecor-0.1.0.dev1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "1c90280fa266518eebf65da8a80ec0eb", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 3706, "upload_time": "2017-06-01T01:13:23", "url": "https://files.pythonhosted.org/packages/e6/95/f74fd8366f605b6b28f0c2cde723202693bddee938001fe43a9121c30e96/pydecor-0.1.0.dev1-py2.py3-none-any.whl" } ], "0.1.0.dev2": [ { "comment_text": "", "digests": { "md5": "617d97ce5495fa983abb553cfcf34bea", "sha256": "6a7a88ea1f30921ef2f2d9c1961e87d5326c870494d7448b5a2e8a528e5ad12e" }, "downloads": -1, "filename": "pydecor-0.1.0.dev2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "617d97ce5495fa983abb553cfcf34bea", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 4098, "upload_time": "2017-06-01T03:15:39", "url": "https://files.pythonhosted.org/packages/5e/a2/1c5aad2c5bf175be1f5d5b7271e969259c9c1f1b54e30a5719be513559ff/pydecor-0.1.0.dev2-py2.py3-none-any.whl" } ], "0.1.0.dev3": [ { "comment_text": "", "digests": { "md5": "fe67d6828ff2efe7a5b96492f88adfbc", "sha256": "3233127d6f4f96285deaa6b2e551bd07261566acc75131f0298a7b4ef21d0a0b" }, "downloads": -1, "filename": "pydecor-0.1.0.dev3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "fe67d6828ff2efe7a5b96492f88adfbc", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 6867, "upload_time": "2017-06-07T13:52:15", "url": "https://files.pythonhosted.org/packages/be/1f/8ed7e83df54b414c4064f34e8dbc4a6498f7686ea2580a1f9587aacca2b7/pydecor-0.1.0.dev3-py2.py3-none-any.whl" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "fc04944ff7c5104f7a84ae4623fcaee7", "sha256": "3a4b54d6df633ec35ddb5a2ca39c4053fb945e1f24ec932a7923be143b3b7c6a" }, "downloads": -1, "filename": "pydecor-1.0.0-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "fc04944ff7c5104f7a84ae4623fcaee7", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 28195, "upload_time": "2017-06-13T00:57:16", "url": "https://files.pythonhosted.org/packages/c8/f8/0a903dec046380b048da5319dd697d68bfe98676c8707c8ed4159ce37206/pydecor-1.0.0-py2.py3-none-any.whl" } ], "1.1.0": [ { "comment_text": "", "digests": { "md5": "6317ee3d5ea33402618fd04ca5ca1a1d", "sha256": "faf9a06c94f5afcd5eda3589bbc3bfafbe31f3e80f9032f77c1107e5bd3e2359" }, "downloads": -1, "filename": "pydecor-1.1.0.tar.gz", "has_sig": false, "md5_digest": "6317ee3d5ea33402618fd04ca5ca1a1d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 30419, "upload_time": "2017-09-04T04:26:40", "url": "https://files.pythonhosted.org/packages/22/55/16ac4f2a0086e98305800f828540d6d3d1134eb945ff4b99127d3771b1b1/pydecor-1.1.0.tar.gz" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "2ed0070e74d4b87f336b64bd31ee0831", "sha256": "bb21d29a4a0eba75650db6c0ac55cef2f18d5aff645b7cad8df3e65e110349d3" }, "downloads": -1, "filename": "pydecor-1.1.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "2ed0070e74d4b87f336b64bd31ee0831", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 53265, "upload_time": "2017-09-04T04:46:29", "url": "https://files.pythonhosted.org/packages/50/4b/88b6651da2bf43312ab07a5b67b647953551f6b3d00dd9eed3d5780daa56/pydecor-1.1.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "879ddd166ec72671d3a81c2987498a2c", "sha256": "cdbd0ba8d638b436436094c574d4a211da51ca0177884725e1296ca90f784eb3" }, "downloads": -1, "filename": "pydecor-1.1.1.tar.gz", "has_sig": false, "md5_digest": "879ddd166ec72671d3a81c2987498a2c", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51258, "upload_time": "2017-09-04T04:46:33", "url": "https://files.pythonhosted.org/packages/11/ba/7ef22682ef1900c002612343b019175eb15e09ac478ee7c2d062e866c098/pydecor-1.1.1.tar.gz" } ], "1.1.2": [ { "comment_text": "", "digests": { "md5": "bbfaf067e6b739267dc86cb8577c7bbd", "sha256": "b1cf4e9861bf379d1ab4215a33dc28fa355031c3b9456766c4bf3eae1407e613" }, "downloads": -1, "filename": "pydecor-1.1.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "bbfaf067e6b739267dc86cb8577c7bbd", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 53269, "upload_time": "2017-09-04T05:00:21", "url": "https://files.pythonhosted.org/packages/f9/a8/578bac5d7f564fa4e3de44a44c581c302460d8e8f0a44eb036e05d97ff92/pydecor-1.1.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "06bbcd7fc700dda2617f3a0e6539aa63", "sha256": "29d342dc600c76754c7ec5d6803ac826e832fa67a6682bd3b15de04435d240c4" }, "downloads": -1, "filename": "pydecor-1.1.2.tar.gz", "has_sig": false, "md5_digest": "06bbcd7fc700dda2617f3a0e6539aa63", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51259, "upload_time": "2017-09-04T05:00:22", "url": "https://files.pythonhosted.org/packages/38/01/a51926001deabffce54d8a622181b178d06674919c2f4cd995c69d3f071a/pydecor-1.1.2.tar.gz" } ], "1.1.3": [ { "comment_text": "", "digests": { "md5": "6b42e23b0357c3ff782d59dc6aa7b72b", "sha256": "7e470328e9ba1a4645b1b2e31f71c8aa4c8f7565fac632d8394603cafc35e9e1" }, "downloads": -1, "filename": "pydecor-1.1.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "6b42e23b0357c3ff782d59dc6aa7b72b", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 53337, "upload_time": "2017-09-04T05:28:43", "url": "https://files.pythonhosted.org/packages/b0/4b/55e6dd57b785e510c9cd91d2d096140138320a7430ed921cf934183b673c/pydecor-1.1.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "a42f0e62de2456f72430dfadeed9f1aa", "sha256": "a5756873384572fdc4398bf1a6c94c8df4d10418fabf0247fac6d086b022b13e" }, "downloads": -1, "filename": "pydecor-1.1.3.tar.gz", "has_sig": false, "md5_digest": "a42f0e62de2456f72430dfadeed9f1aa", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51393, "upload_time": "2017-09-04T05:28:45", "url": "https://files.pythonhosted.org/packages/b7/24/004b671a5e0a85a3e1a0801a48caaf0e9ce201d7694faa3dbdf798115009/pydecor-1.1.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "6b42e23b0357c3ff782d59dc6aa7b72b", "sha256": "7e470328e9ba1a4645b1b2e31f71c8aa4c8f7565fac632d8394603cafc35e9e1" }, "downloads": -1, "filename": "pydecor-1.1.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "6b42e23b0357c3ff782d59dc6aa7b72b", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 53337, "upload_time": "2017-09-04T05:28:43", "url": "https://files.pythonhosted.org/packages/b0/4b/55e6dd57b785e510c9cd91d2d096140138320a7430ed921cf934183b673c/pydecor-1.1.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "a42f0e62de2456f72430dfadeed9f1aa", "sha256": "a5756873384572fdc4398bf1a6c94c8df4d10418fabf0247fac6d086b022b13e" }, "downloads": -1, "filename": "pydecor-1.1.3.tar.gz", "has_sig": false, "md5_digest": "a42f0e62de2456f72430dfadeed9f1aa", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 51393, "upload_time": "2017-09-04T05:28:45", "url": "https://files.pythonhosted.org/packages/b7/24/004b671a5e0a85a3e1a0801a48caaf0e9ce201d7694faa3dbdf798115009/pydecor-1.1.3.tar.gz" } ] }