diff -Nru python-decorator-4.4.2/CHANGES.md python-decorator-5.1.1/CHANGES.md --- python-decorator-4.4.2/CHANGES.md 2020-02-29 05:22:24.000000000 +0000 +++ python-decorator-5.1.1/CHANGES.md 2022-01-07 07:49:37.000000000 +0000 @@ -1,7 +1,52 @@ HISTORY -------- -## unreleased +## Unreleased + +## 5.1.1 (2022-01-07) + +Sangwoo Shim contributed a fix so that cythonized functions can be decorated. +Brian McFee pointed out an issue in the `decorator_apply` example and +Wim Glenn pointed out that the "fix" in version 5.1 broke +`decorator.contextmanager` even more. Both issues are now solved. + +## 5.1.0 (2021-09-11) + +Added a function `decoratorx` using the `FunctionMaker` and thus +preserving the signature of `__code__` objects. Then fixed three small bugs: +- Sphinx was printing a few warnings when building the documentation, as + signaled by Tomasz Kłoczko +- functions decorated with `decorator.contextmanager` were one-shot, + as discovered by Alex Pizarro. +- `decorator.decorator` was not passing the kwsyntax argument. + +## 5.0.9 (2021-05-16) + +Fixed a test breaking PyPy. Restored support for Sphinx. + +## 5.0.8 (2021-05-15) + +Made the decorator module more robust when decorating builtin functions +lacking dunder attributes, like `dict.__setitem__`. + +## 5.0.7 (2021-04-14) + +The decorator module was not passing correctly the defaults inside the +`*args` tuple, thanks to Dan Shult for the fix. Also fixed some mispellings +in the documentation and integrated codespell in the CI, thanks to +Christian Clauss. + +## 5.0.6 (2021-04-08) + +The decorator module was not copying the __module__ attribute anymore. +Thanks to Nikolay Markov for the notice. + +## 5.0.5 (2021-04-04) + +Dropped support for Python < 3.5 with a substantial simplification of +the code base (now building a decorator does not require calling "exec"). +Added a way to mimic functools.wraps-generated decorators. +Ported the Continuous Integration from Travis to GitHub. ## 4.4.2 (2020-02-29) diff -Nru python-decorator-4.4.2/debian/changelog python-decorator-5.1.1/debian/changelog --- python-decorator-4.4.2/debian/changelog 2020-03-23 14:04:19.000000000 +0000 +++ python-decorator-5.1.1/debian/changelog 2022-12-05 11:07:15.000000000 +0000 @@ -1,29 +1,70 @@ -python-decorator (4.4.2-0ubuntu1) focal; urgency=medium +python-decorator (5.1.1-3) unstable; urgency=medium - * d/gbp.conf: Update gbp configuration file. - * d/control: Update Vcs-* links and maintainers. - * New upstream release. + [ Athos Ribeiro ] + * Team upload. + * Run tests with pytest instead of setup.py test. + + [ Jeroen Ploemen ] + * Tests: rewrite to loop over all supported Python versions. + + -- Athos Ribeiro Mon, 05 Dec 2022 11:07:15 +0000 + +python-decorator (5.1.1-2) unstable; urgency=medium + + * upload to unstable - -- Corey Bryant Mon, 23 Mar 2020 10:04:19 -0400 + -- Sandro Tosi Sat, 18 Jun 2022 00:48:01 -0400 -python-decorator (4.3.0-1.1build1) focal; urgency=medium +python-decorator (5.1.1-1) experimental; urgency=medium - * No-change rebuild to generate dependencies on python2. + * Team upload. + * New upstream version 5.1.1 + + -- Gordon Ball Fri, 07 Jan 2022 15:08:46 +0000 + +python-decorator (5.1.0-1) experimental; urgency=medium + + * Team upload. + + [ Ondřej Nový ] + * d/control: Update Maintainer field with new Debian Python Team + contact address. + * d/control: Update Vcs-* fields with new Debian Python Team Salsa + layout. - -- Matthias Klose Tue, 17 Dec 2019 12:42:01 +0000 + [ Debian Janitor ] + * Set upstream metadata fields: Bug-Submit, Repository-Browse. + * Bump debhelper from old 12 to 13. -python-decorator (4.3.0-1.1) unstable; urgency=medium + [ Gordon Ball ] + * New upstream version 5.1.0 + * Standards-Version: 4.6.0 + * Run testsuite at build time + * Run testsuite against the installed library as an autopkgtest + * Update homepage to github.com/micheles/decorator - * Non-maintainer upload. + -- Gordon Ball Sun, 10 Oct 2021 21:16:08 +0000 - [ Niels Thykier ] - * Declare that python-decorator does not need (fake)root by - setting Rules-Requires-Root to "no". +python-decorator (4.4.2-2) unstable; urgency=medium - [ Helmut Grohne ] - * Mark all packages Multi-Arch: foreign (closes: #892949) + * Drop python2 support; Closes: #937694 - -- Niels Thykier Sun, 30 Dec 2018 22:37:21 +0000 + -- Sandro Tosi Fri, 10 Apr 2020 18:39:17 -0400 + +python-decorator (4.4.2-1) unstable; urgency=medium + + [ Ondřej Nový ] + * Convert git repository from git-dpm to gbp layout + * Use debhelper-compat instead of debian/compat. + + [ Debian Janitor ] + * Bump debhelper from old 10 to 12. + * Set upstream metadata fields: Bug-Database, Name, Repository. + + [ Piotr Ożarowski ] + * New upstream release + + -- Piotr Ożarowski Sun, 22 Mar 2020 23:07:18 +0100 python-decorator (4.3.0-1) unstable; urgency=medium @@ -234,4 +275,3 @@ * Initial release (Closes: #419127). -- Oleksandr Moskalenko Fri, 13 Apr 2007 13:10:43 -0600 - diff -Nru python-decorator-4.4.2/debian/compat python-decorator-5.1.1/debian/compat --- python-decorator-4.4.2/debian/compat 2020-03-23 14:04:19.000000000 +0000 +++ python-decorator-5.1.1/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -10 diff -Nru python-decorator-4.4.2/debian/control python-decorator-5.1.1/debian/control --- python-decorator-4.4.2/debian/control 2020-03-23 14:04:19.000000000 +0000 +++ python-decorator-5.1.1/debian/control 2022-12-05 09:45:33.000000000 +0000 @@ -1,37 +1,17 @@ Source: python-decorator Section: python Priority: optional -Maintainer: Ubuntu Developers -XSBC-Original-Maintainer: Debian Python Modules Team +Maintainer: Debian Python Team Uploaders: Piotr Ożarowski -Build-Depends: debhelper (>= 10), dh-python, - python-all (>= 2.6.6-3), python3-all, - python-setuptools (>= 0.6b3~), python3-setuptools +Build-Depends: debhelper-compat (= 13), dh-python, + python3-all, + python3-pytest , + python3-setuptools +Standards-Version: 4.6.0 +Vcs-Git: https://salsa.debian.org/python-team/packages/python-decorator.git +Vcs-Browser: https://salsa.debian.org/python-team/packages/python-decorator +Homepage: https://github.com/micheles/decorator Rules-Requires-Root: no -Standards-Version: 4.1.1 -Vcs-Git: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-decorator -Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/python-decorator -Homepage: https://pypi.python.org/pypi/decorator - -Package: python-decorator -Architecture: all -Multi-Arch: foreign -Depends: ${python:Depends}, ${misc:Depends} -Enhances: python-pylons -Description: simplify usage of Python decorators by programmers - Python 2.4 decorators have significantly changed the way Python programs are - structured. - * decorators help reduce boilerplate code; - * decorators help the separation of concerns; - * decorators enhance readability and maintainability; - * decorators are very explicit. - Still, as of now, writing custom decorators correctly requires some - experience and is not as easy as it could be. For instance, typical - implementations of decorators involve nested functions and we all know that - flat is better than nested. - The aim of the decorator module it to simplify the usage of decorators for - the average programmer and to popularize decorators usage giving examples of - useful decorators, such as memoize, tracing, redirecting_stdout, locked, etc. Package: python3-decorator Architecture: all diff -Nru python-decorator-4.4.2/debian/gbp.conf python-decorator-5.1.1/debian/gbp.conf --- python-decorator-4.4.2/debian/gbp.conf 2020-03-23 14:04:19.000000000 +0000 +++ python-decorator-5.1.1/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -[DEFAULT] -debian-branch = master -upstream-tag = %(version)s -pristine-tar = True - -[buildpackage] -export-dir = ../build-area diff -Nru python-decorator-4.4.2/debian/.git-dpm python-decorator-5.1.1/debian/.git-dpm --- python-decorator-4.4.2/debian/.git-dpm 2020-03-23 14:04:19.000000000 +0000 +++ python-decorator-5.1.1/debian/.git-dpm 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -# see git-dpm(1) from git-dpm package -6ecaab042e5bc83ead03a62d625958d760dda4bd -6ecaab042e5bc83ead03a62d625958d760dda4bd -6ecaab042e5bc83ead03a62d625958d760dda4bd -6ecaab042e5bc83ead03a62d625958d760dda4bd -python-decorator_4.3.0.orig.tar.gz -d68f722a13e85eb97553742a5ac5bd37a07cf651 -33758 -debianTag="debian/%e%v" -patchedTag="patched/%e%v" -upstreamTag="upstream/%e%u" diff -Nru python-decorator-4.4.2/debian/pybuild.testfiles python-decorator-5.1.1/debian/pybuild.testfiles --- python-decorator-4.4.2/debian/pybuild.testfiles 1970-01-01 00:00:00.000000000 +0000 +++ python-decorator-5.1.1/debian/pybuild.testfiles 2022-12-05 09:45:33.000000000 +0000 @@ -0,0 +1 @@ +src/tests diff -Nru python-decorator-4.4.2/debian/rules python-decorator-5.1.1/debian/rules --- python-decorator-4.4.2/debian/rules 2020-03-23 14:04:19.000000000 +0000 +++ python-decorator-5.1.1/debian/rules 2022-12-05 09:45:33.000000000 +0000 @@ -1,6 +1,7 @@ #!/usr/bin/make -f export PYBUILD_NAME=decorator +export PYBUILD_TEST_ARGS=tests/test.py tests/documentation.py %: - dh $@ --with python2,python3 --buildsystem=pybuild + dh $@ --with python3 --buildsystem=pybuild diff -Nru python-decorator-4.4.2/debian/tests/control python-decorator-5.1.1/debian/tests/control --- python-decorator-4.4.2/debian/tests/control 1970-01-01 00:00:00.000000000 +0000 +++ python-decorator-5.1.1/debian/tests/control 2022-12-05 10:18:42.000000000 +0000 @@ -0,0 +1,3 @@ +Tests: upstream-tests +Depends: @, python3-all, python3-pytest +Restrictions: allow-stderr diff -Nru python-decorator-4.4.2/debian/tests/upstream-tests python-decorator-5.1.1/debian/tests/upstream-tests --- python-decorator-4.4.2/debian/tests/upstream-tests 1970-01-01 00:00:00.000000000 +0000 +++ python-decorator-5.1.1/debian/tests/upstream-tests 2022-12-05 10:43:39.000000000 +0000 @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e -u + +cp -va src/tests "$AUTOPKGTEST_TMP"/ +cd "$AUTOPKGTEST_TMP" + +for py in $(py3versions -s 2>/dev/null); do + echo "Running upstream tests with $py:" + $py -m pytest tests/test.py tests/documentation.py +done diff -Nru python-decorator-4.4.2/debian/upstream/metadata python-decorator-5.1.1/debian/upstream/metadata --- python-decorator-4.4.2/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ python-decorator-5.1.1/debian/upstream/metadata 2022-12-05 09:45:33.000000000 +0000 @@ -0,0 +1,5 @@ +Name: decorator +Bug-Database: https://github.com/micheles/decorator/issues +Bug-Submit: https://github.com/micheles/decorator/issues/new +Repository: https://github.com/micheles/decorator.git +Repository-Browse: https://github.com/micheles/decorator diff -Nru python-decorator-4.4.2/PKG-INFO python-decorator-5.1.1/PKG-INFO --- python-decorator-4.4.2/PKG-INFO 2020-02-29 05:24:41.000000000 +0000 +++ python-decorator-5.1.1/PKG-INFO 2022-01-07 08:11:37.281304000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: decorator -Version: 4.4.2 +Version: 5.1.1 Summary: Decorators for Humans Home-page: https://github.com/micheles/decorator Author: Michele Simionato @@ -113,17 +113,13 @@ Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.2 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Utilities -Requires-Python: >=2.6, !=3.0.*, !=3.1.* +Requires-Python: >=3.5 diff -Nru python-decorator-4.4.2/setup.cfg python-decorator-5.1.1/setup.cfg --- python-decorator-4.4.2/setup.cfg 2020-02-29 05:24:41.000000000 +0000 +++ python-decorator-5.1.1/setup.cfg 2022-01-07 08:11:37.281304000 +0000 @@ -1,8 +1,5 @@ -[bdist_wheel] -universal = 1 - [upload_docs] -upload-dir = docs +upload_dir = docs [egg_info] tag_build = diff -Nru python-decorator-4.4.2/setup.py python-decorator-5.1.1/setup.py --- python-decorator-4.4.2/setup.py 2019-10-27 07:59:44.000000000 +0000 +++ python-decorator-5.1.1/setup.py 2021-11-01 15:34:35.000000000 +0000 @@ -18,23 +18,19 @@ py_modules=['decorator'], keywords="decorators generic utility", platforms=["All"], - python_requires='>=2.6, !=3.0.*, !=3.1.*', + python_requires='>=3.5', classifiers=['Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Software Development :: Libraries', 'Topic :: Utilities'], diff -Nru python-decorator-4.4.2/src/decorator.egg-info/PKG-INFO python-decorator-5.1.1/src/decorator.egg-info/PKG-INFO --- python-decorator-4.4.2/src/decorator.egg-info/PKG-INFO 2020-02-29 05:24:40.000000000 +0000 +++ python-decorator-5.1.1/src/decorator.egg-info/PKG-INFO 2022-01-07 08:11:37.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: decorator -Version: 4.4.2 +Version: 5.1.1 Summary: Decorators for Humans Home-page: https://github.com/micheles/decorator Author: Michele Simionato @@ -113,17 +113,13 @@ Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.2 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Utilities -Requires-Python: >=2.6, !=3.0.*, !=3.1.* +Requires-Python: >=3.5 diff -Nru python-decorator-4.4.2/src/decorator.py python-decorator-5.1.1/src/decorator.py --- python-decorator-4.4.2/src/decorator.py 2020-02-29 05:20:33.000000000 +0000 +++ python-decorator-5.1.1/src/decorator.py 2022-01-07 07:50:09.000000000 +0000 @@ -1,6 +1,6 @@ # ######################### LICENSE ############################ # -# Copyright (c) 2005-2018, Michele Simionato +# Copyright (c) 2005-2021, Michele Simionato # All rights reserved. # Redistribution and use in source and binary forms, with or without @@ -28,55 +28,26 @@ # DAMAGE. """ -Decorator module, see http://pypi.python.org/pypi/decorator +Decorator module, see +https://github.com/micheles/decorator/blob/master/docs/documentation.md for the documentation. """ -from __future__ import print_function - import re import sys import inspect import operator import itertools -import collections - -__version__ = '4.4.2' - -if sys.version_info >= (3,): - from inspect import getfullargspec - - def get_init(cls): - return cls.__init__ -else: - FullArgSpec = collections.namedtuple( - 'FullArgSpec', 'args varargs varkw defaults ' - 'kwonlyargs kwonlydefaults annotations') - - def getfullargspec(f): - "A quick and dirty replacement for getfullargspec for Python 2.X" - return FullArgSpec._make(inspect.getargspec(f) + ([], None, {})) - - def get_init(cls): - return cls.__init__.__func__ - -try: - iscoroutinefunction = inspect.iscoroutinefunction -except AttributeError: - # let's assume there are no coroutine functions in old Python - def iscoroutinefunction(f): - return False -try: - from inspect import isgeneratorfunction -except ImportError: - # assume no generator function in old Python versions - def isgeneratorfunction(caller): - return False +from contextlib import _GeneratorContextManager +from inspect import getfullargspec, iscoroutinefunction, isgeneratorfunction +__version__ = '5.1.1' DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(') +POS = inspect.Parameter.POSITIONAL_OR_KEYWORD +EMPTY = inspect.Parameter.empty -# basic functionality +# this is not used anymore in the core, but kept for backward compatibility class FunctionMaker(object): """ An object with the ability to create functions with a given signature. @@ -100,7 +71,7 @@ self.name = '_lambda_' self.doc = func.__doc__ self.module = func.__module__ - if inspect.isfunction(func): + if inspect.isroutine(func): argspec = getfullargspec(func) self.annotations = getattr(func, '__annotations__', {}) for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs', @@ -143,7 +114,9 @@ raise TypeError('You are decorating a non function: %s' % func) def update(self, func, **kw): - "Update the signature of func with the data in self" + """ + Update the signature of func with the data in self + """ func.__name__ = self.name func.__doc__ = getattr(self, 'doc', None) func.__dict__ = getattr(self, 'dict', {}) @@ -160,7 +133,9 @@ func.__dict__.update(kw) def make(self, src_templ, evaldict=None, addsource=False, **attrs): - "Make a new function from a given template and update the signature" + """ + Make a new function from a given template and update the signature + """ src = src_templ % vars(self) # expand name and signature evaldict = evaldict or {} mo = DEF.search(src) @@ -221,105 +196,127 @@ return self.make(body, evaldict, addsource, **attrs) -def decorate(func, caller, extras=()): +def fix(args, kwargs, sig): """ - decorate(func, caller) decorates a function using a caller. - If the caller is a generator function, the resulting function - will be a generator function. + Fix args and kwargs to be consistent with the signature """ - evaldict = dict(_call_=caller, _func_=func) - es = '' - for i, extra in enumerate(extras): - ex = '_e%d_' % i - evaldict[ex] = extra - es += ex + ', ' - - if '3.5' <= sys.version < '3.6': - # with Python 3.5 isgeneratorfunction returns True for all coroutines - # however we know that it is NOT possible to have a generator - # coroutine in python 3.5: PEP525 was not there yet - generatorcaller = isgeneratorfunction( - caller) and not iscoroutinefunction(caller) - else: - generatorcaller = isgeneratorfunction(caller) - if generatorcaller: - fun = FunctionMaker.create( - func, "for res in _call_(_func_, %s%%(shortsignature)s):\n" - " yield res" % es, evaldict, __wrapped__=func) + ba = sig.bind(*args, **kwargs) + ba.apply_defaults() # needed for test_dan_schult + return ba.args, ba.kwargs + + +def decorate(func, caller, extras=(), kwsyntax=False): + """ + Decorates a function/generator/coroutine using a caller. + If kwsyntax is True calling the decorated functions with keyword + syntax will pass the named arguments inside the ``kw`` dictionary, + even if such argument are positional, similarly to what functools.wraps + does. By default kwsyntax is False and the the arguments are untouched. + """ + sig = inspect.signature(func) + if iscoroutinefunction(caller): + async def fun(*args, **kw): + if not kwsyntax: + args, kw = fix(args, kw, sig) + return await caller(func, *(extras + args), **kw) + elif isgeneratorfunction(caller): + def fun(*args, **kw): + if not kwsyntax: + args, kw = fix(args, kw, sig) + for res in caller(func, *(extras + args), **kw): + yield res else: - fun = FunctionMaker.create( - func, "return _call_(_func_, %s%%(shortsignature)s)" % es, - evaldict, __wrapped__=func) - if hasattr(func, '__qualname__'): - fun.__qualname__ = func.__qualname__ + def fun(*args, **kw): + if not kwsyntax: + args, kw = fix(args, kw, sig) + return caller(func, *(extras + args), **kw) + fun.__name__ = func.__name__ + fun.__doc__ = func.__doc__ + fun.__wrapped__ = func + fun.__signature__ = sig + fun.__qualname__ = func.__qualname__ + # builtin functions like defaultdict.__setitem__ lack many attributes + try: + fun.__defaults__ = func.__defaults__ + except AttributeError: + pass + try: + fun.__kwdefaults__ = func.__kwdefaults__ + except AttributeError: + pass + try: + fun.__annotations__ = func.__annotations__ + except AttributeError: + pass + try: + fun.__module__ = func.__module__ + except AttributeError: + pass + try: + fun.__dict__.update(func.__dict__) + except AttributeError: + pass return fun -def decorator(caller, _func=None): - """decorator(caller) converts a caller function into a decorator""" +def decoratorx(caller): + """ + A version of "decorator" implemented via "exec" and not via the + Signature object. Use this if you are want to preserve the `.__code__` + object properties (https://github.com/micheles/decorator/issues/129). + """ + def dec(func): + return FunctionMaker.create( + func, + "return _call_(_func_, %(shortsignature)s)", + dict(_call_=caller, _func_=func), + __wrapped__=func, __qualname__=func.__qualname__) + return dec + + +def decorator(caller, _func=None, kwsyntax=False): + """ + decorator(caller) converts a caller function into a decorator + """ if _func is not None: # return a decorated function # this is obsolete behavior; you should use decorate instead - return decorate(_func, caller) + return decorate(_func, caller, (), kwsyntax) # else return a decorator function - defaultargs, defaults = '', () - if inspect.isclass(caller): - name = caller.__name__.lower() - doc = 'decorator(%s) converts functions/generators into ' \ - 'factories of %s objects' % (caller.__name__, caller.__name__) - elif inspect.isfunction(caller): - if caller.__name__ == '': - name = '_lambda_' + sig = inspect.signature(caller) + dec_params = [p for p in sig.parameters.values() if p.kind is POS] + + def dec(func=None, *args, **kw): + na = len(args) + 1 + extras = args + tuple(kw.get(p.name, p.default) + for p in dec_params[na:] + if p.default is not EMPTY) + if func is None: + return lambda func: decorate(func, caller, extras, kwsyntax) else: - name = caller.__name__ - doc = caller.__doc__ - nargs = caller.__code__.co_argcount - ndefs = len(caller.__defaults__ or ()) - defaultargs = ', '.join(caller.__code__.co_varnames[nargs-ndefs:nargs]) - if defaultargs: - defaultargs += ',' - defaults = caller.__defaults__ - else: # assume caller is an object with a __call__ method - name = caller.__class__.__name__.lower() - doc = caller.__call__.__doc__ - evaldict = dict(_call=caller, _decorate_=decorate) - dec = FunctionMaker.create( - '%s(func, %s)' % (name, defaultargs), - 'if func is None: return lambda func: _decorate_(func, _call, (%s))\n' - 'return _decorate_(func, _call, (%s))' % (defaultargs, defaultargs), - evaldict, doc=doc, module=caller.__module__, __wrapped__=caller) - if defaults: - dec.__defaults__ = (None,) + defaults + return decorate(func, caller, extras, kwsyntax) + dec.__signature__ = sig.replace(parameters=dec_params) + dec.__name__ = caller.__name__ + dec.__doc__ = caller.__doc__ + dec.__wrapped__ = caller + dec.__qualname__ = caller.__qualname__ + dec.__kwdefaults__ = getattr(caller, '__kwdefaults__', None) + dec.__dict__.update(caller.__dict__) return dec # ####################### contextmanager ####################### # -try: # Python >= 3.2 - from contextlib import _GeneratorContextManager -except ImportError: # Python >= 2.5 - from contextlib import GeneratorContextManager as _GeneratorContextManager - class ContextManager(_GeneratorContextManager): - def __call__(self, func): - """Context manager decorator""" - return FunctionMaker.create( - func, "with _self_: return _func_(%(shortsignature)s)", - dict(_self_=self, _func_=func), __wrapped__=func) + def __init__(self, g, *a, **k): + _GeneratorContextManager.__init__(self, g, a, k) + def __call__(self, func): + def caller(f, *a, **k): + with self.__class__(self.func, *self.args, **self.kwds): + return f(*a, **k) + return decorate(func, caller) -init = getfullargspec(_GeneratorContextManager.__init__) -n_args = len(init.args) -if n_args == 2 and not init.varargs: # (self, genobj) Python 2.7 - def __init__(self, g, *a, **k): - return _GeneratorContextManager.__init__(self, g(*a, **k)) - ContextManager.__init__ = __init__ -elif n_args == 2 and init.varargs: # (self, gen, *a, **k) Python 3.4 - pass -elif n_args == 4: # (self, gen, args, kwds) Python 3.5 - def __init__(self, g, *a, **k): - return _GeneratorContextManager.__init__(self, g, a, k) - ContextManager.__init__ = __init__ _contextmanager = decorator(ContextManager) diff -Nru python-decorator-4.4.2/src/tests/documentation.py python-decorator-5.1.1/src/tests/documentation.py --- python-decorator-4.4.2/src/tests/documentation.py 2020-02-29 05:10:07.000000000 +0000 +++ python-decorator-5.1.1/src/tests/documentation.py 2022-01-07 07:38:28.000000000 +0000 @@ -1,58 +1,52 @@ -from __future__ import print_function -import sys +import inspect import threading import time import functools import itertools import collections -try: - import collections.abc as c -except ImportError: - c = collections - collections.abc = collections -from decorator import (decorator, decorate, FunctionMaker, contextmanager, +import collections.abc as c +from decorator import (decorator, decorate, FunctionMaker, dispatch_on, __version__) -doc = r"""Decorators for Humans ----------------------------------- +doc = r"""# Decorators for Humans |Author | Michele Simionato| |---|---| |E-mail | michele.simionato@gmail.com| |Version| $VERSION ($DATE)| -|Supports| Python 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8| +|Supports| Python 3.5, 3.6, 3.7, 3.8, 3.9, 3.10| |Download page| http://pypi.python.org/pypi/decorator/$VERSION| |Installation| ``pip install decorator``| |License | BSD license| -Introduction ------------------------------------------ +## Introduction The ``decorator`` module is over ten years old, but still alive and kicking. It is used by several frameworks (IPython, scipy, authkit, -pylons, pycuda, sugar, ...) and has been stable for a *long* -time. It is your best option if you want to preserve the signature of -decorated functions in a consistent way across Python -releases. Version 4 is fully compatible with the past, except for -one thing: support for Python 2.4 and 2.5 has been dropped. That -decision made it possible to use a single code base both for Python -2.X and Python 3.X. This is a *huge* bonus, since I could remove over -2,000 lines of duplicated documentation/doctests. Having to maintain -separate docs for Python 2 and Python 3 effectively stopped any -development on the module for several years. Moreover, it is now -trivial to distribute the module as an universal - [wheel](http://pythonwheels.com) since 2to3 is no more -required. Since Python 2.5 has been released ages ago (in 2006), I felt that -it was reasonable to drop the support for it. If you need to support -ancient versions of Python, stick with the decorator module version -3.4.2. The current version supports all Python releases from 2.6 up. +pylons, pycuda, sugar, ...) and has been stable for a *long* time. It +is your best option if you want to preserve the signature of decorated +functions in a consistent way across Python releases. Versions 5.X +supports Python versions greater than 3.4, versions 4.X supports Python +versions back to 2.6; versions 3.X are able to support even Python 2.5 and +2.4. + +## What's New in version 5 + +Version 5 of the decorator module features a major simplification of +the code base made possible by dropping support for Python releases +older than 3.5. From that version the ``Signature`` object works well +enough that it is possible to fix the signature of a decorated +function without resorting to ``exec`` tricks. The simplification +has a very neat advantage: in case of exceptions raised in decorated +functions the traceback is nicer than it used to be. Moreover, it is +now possible to mimic the behavior of decorators defined with +``functool.wraps``: see the section about the ``kwsyntax`` flag below. -What's New in version 4 ------------------------ +## What's New in version 4 - **New documentation** There is now a single manual for all Python versions, so I took the - opportunity to overhaul the documentation and to move it to readthedocs.org. + opportunity to overhaul the documentation. Even if you are a long-time user, you may want to revisit the docs, since several examples have been improved. @@ -87,15 +81,14 @@ From version 4.2 there is facility to define factories of decorators in a simple way, a feature requested by the users since a long time. -Usefulness of decorators ------------------------------------------------- +## Usefulness of decorators Python decorators are an interesting example of why syntactic sugar matters. In principle, their introduction in Python 2.4 changed nothing, since they did not provide any new functionality which was not already present in the language. In practice, their introduction has -significantly changed the way we structure our programs in Python. I -believe the change is for the best, and that decorators are a great +significantly changed the way we structure our programs. +I believe the change is for the best, and that decorators are a great idea since: * decorators help reducing boilerplate code; @@ -118,8 +111,7 @@ discussed here in the ``documentation.py`` file, which contains the documentation you are reading in the form of doctests. -Definitions ------------------------------------- +## Definitions Technically speaking, any Python object which can be called with one argument can be used as a decorator. However, this definition is somewhat too large @@ -148,8 +140,7 @@ can accept functions with any signature. A simple example will clarify the issue. -Statement of the problem ------------------------------- +## Statement of the problem A very common use case for decorators is the memoization of functions. A ``memoize`` decorator works by caching @@ -176,10 +167,6 @@ ``__name__``, ``__doc__``, ``__module__``, and ``__dict__`` to the decorated function by hand). -Here is an example of usage: - -$$f1 - This works insofar as the decorator accepts functions with generic signatures. Unfortunately, it is *not* a signature-preserving decorator, since ``memoize_uw`` generally returns a function with a *different signature* @@ -194,17 +181,17 @@ keyword arguments: ```python ->>> from decorator import getfullargspec +>>> from inspect import getfullargspec >>> print(getfullargspec(f1)) FullArgSpec(args=[], varargs='args', varkw='kw', defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={}) ``` -This means that introspection tools (like ``pydoc``) will give false -information about the signature of ``f1`` -- unless you are using -Python 3.5. This is pretty bad: ``pydoc`` will tell you that the -function accepts the generic signature ``*args, **kw``, but -calling the function with more than one argument raises an error: +This means that introspection tools like ``getfullargspec`` will give +you false information about the signature of ``f1`` This is pretty bad: +``getfullargspec`` says that the function accepts the generic +signature ``*args, **kw``, but calling the function with more than one +argument raises an error: ```python >>> f1(0, 1) # doctest: +IGNORE_EXCEPTION_DETAIL @@ -214,12 +201,10 @@ ``` -Notice that ``inspect.getfullargspec`` -will give the wrong signature, even in the latest Python, i.e. version 3.6 -at the time of writing. +Notice that ``pydoc`` will give the right signature, but only in Python +versions greater than 3.5. -The solution ------------------------------------------ +## The solution The solution is to provide a generic factory of generators, which hides the complexity of making signature-preserving decorators @@ -276,8 +261,7 @@ ``` -A ``trace`` decorator ------------------------------------------------------- +## A ``trace`` decorator Here is an example of how to define a simple ``trace`` decorator, which prints a message whenever the traced function is called: @@ -315,20 +299,72 @@ ```python >>> @trace -... def f(x, y=1, z=2, *args, **kw): +... def f(x, y=1, *args, **kw): ... pass >>> f(0, 3) -calling f with args (0, 3, 2), {} +calling f with args (0, 3), {} >>> print(getfullargspec(f)) -FullArgSpec(args=['x', 'y', 'z'], varargs='args', varkw='kw', defaults=(1, 2), kwonlyargs=[], kwonlydefaults=None, annotations={}) +FullArgSpec(args=['x', 'y'], varargs='args', varkw='kw', defaults=(1,), kwonlyargs=[], kwonlydefaults=None, annotations={}) + +``` + +## Function annotations + +Python 3 introduced the concept of [function annotations]( +http://www.python.org/dev/peps/pep-3107/): the ability +to annotate the signature of a function with additional information, +stored in a dictionary named ``__annotations__``. The ``decorator`` module +(starting from release 3.3) will understand and preserve these annotations. + +Here is an example: + +```python +>>> @trace +... def f(x: 'the first argument', y: 'default argument'=1, z=2, +... *args: 'varargs', **kw: 'kwargs'): +... pass + +``` + +In order to introspect functions with annotations, one needs +``inspect.getfullargspec`` (introduced in Python 3, then +deprecated in Python 3.5, then undeprecated in Python 3.6): + +```python +>>> from inspect import getfullargspec +>>> argspec = getfullargspec(f) +>>> argspec.args +['x', 'y', 'z'] +>>> argspec.varargs +'args' +>>> argspec.varkw +'kw' +>>> argspec.defaults +(1, 2) +>>> argspec.kwonlyargs +[] +>>> argspec.kwonlydefaults + +``` + +You can check that the ``__annotations__`` dictionary is preserved: + +```python +>>> f.__annotations__ is f.__wrapped__.__annotations__ +True ``` -$FUNCTION_ANNOTATIONS -``decorator.decorator`` ---------------------------------------------- +Here ``f.__wrapped__`` is the original undecorated function. +This attribute exists for consistency with the behavior of +``functools.update_wrapper``. + +Another attribute copied from the original function is ``__qualname__``, +the qualified name. This attribute was introduced in Python 3.3. + +## ``decorator.decorator`` It can become tedious to write a caller function (like the above ``_trace`` example) and then a trivial wrapper @@ -340,8 +376,6 @@ ```python >>> from decorator import decorator ->>> print(decorator.__doc__) -decorator(caller) converts a caller function into a decorator ``` The ``decorator`` function can be used as a signature-changing @@ -380,6 +414,67 @@ ``` +## Mimicking the behavior of functools.wrap + +Often people are confused by the decorator module since, contrarily +to ``functools.wraps`` in the standard library, it tries very hard +to keep the semantics of the arguments: in particular, positional arguments +stay positional even if they are called with the keyword argument syntax. +An example will make the issue clear. Here is a simple caller + +$$chatty + +and here is a function to decorate: + +$$printsum + +In this example ``x`` and ``y`` are positional arguments (with +defaults). From the caller perspective, it does not matter if the user +calls them as named arguments, they will stay inside the ``args`` +tuple and not inside the ``kwargs`` dictionary: + +```python +>>> printsum(y=2, x=1) +(1, 2) [] +3 + +``` + +This is quite different from the behavior of ``functools.wraps``; if you +define the decorator as follows + +$$chattywrapper + +you will see that calling ``printsum`` with named arguments will pass +such arguments to ``kwargs``, while ``args`` will be the empty tuple. +Since version 5 of the decorator module it is possible to mimic that +behavior by using the ``kwsyntax`` flag: + +$$printsum2 + +Here is how it works: + +```python +>>> printsum2(y=2, x=1) +() [('x', 1), ('y', 2)] +3 + +``` + +This is exactly what the ``chattywrapper`` decorator would print: +positional arguments are seen as keyword arguments, but only if the +client code calls them with the keyword syntax. Otherwise they stay +positional, i.e. they belongs to the ``args`` tuple and not to ``kwargs``: + +```python +>>> printsum2(1, 2) +(1, 2) [] +3 + +``` + +## Decorator factories + The `decorator` function can also be used to define factories of decorators, i.e. functions returning decorators. In general you can just write something like this: @@ -392,9 +487,8 @@ ``` This is fully general but requires an additional level of nesting. For this -reason since version 4.2 there is a facility to build -decorator factories by using a single caller with default arguments i.e. -writing something like this: +reason since version 4.2 there is a facility to build decorator factories by +using a single caller with default arguments: ```python def caller(f, param1=default1, param2=default2, ..., *args, **kw): @@ -408,22 +502,17 @@ of the family which uses the default values for all parameters. Such decorator can be written as ``decfactory()`` with no parameters specified; moreover, as a shortcut, it is also possible to elide the parenthesis, -a feature much requested by the users. For years I have been opposite -to this feature request, since having explicit parenthesis to me is more clear +a feature much requested by the users. For years I have been opposing +the request, since having explicit parenthesis to me is more clear and less magic; however once this feature entered in decorators of the Python standard library (I am referring to the [dataclass decorator]( https://www.python.org/dev/peps/pep-0557/)) I finally gave up. -The example below will show how it works in practice. - -Decorator factories -------------------------------------------- - -Sometimes one has to deal with blocking resources, such as ``stdin``. -Sometimes it is better to receive a "busy" message than just blocking -everything. -This can be accomplished with a suitable family of decorators (decorator -factory), parameterize by a string, the busy message: +The example below shows how it works in practice. The goal is to +convert a function relying on a blocking resource into a function +returning a "busy" message if the resource is not available. +This can be accomplished with a suitable family of decorators +parameterize by a string, the busy message: $$blocking @@ -474,8 +563,7 @@ Be careful! -``decorator(cls)`` --------------------------------------------- +## ``decorator(cls)`` The ``decorator`` facility can also produce a decorator starting from a class with the signature of a caller. In such a case the @@ -511,8 +599,7 @@ ``` -contextmanager -------------------------------------- +## contextmanager Python's standard library has the ``contextmanager`` decorator, which converts a generator function into a ``GeneratorContextManager`` @@ -529,8 +616,7 @@ ``` ...then ``before_after`` is a factory function that returns -``GeneratorContextManager`` objects, which provide the -use of the ``with`` statement: +``GeneratorContextManager`` objects, usable with the ``with`` statement: ```python >>> with before_after('BEFORE', 'AFTER'): @@ -550,11 +636,11 @@ ```python >>> ba = before_after('BEFORE', 'AFTER') >>> ->>> @ba # doctest: +SKIP +>>> @ba ... def hello(): ... print('hello') ... ->>> hello() # doctest: +SKIP +>>> hello() BEFORE hello AFTER @@ -564,17 +650,10 @@ The ``ba`` decorator basically inserts a ``with ba:`` block inside the function. -However, there are two issues: - -1. ``GeneratorContextManager`` objects are only callable in Python 3.2, - so the previous example breaks in older versions of Python. - (You can solve this by installing ``contextlib2``, which backports - the Python 3 functionality to Python 2.) - -2. ``GeneratorContextManager`` objects do not preserve the signature of - the decorated functions. The decorated ``hello`` function above will - have the generic signature ``hello(*args, **kwargs)``, but fails if - called with more than zero arguments. +However ``GeneratorContextManager`` objects do not preserve the signature of +the decorated functions. The decorated ``hello`` function above will +have the generic signature ``hello(*args, **kwargs)``, but fails if +called with more than zero arguments. For these reasons, the `decorator` module, starting from release 3.4, offers a ``decorator.contextmanager`` decorator that solves both problems, @@ -585,17 +664,15 @@ an improved ``__call__`` method, which acts as a signature-preserving decorator. -The ``FunctionMaker`` class ---------------------------------------------------------------- +## The ``FunctionMaker`` class -You may wonder how the functionality of the ``decorator`` module -is implemented. The basic building block is -a ``FunctionMaker`` class. It generates on-the-fly functions +The ``decorator`` module also provides a ``FunctionMaker`` class, which +is able to generate on-the-fly functions with a given name and signature from a function template passed as a string. If you're just writing ordinary decorators, then you probably won't -need to use ``FunctionMaker`` directly. But in some circumstances, it +need to use ``FunctionMaker``. But in some circumstances, it can be handy. You will see an example shortly--in the implementation of a cool decorator utility (``decorator_apply``). @@ -655,9 +732,9 @@ - If first argument of ``FunctionMaker.create`` is a function, an instance of ``FunctionMaker`` is created with the attributes - ``args``, ``varargs``, ``keywords``, and ``defaults``. - (These mirror the return values of the standard library's - ``inspect.getfullargspec``.) + ``args``, ``varargs``, ``keywords``, and ``defaults`` + (these mirror the return values of the standard library's + ``inspect.getfullargspec``). - For each item in ``args`` (a list of strings of the names of all required arguments), an attribute ``arg0``, ``arg1``, ..., ``argN`` is also generated. @@ -677,36 +754,13 @@ ``` -Getting the source code ---------------------------------------------------- +## Getting the source code Internally, ``FunctionMaker.create`` uses ``exec`` to generate the decorated function. Therefore ``inspect.getsource`` will not work for decorated functions. In IPython, this means that the usual ``??`` trick will give you the (right on the spot) message ``Dynamically generated function. No source code available``. - -In the past, I considered this acceptable, since ``inspect.getsource`` -does not really work with "regular" decorators. In those cases, -``inspect.getsource`` gives you the wrapper source code, which is probably -not what you want: - -$$identity_dec -$$example - -```python ->>> import inspect ->>> print(inspect.getsource(example)) - def wrapper(*args, **kw): - return func(*args, **kw) - - -``` - -(See bug report [1764286](http://bugs.python.org/issue1764286) -for an explanation of what is happening). -Unfortunately the bug still exists in all versions of Python < 3.5. - However, there is a workaround. The decorated function has the ``__wrapped__`` attribute, pointing to the original function. The simplest way to get the source code is to call ``inspect.getsource`` on the undecorated function: @@ -723,8 +777,7 @@ ``` -Dealing with third-party decorators ------------------------------------------------------------------ +## Dealing with third-party decorators Sometimes on the net you find some cool decorator that you would like to include in your code. However, more often than not, the cool @@ -788,14 +841,12 @@ - returns a value without making a recursive call; or, - returns directly the result of a recursive call. -Python 3.5 coroutines ------------------------ +## Python 3.5 coroutines -I am personally not using Python 3.5 coroutines yet, because at work we are -still maintaining compatibility with Python 2.7. However, some users requested -support for coroutines and since version 4.1 the decorator module has it. -You should consider the support experimental and kindly report issues if -you find any. +I am personally not using Python 3.5 coroutines yet. However, some +users requested support for coroutines and since version 4.1 the +decorator module has it. You should consider the support experimental +and kindly report issues if you find any. Here I will give a single example of usage. Suppose you want to log the moment a coroutine starts and the moment it stops for debugging purposes. You could @@ -807,7 +858,7 @@ from asyncio import get_event_loop, sleep, wait from decorator import decorator - @decorator +@decorator async def log_start_stop(coro, *args, **kwargs): logging.info('Starting %s%s', coro.__name__, args) t0 = time.time() @@ -855,13 +906,12 @@ return get_event_loop().run_until_complete(coro(*args, **kw)) ``` -Notice the diffence: the caller in ``log_start_stop`` was a coroutine -function and the associate decorator was converting coroutines->coroutines; +Notice the difference: the caller in ``log_start_stop`` was a coroutine +function and the associate decorator was converting coroutines in coroutines; the caller in ``coro_to_func`` is a regular function and converts coroutines -> functions. -Multiple dispatch -------------------------------------------- +## Multiple dispatch There has been talk of implementing multiple dispatch functions (i.e. "generic functions") in Python for over ten years. Last year, @@ -982,7 +1032,7 @@ ``` -You can introspect the precedence used by the dispath algorithm by +You can introspect the precedence used by the dispatch algorithm by calling ``.dispatch_info(*types)``: ```python @@ -998,8 +1048,7 @@ [MRO](http://www.python.org/2.3/mro.html) for short) of ``StrongRock`` and ``Scissors``, respectively. -Generic functions and virtual ancestors -------------------------------------------------- +## Generic functions and virtual ancestors In Python, generic functions are complicated by the existence of "virtual ancestors": superclasses which are not in the class hierarchy. @@ -1143,110 +1192,49 @@ Finally, let me notice that the decorator module implementation does not use any cache, whereas the ``singledispatch`` implementation does. -Caveats and limitations -------------------------------------------- - -One thing you should be aware of, is the performance penalty of decorators. -The worse case is shown by the following example: +## Caveats and limitations -```bash - $ cat performance.sh - python3 -m timeit -s " - from decorator import decorator - - @decorator - def do_nothing(func, *args, **kw): - return func(*args, **kw) - - @do_nothing - def f(): - pass - " "f()" - - python3 -m timeit -s " - def f(): - pass - " "f()" - -``` -On my laptop, using the ``do_nothing`` decorator instead of the -plain function is five times slower: - -```bash - $ bash performance.sh - 1000000 loops, best of 3: 1.39 usec per loop - 1000000 loops, best of 3: 0.278 usec per loop -``` -Of course, a real life function probably does something more useful -than the function ``f`` here, so the real life performance penalty -*could* be negligible. As always, the only way to know if there is a -penalty in your specific use case is to measure it. - -More importantly, you should be aware that decorators will make your -tracebacks longer and more difficult to understand. +In the present implementation, decorators generated by ``decorator`` +can only be used on user-defined Python functions, methods or coroutines. +I have no interest in decorating generic callable objects. If you want to +decorate things like classmethods/staticmethods and general callables - +which I will never support in the decorator module - I suggest you +to look at the [wrapt](https://wrapt.readthedocs.io/en/latest/) +project by Graeme Dumpleton. -Consider this example: +Since version 5 the ``decorator`` module uses the ``inspect.Signature`` +object in the standard library. Unfortunately, for legacy reasons, some +applications introspect decorated functions by using low-level entities like +the ``__code__`` object and not signature objects. An example will make +the issue clear: ```python ->>> @trace -... def f(): -... 1/0 +>>> def f(a, b): pass +>>> f_dec = decorator(_trace)(f) +>>> f_dec.__code__.co_argcount +0 +>>> f_dec.__code__.co_varnames +('args', 'kw') ``` - -Calling ``f()`` gives you a ``ZeroDivisionError``. -But since the function is decorated, the traceback is longer: +This is not what one would expect: the `argcount` should be 2 since +the original functions has two arguments and the `varnames` should be +`a` and `b`. The only way to fix the issue is to go back to an implementation +of the decorator using ``exec``, which is provided for convenience since +version 5.1: ```python ->>> f() # doctest: +ELLIPSIS -Traceback (most recent call last): - ... - File "", line 2, in f - File "", line 4, in trace - return f(*args, **kw) - File "", line 3, in f - 1/0 -ZeroDivisionError: ... +>>> from decorator import decoratorx +>>> f_dec = decoratorx(_trace)(f) +>>> f_dec.__code__.co_argcount +2 +>>> f_dec.__code__.co_varnames +('a', 'b') ``` - -You see here the inner call to the decorator ``trace``, which calls -``f(*args, **kw)``, and a reference to ``File "", line 2, in f``. - -This latter reference is due to the fact that, internally, the decorator -module uses ``exec`` to generate the decorated function. Notice that -``exec`` is *not* responsible for the performance penalty, since is the -called *only once* (at function decoration time); it is *not* called -each time the decorated function is called. - -Presently, there is no clean way to avoid ``exec``. A clean solution -would require changing the CPython implementation, by -adding a hook to functions (to allow changing their signature directly). - -Even in Python 3.5, it is impossible to change the -function signature directly. Thus, the ``decorator`` module is -still useful! As a matter of fact, this is the main reason why I still -maintain the module and release new versions. - -It should be noted that in Python 3.5, a *lot* of improvements have -been made: you can decorate a function with -``func_tools.update_wrapper``, and ``pydoc`` will see the correct -signature. Unfortunately, the function will still have an incorrect -signature internally, as you can see by using -``inspect.getfullargspec``; so, all documentation tools using -``inspect.getfullargspec`` - which has been rightly deprecated - -will see the wrong signature. - -In the present implementation, decorators generated by ``decorator`` -can only be used on user-defined Python functions or methods. -They cannot be used on generic callable objects or built-in functions, -due to limitations of the standard library's ``inspect`` module, especially -for Python 2. In Python 3.5, many such limitations have been removed, but -I still think that it is cleaner and safer to decorate only functions and -coroutines. If you want to decorate things like classmethods/staticmethods -and general callables - which I will never support in the decorator module - -I suggest you to look at the [wrapt](https://wrapt.readthedocs.io/en/latest/) -project by Graeme Dumpleton. +Rather than using `decoratorx`, you should fix your introspection +routines to use ``inspect.Signature`` without fiddling with the +``__code__`` object. There is a strange quirk when decorating functions with keyword arguments, if one of the arguments has the same name used in the @@ -1270,7 +1258,7 @@ The error message looks really strange... until you realize that the caller function `_memoize` uses `func` as first argument, so there is a confusion between the positional argument and the -keywork arguments. +keyword arguments. The solution is to change the name of the first argument in `_memoize`, or to change the implementation like so: @@ -1297,22 +1285,7 @@ keyword arguments is not such a good idea, and you may want not to do that. -On a similar note, there is a restriction on argument names. For instance, -if you name an argument ``_call_`` or ``_func_``, you will get a ``NameError``: - -```python ->>> @trace -... def f(_func_): print(f) -... -Traceback (most recent call last): - ... -NameError: _func_ is overridden in -def f(_func_): - return _call_(_func_, _func_) - -``` - -Finally, the implementation is such that the decorated function makes +The implementation is such that the decorated function makes a (shallow) copy of the original function dictionary: ```python @@ -1330,8 +1303,44 @@ ``` -LICENSE (2-clause BSD) ---------------------------------------------- +Finally, you should be aware of the performance penalty of decorators. +The worse case is shown by the following example: + +```bash + $ cat performance.sh + python3 -m timeit -s " + from decorator import decorator + + @decorator + def do_nothing(func, *args, **kw): + return func(*args, **kw) + + @do_nothing + def f(): + pass + " "f()" + + python3 -m timeit -s " + def f(): + pass + " "f()" + +``` +On my laptop, using the ``do_nothing`` decorator instead of the +plain function is five times slower: + +```bash + $ bash performance.sh + 1000000 loops, best of 3: 1.39 usec per loop + 1000000 loops, best of 3: 0.278 usec per loop +``` + +Of course, a real life function probably does something more useful +than the function ``f`` here, so the real life performance penalty +*could* be negligible. As always, the only way to know if there is a +penalty in your specific use case is to measure it. + +## LICENSE (2-clause BSD) Copyright (c) 2005-2020, Michele Simionato All rights reserved. @@ -1365,69 +1374,9 @@ you are unhappy with it, send me a patch! """ -function_annotations = """Function annotations ---------------------------------------------- - -Python 3 introduced the concept of [function annotations]( -http://www.python.org/dev/peps/pep-3107/): the ability -to annotate the signature of a function with additional information, -stored in a dictionary named ``__annotations__``. The ``decorator`` module -(starting from release 3.3) will understand and preserve these annotations. - -Here is an example: - -```python ->>> @trace -... def f(x: 'the first argument', y: 'default argument'=1, z=2, -... *args: 'varargs', **kw: 'kwargs'): -... pass - -``` - -In order to introspect functions with annotations, one needs the -utility ``inspect.getfullargspec`` (introduced in Python 3, then -deprecated in Python 3.5, then undeprecated in Python 3.6): - -```python ->>> from inspect import getfullargspec ->>> argspec = getfullargspec(f) ->>> argspec.args -['x', 'y', 'z'] ->>> argspec.varargs -'args' ->>> argspec.varkw -'kw' ->>> argspec.defaults -(1, 2) ->>> argspec.kwonlyargs -[] ->>> argspec.kwonlydefaults - -``` - -You can check that the ``__annotations__`` dictionary is preserved: - -```python ->>> f.__annotations__ is f.__wrapped__.__annotations__ -True - -``` - -Here ``f.__wrapped__`` is the original undecorated function. -This attribute exists for consistency with the behavior of -``functools.update_wrapper``. - -Another attribute copied from the original function is ``__qualname__``, -the qualified name. This attribute was introduced in Python 3.3. -""" - -if sys.version_info < (3,): - function_annotations = '' - today = time.strftime('%Y-%m-%d') -__doc__ = (doc.replace('$VERSION', __version__).replace('$DATE', today) - .replace('$FUNCTION_ANNOTATIONS', function_annotations)) +__doc__ = doc.replace('$VERSION', __version__).replace('$DATE', today) def decorator_apply(dec, func): @@ -1436,7 +1385,7 @@ is not a signature-preserving decorator. """ return FunctionMaker.create( - func, 'return decfunc(%(signature)s)', + func, 'return decfunc(%(shortsignature)s)', dict(decfunc=dec(func)), __wrapped__=func) @@ -1654,71 +1603,48 @@ """ -if sys.version_info >= (3,): # tests for signatures specific to Python 3 +def test_kwonlydefaults(): + """ + >>> @trace + ... def f(arg, defarg=1, *args, kwonly=2): pass + ... + >>> f.__kwdefaults__ + {'kwonly': 2} + """ + - def test_kwonlydefaults(): - """ - >>> @trace - ... def f(arg, defarg=1, *args, kwonly=2): pass - ... - >>> f.__kwdefaults__ - {'kwonly': 2} - """ - - def test_kwonlyargs(): - """ - >>> @trace - ... def func(a, b, *args, y=2, z=3, **kwargs): - ... return y, z - ... - >>> func('a', 'b', 'c', 'd', 'e', y='y', z='z', cat='dog') - calling func with args ('a', 'b', 'c', 'd', 'e'), {'cat': 'dog', 'y': 'y', 'z': 'z'} - ('y', 'z') - """ - - def test_kwonly_no_args(): - """# this was broken with decorator 3.3.3 - >>> @trace - ... def f(**kw): pass - ... - >>> f() - calling f with args (), {} - """ - - def test_kwonly_star_notation(): - """ - >>> @trace - ... def f(*, a=1, **kw): pass - ... - >>> import inspect - >>> inspect.getfullargspec(f) - FullArgSpec(args=[], varargs=None, varkw='kw', defaults=None, kwonlyargs=['a'], kwonlydefaults={'a': 1}, annotations={}) - """ - - -@contextmanager -def before_after(before, after): - print(before) - yield - print(after) - - -ba = before_after('BEFORE', 'AFTER') # ContextManager instance - - -@ba -def hello(user): - """ - >>> ba.__class__.__name__ - 'ContextManager' - >>> hello('michele') - BEFORE - hello michele - AFTER +def test_kwonlyargs(): + """ + >>> @trace + ... def func(a, b, *args, y=2, z=3, **kwargs): + ... return y, z + ... + >>> func('a', 'b', 'c', 'd', 'e', y='y', z='z', cat='dog') + calling func with args ('a', 'b', 'c', 'd', 'e'), {'cat': 'dog', 'y': 'y', 'z': 'z'} + ('y', 'z') """ - print('hello %s' % user) +def test_kwonly_no_args(): + """# this was broken with decorator 3.3.3 + >>> @trace + ... def f(**kw): pass + ... + >>> f() + calling f with args (), {} + """ + + +def test_kwonly_star_notation(): + """ + >>> @trace + ... def f(*, a=1, **kw): pass + ... + >>> import inspect + >>> inspect.getfullargspec(f) + FullArgSpec(args=[], varargs=None, varkw='kw', defaults=None, kwonlyargs=['a'], kwonlydefaults={'a': 1}, annotations={}) + """ + # ####################### multiple dispatch ############################ # @@ -1892,6 +1818,73 @@ time.sleep(.1) +def chatty(func, *args, **kwargs): + print(args, sorted(kwargs.items())) + return func(*args, **kwargs) + + +@decorator(chatty) +def printsum(x=1, y=2): + print(x + y) + + +@decorator(chatty, kwsyntax=True) +def printsum2(x=1, y=2): + print(x + y) + + +def chattywrapper(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + print(args, kwargs) + return func(*args, **kwargs) + return functools.wraps(wrapper) + +# ####################### changing the signature ########################## # + + +# see https://github.com/micheles/decorator/pull/85 +def to_method(f): + """ + Takes a function with signature (..., context) and returns a new + function with signature (self, ...) to be used a a method in a + class with a .context attribute. + """ + sig = inspect.signature(f) + params = list(sig.parameters.values()) + assert params[-1].name == 'context' + self = inspect.Parameter('self', inspect.Parameter.POSITIONAL_OR_KEYWORD) + params.insert(0, self) # insert self + del params[-1] # remove context + newsig = '%s%s' % (f.__name__, sig.replace(parameters=params)) + return FunctionMaker.create( + newsig, 'context = self.context; return _func_%s' % sig, + dict(_func_=f)) + + +def foo(x, context=None): + return x + + +def bar(x, y, context): + return x + y + + +class Client: + def __init__(self, context): + self.context = context + + +def test_change_sig(): + """ + >>> Client.foo = to_method(foo) + >>> Client.bar = to_method(bar) + >>> c = Client(None) + >>> assert c.foo(1) == 1 + >>> assert c.bar(1, 2) == 3 + """ + + if __name__ == '__main__': import doctest doctest.testmod() diff -Nru python-decorator-4.4.2/src/tests/test.py python-decorator-5.1.1/src/tests/test.py --- python-decorator-4.4.2/src/tests/test.py 2020-02-29 04:50:53.000000000 +0000 +++ python-decorator-5.1.1/src/tests/test.py 2021-09-11 04:07:12.000000000 +0000 @@ -1,21 +1,15 @@ -from __future__ import absolute_import import sys import doctest import unittest import decimal import inspect -import functools -import collections -from collections import defaultdict -try: - c = collections.abc -except AttributeError: - c = collections +from asyncio import get_event_loop +from collections import defaultdict, ChainMap, abc as c from decorator import dispatch_on, contextmanager, decorator try: - from . import documentation as doc -except (ImportError, ValueError, SystemError): # depending on the py-version - import documentation as doc + from . import documentation as doc # good with pytest +except ImportError: + import documentation as doc # good with `python src/tests/test.py` @contextmanager @@ -29,22 +23,21 @@ raise Exception('Expected %s' % etype.__name__) -if sys.version_info >= (3, 5): - exec('''from asyncio import get_event_loop - @decorator async def before_after(coro, *args, **kwargs): return "" + (await coro(*args, **kwargs)) + "" + @decorator def coro_to_func(coro, *args, **kw): return get_event_loop().run_until_complete(coro(*args, **kw)) + class CoroutineTestCase(unittest.TestCase): def test_before_after(self): @before_after async def coro(x): - return x + return x self.assertTrue(inspect.iscoroutinefunction(coro)) out = get_event_loop().run_until_complete(coro('x')) self.assertEqual(out, 'x') @@ -55,7 +48,6 @@ return x self.assertFalse(inspect.iscoroutinefunction(coro)) self.assertEqual(coro('x'), 'x') -''') def gen123(): @@ -80,28 +72,42 @@ err = doctest.testmod(doc)[0] self.assertEqual(err, 0) + def test_copy_dunder_attrs(self): + traced = doc.trace(doc.foo) + self.assertIn('documentation', traced.__module__) + self.assertEqual(traced.__annotations__, {}) + self.assertEqual(traced.__defaults__, (None,)) + def test_singledispatch1(self): - if hasattr(functools, 'singledispatch'): - with assertRaises(RuntimeError): - doc.singledispatch_example1() + with assertRaises(RuntimeError): + doc.singledispatch_example1() def test_singledispatch2(self): - if hasattr(functools, 'singledispatch'): - doc.singledispatch_example2() + doc.singledispatch_example2() + + def test_context_manager(self): + + @contextmanager + def before_after(before, after): + print(before) + yield + print(after) + + @before_after('BEFORE', 'AFTER') + def hello_user(user): + print('hello %s' % user) + + argspec = inspect.getfullargspec(hello_user) + self.assertEqual(argspec.args, ['user']) class ExtraTestCase(unittest.TestCase): def test_qualname(self): - if sys.version_info >= (3, 3): - self.assertEqual(doc.hello.__qualname__, 'hello') - else: - with assertRaises(AttributeError): - doc.hello.__qualname__ + self.assertEqual(doc.operation1.__qualname__, 'operation1') def test_signature(self): - if hasattr(inspect, 'signature'): - sig = inspect.signature(doc.f1) - self.assertEqual(str(sig), '(x)') + sig = inspect.signature(doc.f1) + self.assertEqual(str(sig), '(x)') def test_unique_filenames(self): @decorator @@ -125,10 +131,13 @@ @d1 def f1(x, y, z): pass - self.assertNotEqual(d1.__code__.co_filename, d2.__code__.co_filename) - self.assertNotEqual(f1.__code__.co_filename, f2.__code__.co_filename) - self.assertNotEqual(f1_orig.__code__.co_filename, - f1.__code__.co_filename) + + self.assertEqual(d1.__code__.co_filename, + d2.__code__.co_filename) + self.assertEqual(f1.__code__.co_filename, + f2.__code__.co_filename) + self.assertEqual(f1_orig.__code__.co_filename, + f1.__code__.co_filename) def test_no_first_arg(self): @decorator @@ -137,17 +146,19 @@ @example def func(**kw): + "Docstring" return kw # there is no confusion when passing args as a keyword argument self.assertEqual(func(args='a'), {'args': 'a'}) + self.assertEqual(func.__doc__, "Docstring") def test_decorator_factory(self): # similar to what IPython is doing in traitlets.config.application @decorator def catch_config_error(method, app, *args, **kwargs): return method(app) - catch_config_error(lambda app: None) + catch_config_error(lambda app, **kw: None)(1) def test_add1(self): # similar to what IPython is doing in traitlets.config.application @@ -159,6 +170,29 @@ return x self.assertEqual(add(f, 2)(0), 2) + def test_dan_schult(self): + # see https://github.com/micheles/decorator/issues/120 + @decorator + def prnt(func, index=0, *args, **kw): + print(args[index]) + return func(*args, **kw) + + @prnt(index=2) # print the value of the third argument + def f(a, b, c=None): + return [a, b, c] + + self.assertEqual(f(0, 1), [0, 1, None]) + + def test_slow_wrapper(self): + # see https://github.com/micheles/decorator/issues/123 + dd = defaultdict(list) + doc.trace(defaultdict.__setitem__)(dd, 'x', [1]) + self.assertEqual(dd['x'], [1]) + doc.trace(defaultdict.__delitem__)(dd, 'x') + self.assertEqual(dd['x'], []) + # NB: defaultdict.__getitem__ has no signature and cannot be + # decorated in CPython, while it is regular in PyPy + # ################### test dispatch_on ############################# # # adapted from test_functools in Python 3.5 @@ -290,14 +324,13 @@ self.assertEqual(g(f), "sized") self.assertEqual(g(t), "sized") - if hasattr(c, 'ChainMap'): - g.register(c.ChainMap)(lambda obj: "chainmap") - # irrelevant ABCs registered - self.assertEqual(g(d), "mutablemapping") - self.assertEqual(g(l), "sized") - self.assertEqual(g(s), "sized") - self.assertEqual(g(f), "sized") - self.assertEqual(g(t), "sized") + g.register(ChainMap)(lambda obj: "chainmap") + # irrelevant ABCs registered + self.assertEqual(g(d), "mutablemapping") + self.assertEqual(g(l), "sized") + self.assertEqual(g(s), "sized") + self.assertEqual(g(f), "sized") + self.assertEqual(g(t), "sized") g.register(c.MutableSequence)(lambda obj: "mutablesequence") self.assertEqual(g(d), "mutablemapping")