Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-decorator for
openSUSE:Factory checked in at 2026-05-20 16:47:47
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-decorator (Old)
and /work/SRC/openSUSE:Factory/.python-decorator.new.1966 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-decorator"
Wed May 20 16:47:47 2026 rev:28 rq:1353608 version:5.3.0
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-decorator/python-decorator.changes
2025-05-23 14:27:41.741322659 +0200
+++
/work/SRC/openSUSE:Factory/.python-decorator.new.1966/python-decorator.changes
2026-05-20 16:47:47.968383399 +0200
@@ -1,0 +2,9 @@
+Sun May 17 18:49:43 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 5.3.0:
+ * Added official support for Python 3.14
+ * Fixed a bug with "return await"
+ * Moved decorator.py to a package structure
+ * added a stub file (decorator/__init__.pyi)
+
+-------------------------------------------------------------------
Old:
----
decorator-5.2.1.tar.gz
New:
----
decorator-5.3.0.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-decorator.spec ++++++
--- /var/tmp/diff_new_pack.CN08Hn/_old 2026-05-20 16:47:49.912463719 +0200
+++ /var/tmp/diff_new_pack.CN08Hn/_new 2026-05-20 16:47:49.924464215 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-decorator
#
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2026 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -20,7 +20,7 @@
#
%{?sle15_python_module_pythons}
Name: python-decorator
-Version: 5.2.1
+Version: 5.3.0
Release: 0
Summary: Decorators for Humans
License: BSD-2-Clause
@@ -45,8 +45,7 @@
redirecting_stdout, locked, etc. more accessible.
%prep
-%setup -q -n decorator-%{version}
-%autopatch -p1
+%autosetup -p1 -n decorator-%{version}
%build
%pyproject_wheel
@@ -61,7 +60,6 @@
%files %{python_files}
%license LICENSE.txt
%doc CHANGES.md README.rst
-%{python_sitelib}/decorator.py*
-%pycache_only %{python_sitelib}/__pycache__/decorator.*.py*
+%{python_sitelib}/decorator
%{python_sitelib}/decorator-%{version}.dist-info
++++++ decorator-5.2.1.tar.gz -> decorator-5.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/CHANGES.md
new/decorator-5.3.0/CHANGES.md
--- old/decorator-5.2.1/CHANGES.md 2025-02-24 05:34:46.000000000 +0100
+++ new/decorator-5.3.0/CHANGES.md 2026-05-17 08:34:15.000000000 +0200
@@ -3,6 +3,12 @@
## Unreleased
+Added official support for Python 3.14 (thanks to Hugo van Kemenade,
+David Cain and the GitHub user bersbersbers).
+Fixed a bug with "return await" contributed by Kadir Can Ozden.
+Moved decorator.py to a package structure (decorator/__init__.py) and
+added a stub file (decorator/__init__.pyi) contributed by Marco Gorelli.
+
## 5.2.1 (2025-02-24)
Shiv Krishna Jaiswal suggested how to manage che case of functions
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/LICENSE.txt
new/decorator-5.3.0/LICENSE.txt
--- old/decorator-5.2.1/LICENSE.txt 2025-02-22 10:11:03.000000000 +0100
+++ new/decorator-5.3.0/LICENSE.txt 2026-05-17 07:17:05.000000000 +0200
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2025, Michele Simionato
+Copyright (c) 2005-2026, Michele Simionato
All rights reserved.
Redistribution and use in source and binary forms, with or without
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/MANIFEST.in
new/decorator-5.3.0/MANIFEST.in
--- old/decorator-5.2.1/MANIFEST.in 2025-02-24 05:15:02.000000000 +0100
+++ new/decorator-5.3.0/MANIFEST.in 2026-05-17 07:17:05.000000000 +0200
@@ -1,4 +1,4 @@
-include README.rst LICENSE.txt CHANGES.md performance.sh
-include src/decorator.py
+include CHANGES.md performance.sh
+include src/decorator
include tests/*.py
graft docs
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/PKG-INFO new/decorator-5.3.0/PKG-INFO
--- old/decorator-5.2.1/PKG-INFO 2025-02-24 05:41:30.464040000 +0100
+++ new/decorator-5.3.0/PKG-INFO 2026-05-17 08:59:52.503007700 +0200
@@ -1,13 +1,11 @@
-Metadata-Version: 2.2
+Metadata-Version: 2.4
Name: decorator
-Version: 5.2.1
+Version: 5.3.0
Summary: Decorators for Humans
Author-email: Michele Simionato <[email protected]>
-License: BSD-2-Clause
Keywords: decorators
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
@@ -17,12 +15,14 @@
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
+Dynamic: license-file
Decorators for Humans
=====================
@@ -58,7 +58,7 @@
If you have the source code installation you can run the tests with
- `$ python src/tests/test.py -v`
+ `$ python tests/test.py -v`
or (if you have setuptools installed)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/README.rst
new/decorator-5.3.0/README.rst
--- old/decorator-5.2.1/README.rst 2023-08-23 18:27:29.000000000 +0200
+++ new/decorator-5.3.0/README.rst 2026-05-17 07:17:05.000000000 +0200
@@ -32,7 +32,7 @@
If you have the source code installation you can run the tests with
- `$ python src/tests/test.py -v`
+ `$ python tests/test.py -v`
or (if you have setuptools installed)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/docs/documentation.md
new/decorator-5.3.0/docs/documentation.md
--- old/decorator-5.2.1/docs/documentation.md 2025-02-24 05:41:00.000000000
+0100
+++ new/decorator-5.3.0/docs/documentation.md 2026-05-17 08:58:19.000000000
+0200
@@ -3,9 +3,9 @@
|Author | Michele Simionato|
|---|---|
|E-mail | [email protected]|
-|Version| 5.2.0 (2025-02-24)|
+|Version| 5.3.0 (2026-05-17)|
|Supports| Python 3.7, 3.8, 3.9, 3.10, 3.11, 3.12|
-|Download page| https://pypi.org/project/decorator/5.2.0|
+|Download page| https://pypi.org/project/decorator/5.3.0|
|Installation| ``pip install decorator``|
|License | BSD license|
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/pyproject.toml
new/decorator-5.3.0/pyproject.toml
--- old/decorator-5.2.1/pyproject.toml 2025-02-22 10:11:03.000000000 +0100
+++ new/decorator-5.3.0/pyproject.toml 2026-05-17 07:17:05.000000000 +0200
@@ -1,5 +1,5 @@
[build-system]
-requires = ["setuptools"]
+requires = ["setuptools>=61.0.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
@@ -12,11 +12,9 @@
dynamic = ["version"]
requires-python = ">=3.8"
keywords = ["decorators"]
-license = {text = "BSD-2-Clause"}
classifiers = [
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
- 'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
@@ -26,9 +24,16 @@
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
+ 'Programming Language :: Python :: 3.14',
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Software Development :: Libraries',
'Topic :: Utilities']
[tool.setuptools.dynamic]
version.attr = "decorator.__version__"
+
+[tool.setuptools.packages.find]
+where = ["src"]
+
+[tool.setuptools.package-data]
+"decorator" = ["*.pyi", "py.typed"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/src/decorator/__init__.py
new/decorator-5.3.0/src/decorator/__init__.py
--- old/decorator-5.2.1/src/decorator/__init__.py 1970-01-01
01:00:00.000000000 +0100
+++ new/decorator-5.3.0/src/decorator/__init__.py 2026-05-17
08:21:49.000000000 +0200
@@ -0,0 +1,470 @@
+# ######################### LICENSE ############################ #
+
+# Copyright (c) 2005-2025, Michele Simionato
+# All rights reserved.
+
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+
+# Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# Redistributions in bytecode form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in
+# the documentation and/or other materials provided with the
+# distribution.
+
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+
+"""
+Decorator module, see
+https://github.com/micheles/decorator/blob/master/docs/documentation.md
+for the documentation.
+"""
+import re
+import sys
+import inspect
+import operator
+import itertools
+import functools
+from contextlib import _GeneratorContextManager
+from inspect import getfullargspec, iscoroutinefunction, isgeneratorfunction
+from typing import Any, Dict, List, Optional
+try:
+ import annotationlib # in Python 3.14+
+
+ def inspect_sig(func):
+ return inspect.signature(
+ func, annotation_format=annotationlib.Format.FORWARDREF)
+
+except ImportError:
+ inspect_sig = inspect.signature
+
+__version__ = '5.3.0'
+
+DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(')
+POS = inspect.Parameter.POSITIONAL_OR_KEYWORD
+EMPTY = inspect.Parameter.empty
+
+
+# this is not used anymore in the core, but kept for backward compatibility
+class FunctionMaker:
+ """
+ An object with the ability to create functions with a given signature.
+ It has attributes name, doc, module, signature, defaults, dict and
+ methods update and make.
+ """
+
+ # Atomic get-and-increment provided by the GIL
+ _compile_count = itertools.count()
+
+ # make pylint happy
+ args: List[str] = []
+ varargs = varkw = defaults = None
+ kwonlyargs: List[str] = []
+ kwonlydefaults: Optional[Dict[str, Any]] = None
+
+ def __init__(self, func=None, name=None, signature=None,
+ defaults=None, doc=None, module=None, funcdict=None):
+ self.shortsignature = signature
+ if func:
+ # func can be a class or a callable, but not an instance method
+ self.name = func.__name__
+ if self.name == '<lambda>': # small hack for lambda functions
+ self.name = '_lambda_'
+ self.doc = func.__doc__
+ self.module = func.__module__
+ if inspect.isroutine(func) or isinstance(func, functools.partial):
+ argspec = getfullargspec(func)
+ self.annotations = getattr(func, '__annotations__', {})
+ for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs',
+ 'kwonlydefaults'):
+ setattr(self, a, getattr(argspec, a))
+ for i, arg in enumerate(self.args):
+ setattr(self, 'arg%d' % i, arg)
+ allargs = list(self.args)
+ allshortargs = list(self.args)
+ if self.varargs:
+ allargs.append('*' + self.varargs)
+ allshortargs.append('*' + self.varargs)
+ elif self.kwonlyargs:
+ allargs.append('*') # single star syntax
+ for a in self.kwonlyargs:
+ allargs.append('%s=None' % a)
+ allshortargs.append('{}={}'.format(a, a))
+ if self.varkw:
+ allargs.append('**' + self.varkw)
+ allshortargs.append('**' + self.varkw)
+ self.signature = ', '.join(allargs)
+ self.shortsignature = ', '.join(allshortargs)
+ self.dict = func.__dict__.copy()
+ # func=None happens when decorating a caller
+ if name:
+ self.name = name
+ if signature is not None:
+ self.signature = signature
+ if defaults:
+ self.defaults = defaults
+ if doc:
+ self.doc = doc
+ if module:
+ self.module = module
+ if funcdict:
+ self.dict = funcdict
+ # check existence required attributes
+ assert hasattr(self, 'name')
+ if not hasattr(self, 'signature'):
+ 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
+ """
+ func.__name__ = self.name
+ func.__doc__ = getattr(self, 'doc', None)
+ func.__dict__ = getattr(self, 'dict', {})
+ func.__defaults__ = self.defaults
+ func.__kwdefaults__ = self.kwonlydefaults or None
+ func.__annotations__ = getattr(self, 'annotations', None)
+ try:
+ frame = sys._getframe(3)
+ except AttributeError: # for IronPython and similar implementations
+ callermodule = '?'
+ else:
+ callermodule = frame.f_globals.get('__name__', '?')
+ func.__module__ = getattr(self, 'module', callermodule)
+ 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
+ """
+ src = src_templ % vars(self) # expand name and signature
+ evaldict = evaldict or {}
+ mo = DEF.search(src)
+ if mo is None:
+ raise SyntaxError('not a valid function template\n%s' % src)
+ name = mo.group(1) # extract the function name
+ names = set([name] + [arg.strip(' *') for arg in
+ self.shortsignature.split(',')])
+ for n in names:
+ if n in ('_func_', '_call_'):
+ raise NameError('{} is overridden in\n{}'.format(n, src))
+
+ if not src.endswith('\n'): # add a newline for old Pythons
+ src += '\n'
+
+ # Ensure each generated function has a unique filename for profilers
+ # (such as cProfile) that depend on the tuple of (<filename>,
+ # <definition line>, <function name>) being unique.
+ filename = '<decorator-gen-%d>' % next(self._compile_count)
+ try:
+ code = compile(src, filename, 'single')
+ exec(code, evaldict)
+ except Exception:
+ print('Error in generated code:', file=sys.stderr)
+ print(src, file=sys.stderr)
+ raise
+ func = evaldict[name]
+ if addsource:
+ attrs['__source__'] = src
+ self.update(func, **attrs)
+ return func
+
+ @classmethod
+ def create(cls, obj, body, evaldict, defaults=None,
+ doc=None, module=None, addsource=True, **attrs):
+ """
+ Create a function from the strings name, signature and body.
+ evaldict is the evaluation dictionary. If addsource is true an
+ attribute __source__ is added to the result. The attributes attrs
+ are added, if any.
+ """
+ if isinstance(obj, str): # "name(signature)"
+ name, rest = obj.strip().split('(', 1)
+ signature = rest[:-1] # strip a right parens
+ func = None
+ else: # a function
+ name = None
+ signature = None
+ func = obj
+ self = cls(func, name, signature, defaults, doc, module)
+ ibody = '\n'.join(' ' + line for line in body.splitlines())
+ caller = evaldict.get('_call_') # when called from `decorate`
+ if caller and iscoroutinefunction(caller):
+ body = ('async def %(name)s(%(signature)s):\n' + ibody)
+ body = re.sub(r'\breturn\b', 'return await', body)
+ else:
+ body = 'def %(name)s(%(signature)s):\n' + ibody
+ return self.make(body, evaldict, addsource, **attrs)
+
+
+def fix(args, kwargs, sig):
+ """
+ Fix args and kwargs to be consistent with the signature
+ """
+ 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_sig(func)
+ if isinstance(func, functools.partial):
+ func = functools.update_wrapper(func, func.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)
+ yield from caller(func, *(extras + args), **kw)
+ else:
+ def fun(*args, **kw):
+ if not kwsyntax:
+ args, kw = fix(args, kw, sig)
+ return caller(func, *(extras + args), **kw)
+
+ 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.__name__ = func.__name__
+ except AttributeError: # happens with old versions of numpy.vectorize
+ func.__name__ == 'noname'
+ try:
+ fun.__dict__.update(func.__dict__)
+ except AttributeError:
+ pass
+ return fun
+
+
+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, (), kwsyntax)
+ # else return a decorator function
+ sig = inspect_sig(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:
+ 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 ####################### #
+
+
+class ContextManager(_GeneratorContextManager):
+ 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)
+
+
+_contextmanager = decorator(ContextManager)
+
+
+def contextmanager(func):
+ # Enable Pylint config: contextmanager-decorators=decorator.contextmanager
+ return _contextmanager(func)
+
+
+# ############################ dispatch_on ############################ #
+
+def append(a, vancestors):
+ """
+ Append ``a`` to the list of the virtual ancestors, unless it is already
+ included.
+ """
+ add = True
+ for j, va in enumerate(vancestors):
+ if issubclass(va, a):
+ add = False
+ break
+ if issubclass(a, va):
+ vancestors[j] = a
+ add = False
+ if add:
+ vancestors.append(a)
+
+
+# inspired from simplegeneric by P.J. Eby and functools.singledispatch
+def dispatch_on(*dispatch_args):
+ """
+ Factory of decorators turning a function into a generic function
+ dispatching on the given arguments.
+ """
+ assert dispatch_args, 'No dispatch args passed'
+ dispatch_str = '(%s,)' % ', '.join(dispatch_args)
+
+ def check(arguments, wrong=operator.ne, msg=''):
+ """Make sure one passes the expected number of arguments"""
+ if wrong(len(arguments), len(dispatch_args)):
+ raise TypeError('Expected %d arguments, got %d%s' %
+ (len(dispatch_args), len(arguments), msg))
+
+ def gen_func_dec(func):
+ """Decorator turning a function into a generic function"""
+
+ # first check the dispatch arguments
+ argset = set(getfullargspec(func).args)
+ if not set(dispatch_args) <= argset:
+ raise NameError('Unknown dispatch arguments %s' % dispatch_str)
+
+ typemap = {}
+
+ def vancestors(*types):
+ """
+ Get a list of sets of virtual ancestors for the given types
+ """
+ check(types)
+ ras = [[] for _ in range(len(dispatch_args))]
+ for types_ in typemap:
+ for t, type_, ra in zip(types, types_, ras):
+ if issubclass(t, type_) and type_ not in t.mro():
+ append(type_, ra)
+ return [set(ra) for ra in ras]
+
+ def ancestors(*types):
+ """
+ Get a list of virtual MROs, one for each type
+ """
+ check(types)
+ lists = []
+ for t, vas in zip(types, vancestors(*types)):
+ n_vas = len(vas)
+ if n_vas > 1:
+ raise RuntimeError(
+ 'Ambiguous dispatch for {}: {}'.format(t, vas))
+ elif n_vas == 1:
+ va, = vas
+ mro = type('t', (t, va), {}).mro()[1:]
+ else:
+ mro = t.mro()
+ lists.append(mro[:-1]) # discard t and object
+ return lists
+
+ def register(*types):
+ """
+ Decorator to register an implementation for the given types
+ """
+ check(types)
+
+ def dec(f):
+ check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__)
+ typemap[types] = f
+ return f
+ return dec
+
+ def dispatch_info(*types):
+ """
+ An utility to introspect the dispatch algorithm
+ """
+ check(types)
+ lst = []
+ for ancs in itertools.product(*ancestors(*types)):
+ lst.append(tuple(a.__name__ for a in ancs))
+ return lst
+
+ def _dispatch(dispatch_args, *args, **kw):
+ types = tuple(type(arg) for arg in dispatch_args)
+ try: # fast path
+ f = typemap[types]
+ except KeyError:
+ pass
+ else:
+ return f(*args, **kw)
+ combinations = itertools.product(*ancestors(*types))
+ next(combinations) # the first one has been already tried
+ for types_ in combinations:
+ f = typemap.get(types_)
+ if f is not None:
+ return f(*args, **kw)
+
+ # else call the default implementation
+ return func(*args, **kw)
+
+ return FunctionMaker.create(
+ func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str,
+ dict(_f_=_dispatch), register=register, default=func,
+ typemap=typemap, vancestors=vancestors, ancestors=ancestors,
+ dispatch_info=dispatch_info, __wrapped__=func)
+
+ gen_func_dec.__name__ = 'dispatch_on' + dispatch_str
+ return gen_func_dec
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/src/decorator/__init__.pyi
new/decorator-5.3.0/src/decorator/__init__.pyi
--- old/decorator-5.2.1/src/decorator/__init__.pyi 1970-01-01
01:00:00.000000000 +0100
+++ new/decorator-5.3.0/src/decorator/__init__.pyi 2026-05-17
08:21:49.000000000 +0200
@@ -0,0 +1,79 @@
+import inspect
+import annotationlib
+from builtins import dict as _dict # alias to avoid conflicts with attribute
name
+from collections.abc import Callable, Generator, Iterator
+from contextlib import _GeneratorContextManager
+from inspect import Signature, getfullargspec as getfullargspec,
iscoroutinefunction as iscoroutinefunction
+from re import Pattern
+from typing import Any, Final, Literal, TypeVar, Tuple
+from typing_extensions import ParamSpec
+
+_C = TypeVar("_C", bound=Callable[..., Any])
+_Func = TypeVar("_Func", bound=Callable[..., Any])
+_T = TypeVar("_T")
+_P = ParamSpec("_P")
+
+DEF: Final[Pattern[str]]
+POS: Final[Literal[inspect._ParameterKind.POSITIONAL_OR_KEYWORD]]
+EMPTY: Final[type[inspect._empty]]
+
+def inspect_sig(
+ func: _Func
+) -> Tuple: ...
+
+
+class FunctionMaker:
+ args: list[str]
+ varargs: str | None
+ varkw: str | None
+ defaults: tuple[Any, ...] | None
+ kwonlyargs: list[str]
+ kwonlydefaults: dict[str, Any] | None
+ shortsignature: str | None
+ name: str
+ doc: str | None
+ module: str | None
+ annotations: _dict[str, Any]
+ signature: str
+ dict: _dict[str, Any]
+ def __init__(
+ self,
+ func: Callable[..., Any] | None = ...,
+ name: str | None = ...,
+ signature: str | None = ...,
+ defaults: tuple[Any, ...] | None = ...,
+ doc: str | None = ...,
+ module: str | None = ...,
+ funcdict: _dict[str, Any] | None = ...,
+ ) -> None: ...
+ def update(self, func: Any, **kw: Any) -> None: ...
+ def make(
+ self, src_templ: str, evaldict: _dict[str, Any] | None = ...,
addsource: bool = ..., **attrs: Any
+ ) -> Callable[..., Any]: ...
+ @classmethod
+ def create(
+ cls,
+ obj: Any,
+ body: str,
+ evaldict: _dict[str, Any],
+ defaults: tuple[Any, ...] | None = ...,
+ doc: str | None = ...,
+ module: str | None = ...,
+ addsource: bool = ...,
+ **attrs: Any,
+ ) -> Callable[..., Any]: ...
+
+def fix(args: tuple[Any, ...], kwargs: dict[str, Any], sig: Signature) ->
tuple[tuple[Any, ...], dict[str, Any]]: ...
+def decorate(func: _Func, caller: Callable[..., Any], extras: tuple[Any, ...]
= ..., kwsyntax: bool = False) -> _Func: ...
+def decoratorx(caller: Callable[..., Any]) -> Callable[..., Any]: ...
+def decorator(
+ caller: Callable[..., Any], _func: Callable[..., Any] | None = None,
kwsyntax: bool = False
+) -> Callable[[Callable[..., Any]], Callable[..., Any]]: ...
+
+class ContextManager(_GeneratorContextManager[_T]):
+ def __init__(self, g: Callable[..., Generator[_T]], *a: Any, **k: Any) ->
None: ...
+ def __call__(self, func: _C) -> _C: ...
+
+def contextmanager(func: Callable[_P, Iterator[_T]]) -> Callable[_P,
ContextManager[_T]]: ...
+def append(a: type, vancestors: list[type]) -> None: ...
+def dispatch_on(*dispatch_args: Any) -> Callable[[Callable[..., Any]],
Callable[..., Any]]: ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/decorator-5.2.1/src/decorator/out/decorator/__init__.pyi
new/decorator-5.3.0/src/decorator/out/decorator/__init__.pyi
--- old/decorator-5.2.1/src/decorator/out/decorator/__init__.pyi
1970-01-01 01:00:00.000000000 +0100
+++ new/decorator-5.3.0/src/decorator/out/decorator/__init__.pyi
2026-05-17 08:12:27.000000000 +0200
@@ -0,0 +1,44 @@
+import inspect
+from _typeshed import Incomplete
+from contextlib import _GeneratorContextManager
+from typing import Any
+
+def inspect_sig(func): ...
+inspect_sig = inspect.signature
+__version__: str
+DEF: Incomplete
+POS: Incomplete
+EMPTY: Incomplete
+
+class FunctionMaker:
+ args: list[str]
+ varargs: Incomplete
+ varkw: Incomplete
+ defaults: Incomplete
+ kwonlyargs: list[str]
+ kwonlydefaults: dict[str, Any] | None
+ shortsignature: Incomplete
+ name: Incomplete
+ doc: Incomplete
+ module: Incomplete
+ annotations: Incomplete
+ signature: Incomplete
+ dict: Incomplete
+ def __init__(self, func=None, name=None, signature=None, defaults=None,
doc=None, module=None, funcdict=None) -> None: ...
+ def update(self, func, **kw) -> None: ...
+ def make(self, src_templ, evaldict=None, addsource: bool = False,
**attrs): ...
+ @classmethod
+ def create(cls, obj, body, evaldict, defaults=None, doc=None, module=None,
addsource: bool = True, **attrs): ...
+
+def fix(args, kwargs, sig): ...
+def decorate(func, caller, extras=(), kwsyntax: bool = False): ...
+def decoratorx(caller): ...
+def decorator(caller, _func=None, kwsyntax: bool = False): ...
+
+class ContextManager(_GeneratorContextManager):
+ def __init__(self, g, *a, **k) -> None: ...
+ def __call__(self, func): ...
+
+def contextmanager(func): ...
+def append(a, vancestors) -> None: ...
+def dispatch_on(*dispatch_args): ...
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/src/decorator.egg-info/PKG-INFO
new/decorator-5.3.0/src/decorator.egg-info/PKG-INFO
--- old/decorator-5.2.1/src/decorator.egg-info/PKG-INFO 2025-02-24
05:41:30.000000000 +0100
+++ new/decorator-5.3.0/src/decorator.egg-info/PKG-INFO 2026-05-17
08:59:52.000000000 +0200
@@ -1,13 +1,11 @@
-Metadata-Version: 2.2
+Metadata-Version: 2.4
Name: decorator
-Version: 5.2.1
+Version: 5.3.0
Summary: Decorators for Humans
Author-email: Michele Simionato <[email protected]>
-License: BSD-2-Clause
Keywords: decorators
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
@@ -17,12 +15,14 @@
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
License-File: LICENSE.txt
+Dynamic: license-file
Decorators for Humans
=====================
@@ -58,7 +58,7 @@
If you have the source code installation you can run the tests with
- `$ python src/tests/test.py -v`
+ `$ python tests/test.py -v`
or (if you have setuptools installed)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/src/decorator.egg-info/SOURCES.txt
new/decorator-5.3.0/src/decorator.egg-info/SOURCES.txt
--- old/decorator-5.2.1/src/decorator.egg-info/SOURCES.txt 2025-02-24
05:41:30.000000000 +0100
+++ new/decorator-5.3.0/src/decorator.egg-info/SOURCES.txt 2026-05-17
08:59:52.000000000 +0200
@@ -8,11 +8,13 @@
docs/conf.py
docs/documentation.md
docs/index.rst
-src/decorator.py
+src/decorator/__init__.py
+src/decorator/__init__.pyi
+src/decorator/py.typed
src/decorator.egg-info/PKG-INFO
src/decorator.egg-info/SOURCES.txt
src/decorator.egg-info/dependency_links.txt
-src/decorator.egg-info/pbr.json
src/decorator.egg-info/top_level.txt
+src/decorator/out/decorator/__init__.pyi
tests/documentation.py
tests/test.py
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/src/decorator.egg-info/pbr.json
new/decorator-5.3.0/src/decorator.egg-info/pbr.json
--- old/decorator-5.2.1/src/decorator.egg-info/pbr.json 2017-01-15
10:28:27.000000000 +0100
+++ new/decorator-5.3.0/src/decorator.egg-info/pbr.json 1970-01-01
01:00:00.000000000 +0100
@@ -1 +0,0 @@
-{"is_release": false, "git_version": "8608a46"}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/src/decorator.py
new/decorator-5.3.0/src/decorator.py
--- old/decorator-5.2.1/src/decorator.py 2025-02-24 05:37:26.000000000
+0100
+++ new/decorator-5.3.0/src/decorator.py 1970-01-01 01:00:00.000000000
+0100
@@ -1,459 +0,0 @@
-# ######################### LICENSE ############################ #
-
-# Copyright (c) 2005-2025, Michele Simionato
-# All rights reserved.
-
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are
-# met:
-
-# Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-# Redistributions in bytecode form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in
-# the documentation and/or other materials provided with the
-# distribution.
-
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-# HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
-# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
-# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
-# DAMAGE.
-
-"""
-Decorator module, see
-https://github.com/micheles/decorator/blob/master/docs/documentation.md
-for the documentation.
-"""
-import re
-import sys
-import inspect
-import operator
-import itertools
-import functools
-from contextlib import _GeneratorContextManager
-from inspect import getfullargspec, iscoroutinefunction, isgeneratorfunction
-
-__version__ = '5.2.1'
-
-DEF = re.compile(r'\s*def\s*([_\w][_\w\d]*)\s*\(')
-POS = inspect.Parameter.POSITIONAL_OR_KEYWORD
-EMPTY = inspect.Parameter.empty
-
-
-# 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.
- It has attributes name, doc, module, signature, defaults, dict and
- methods update and make.
- """
-
- # Atomic get-and-increment provided by the GIL
- _compile_count = itertools.count()
-
- # make pylint happy
- args = varargs = varkw = defaults = kwonlyargs = kwonlydefaults = ()
-
- def __init__(self, func=None, name=None, signature=None,
- defaults=None, doc=None, module=None, funcdict=None):
- self.shortsignature = signature
- if func:
- # func can be a class or a callable, but not an instance method
- self.name = func.__name__
- if self.name == '<lambda>': # small hack for lambda functions
- self.name = '_lambda_'
- self.doc = func.__doc__
- self.module = func.__module__
- if inspect.isroutine(func) or isinstance(func, functools.partial):
- argspec = getfullargspec(func)
- self.annotations = getattr(func, '__annotations__', {})
- for a in ('args', 'varargs', 'varkw', 'defaults', 'kwonlyargs',
- 'kwonlydefaults'):
- setattr(self, a, getattr(argspec, a))
- for i, arg in enumerate(self.args):
- setattr(self, 'arg%d' % i, arg)
- allargs = list(self.args)
- allshortargs = list(self.args)
- if self.varargs:
- allargs.append('*' + self.varargs)
- allshortargs.append('*' + self.varargs)
- elif self.kwonlyargs:
- allargs.append('*') # single star syntax
- for a in self.kwonlyargs:
- allargs.append('%s=None' % a)
- allshortargs.append('%s=%s' % (a, a))
- if self.varkw:
- allargs.append('**' + self.varkw)
- allshortargs.append('**' + self.varkw)
- self.signature = ', '.join(allargs)
- self.shortsignature = ', '.join(allshortargs)
- self.dict = func.__dict__.copy()
- # func=None happens when decorating a caller
- if name:
- self.name = name
- if signature is not None:
- self.signature = signature
- if defaults:
- self.defaults = defaults
- if doc:
- self.doc = doc
- if module:
- self.module = module
- if funcdict:
- self.dict = funcdict
- # check existence required attributes
- assert hasattr(self, 'name')
- if not hasattr(self, 'signature'):
- 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
- """
- func.__name__ = self.name
- func.__doc__ = getattr(self, 'doc', None)
- func.__dict__ = getattr(self, 'dict', {})
- func.__defaults__ = self.defaults
- func.__kwdefaults__ = self.kwonlydefaults or None
- func.__annotations__ = getattr(self, 'annotations', None)
- try:
- frame = sys._getframe(3)
- except AttributeError: # for IronPython and similar implementations
- callermodule = '?'
- else:
- callermodule = frame.f_globals.get('__name__', '?')
- func.__module__ = getattr(self, 'module', callermodule)
- 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
- """
- src = src_templ % vars(self) # expand name and signature
- evaldict = evaldict or {}
- mo = DEF.search(src)
- if mo is None:
- raise SyntaxError('not a valid function template\n%s' % src)
- name = mo.group(1) # extract the function name
- names = set([name] + [arg.strip(' *') for arg in
- self.shortsignature.split(',')])
- for n in names:
- if n in ('_func_', '_call_'):
- raise NameError('%s is overridden in\n%s' % (n, src))
-
- if not src.endswith('\n'): # add a newline for old Pythons
- src += '\n'
-
- # Ensure each generated function has a unique filename for profilers
- # (such as cProfile) that depend on the tuple of (<filename>,
- # <definition line>, <function name>) being unique.
- filename = '<decorator-gen-%d>' % next(self._compile_count)
- try:
- code = compile(src, filename, 'single')
- exec(code, evaldict)
- except Exception:
- print('Error in generated code:', file=sys.stderr)
- print(src, file=sys.stderr)
- raise
- func = evaldict[name]
- if addsource:
- attrs['__source__'] = src
- self.update(func, **attrs)
- return func
-
- @classmethod
- def create(cls, obj, body, evaldict, defaults=None,
- doc=None, module=None, addsource=True, **attrs):
- """
- Create a function from the strings name, signature and body.
- evaldict is the evaluation dictionary. If addsource is true an
- attribute __source__ is added to the result. The attributes attrs
- are added, if any.
- """
- if isinstance(obj, str): # "name(signature)"
- name, rest = obj.strip().split('(', 1)
- signature = rest[:-1] # strip a right parens
- func = None
- else: # a function
- name = None
- signature = None
- func = obj
- self = cls(func, name, signature, defaults, doc, module)
- ibody = '\n'.join(' ' + line for line in body.splitlines())
- caller = evaldict.get('_call_') # when called from `decorate`
- if caller and iscoroutinefunction(caller):
- body = ('async def %(name)s(%(signature)s):\n' + ibody).replace(
- 'return', 'return await')
- else:
- body = 'def %(name)s(%(signature)s):\n' + ibody
- return self.make(body, evaldict, addsource, **attrs)
-
-
-def fix(args, kwargs, sig):
- """
- Fix args and kwargs to be consistent with the signature
- """
- 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 isinstance(func, functools.partial):
- func = functools.update_wrapper(func, func.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:
- 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.__name__ = func.__name__
- except AttributeError: # happens with old versions of numpy.vectorize
- func.__name__ == 'noname'
- try:
- fun.__dict__.update(func.__dict__)
- except AttributeError:
- pass
- return fun
-
-
-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, (), kwsyntax)
- # else return a decorator function
- 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:
- 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 ####################### #
-
-
-class ContextManager(_GeneratorContextManager):
- 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)
-
-
-_contextmanager = decorator(ContextManager)
-
-
-def contextmanager(func):
- # Enable Pylint config: contextmanager-decorators=decorator.contextmanager
- return _contextmanager(func)
-
-
-# ############################ dispatch_on ############################ #
-
-def append(a, vancestors):
- """
- Append ``a`` to the list of the virtual ancestors, unless it is already
- included.
- """
- add = True
- for j, va in enumerate(vancestors):
- if issubclass(va, a):
- add = False
- break
- if issubclass(a, va):
- vancestors[j] = a
- add = False
- if add:
- vancestors.append(a)
-
-
-# inspired from simplegeneric by P.J. Eby and functools.singledispatch
-def dispatch_on(*dispatch_args):
- """
- Factory of decorators turning a function into a generic function
- dispatching on the given arguments.
- """
- assert dispatch_args, 'No dispatch args passed'
- dispatch_str = '(%s,)' % ', '.join(dispatch_args)
-
- def check(arguments, wrong=operator.ne, msg=''):
- """Make sure one passes the expected number of arguments"""
- if wrong(len(arguments), len(dispatch_args)):
- raise TypeError('Expected %d arguments, got %d%s' %
- (len(dispatch_args), len(arguments), msg))
-
- def gen_func_dec(func):
- """Decorator turning a function into a generic function"""
-
- # first check the dispatch arguments
- argset = set(getfullargspec(func).args)
- if not set(dispatch_args) <= argset:
- raise NameError('Unknown dispatch arguments %s' % dispatch_str)
-
- typemap = {}
-
- def vancestors(*types):
- """
- Get a list of sets of virtual ancestors for the given types
- """
- check(types)
- ras = [[] for _ in range(len(dispatch_args))]
- for types_ in typemap:
- for t, type_, ra in zip(types, types_, ras):
- if issubclass(t, type_) and type_ not in t.mro():
- append(type_, ra)
- return [set(ra) for ra in ras]
-
- def ancestors(*types):
- """
- Get a list of virtual MROs, one for each type
- """
- check(types)
- lists = []
- for t, vas in zip(types, vancestors(*types)):
- n_vas = len(vas)
- if n_vas > 1:
- raise RuntimeError(
- 'Ambiguous dispatch for %s: %s' % (t, vas))
- elif n_vas == 1:
- va, = vas
- mro = type('t', (t, va), {}).mro()[1:]
- else:
- mro = t.mro()
- lists.append(mro[:-1]) # discard t and object
- return lists
-
- def register(*types):
- """
- Decorator to register an implementation for the given types
- """
- check(types)
-
- def dec(f):
- check(getfullargspec(f).args, operator.lt, ' in ' + f.__name__)
- typemap[types] = f
- return f
- return dec
-
- def dispatch_info(*types):
- """
- An utility to introspect the dispatch algorithm
- """
- check(types)
- lst = []
- for ancs in itertools.product(*ancestors(*types)):
- lst.append(tuple(a.__name__ for a in ancs))
- return lst
-
- def _dispatch(dispatch_args, *args, **kw):
- types = tuple(type(arg) for arg in dispatch_args)
- try: # fast path
- f = typemap[types]
- except KeyError:
- pass
- else:
- return f(*args, **kw)
- combinations = itertools.product(*ancestors(*types))
- next(combinations) # the first one has been already tried
- for types_ in combinations:
- f = typemap.get(types_)
- if f is not None:
- return f(*args, **kw)
-
- # else call the default implementation
- return func(*args, **kw)
-
- return FunctionMaker.create(
- func, 'return _f_(%s, %%(shortsignature)s)' % dispatch_str,
- dict(_f_=_dispatch), register=register, default=func,
- typemap=typemap, vancestors=vancestors, ancestors=ancestors,
- dispatch_info=dispatch_info, __wrapped__=func)
-
- gen_func_dec.__name__ = 'dispatch_on' + dispatch_str
- return gen_func_dec
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/decorator-5.2.1/tests/test.py
new/decorator-5.3.0/tests/test.py
--- old/decorator-5.2.1/tests/test.py 2025-02-22 10:11:03.000000000 +0100
+++ new/decorator-5.3.0/tests/test.py 2026-05-17 08:21:49.000000000 +0200
@@ -11,6 +11,10 @@
from . import documentation as doc # good with pytest
except ImportError:
import documentation as doc # good with `python src/tests/test.py`
+from typing import TYPE_CHECKING # introduced in 3.5
+if TYPE_CHECKING: # only inside mypy
+ from datetime import date
+PYVER = sys.version_info[:2]
@contextmanager
@@ -525,5 +529,12 @@
self.assertEqual(out, '<before>xy<after>')
+if PYVER >= (3, 14):
+ # testing forward references in python 3.14+
+ @decorator
+ def get_dob() -> date:
+ pass
+
+
if __name__ == '__main__':
unittest.main()