patches.py
160 lines
| 6.2 KiB
| text/x-python
|
PythonLexer
r5088 | # Copyright (C) 2016-2023 RhodeCode GmbH | |||
r1 | # | |||
# This program is free software: you can redistribute it and/or modify | ||||
# it under the terms of the GNU Affero General Public License, version 3 | ||||
# (only), as published by the Free Software Foundation. | ||||
# | ||||
# This program is distributed in the hope that it will be useful, | ||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
# GNU General Public License for more details. | ||||
# | ||||
# You should have received a copy of the GNU Affero General Public License | ||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
# | ||||
# This program is dual-licensed. If you wish to learn more about the | ||||
# RhodeCode Enterprise Edition, including its added features, Support services, | ||||
# and proprietary license terms, please see https://rhodecode.com/licenses/ | ||||
""" | ||||
Compatibility patches. | ||||
Please keep the following principles in mind: | ||||
* Keep imports local, so that importing this module does not cause too many | ||||
side effects by itself. | ||||
* Try to make patches idempotent, calling them multiple times should not do | ||||
harm. If that is not possible, ensure that the second call explodes. | ||||
""" | ||||
r5102 | def inspect_formatargspec(): | |||
import inspect | ||||
from inspect import formatannotation | ||||
def backport_inspect_formatargspec( | ||||
args, varargs=None, varkw=None, defaults=None, | ||||
kwonlyargs=(), kwonlydefaults={}, annotations={}, | ||||
formatarg=str, | ||||
formatvarargs=lambda name: '*' + name, | ||||
formatvarkw=lambda name: '**' + name, | ||||
formatvalue=lambda value: '=' + repr(value), | ||||
formatreturns=lambda text: ' -> ' + text, | ||||
formatannotation=formatannotation): | ||||
"""Copy formatargspec from python 3.7 standard library. | ||||
Python 3 has deprecated formatargspec and requested that Signature | ||||
be used instead, however this requires a full reimplementation | ||||
of formatargspec() in terms of creating Parameter objects and such. | ||||
Instead of introducing all the object-creation overhead and having | ||||
to reinvent from scratch, just copy their compatibility routine. | ||||
Utimately we would need to rewrite our "decorator" routine completely | ||||
which is not really worth it right now, until all Python 2.x support | ||||
is dropped. | ||||
""" | ||||
def formatargandannotation(arg): | ||||
result = formatarg(arg) | ||||
if arg in annotations: | ||||
result += ': ' + formatannotation(annotations[arg]) | ||||
return result | ||||
specs = [] | ||||
if defaults: | ||||
firstdefault = len(args) - len(defaults) | ||||
for i, arg in enumerate(args): | ||||
spec = formatargandannotation(arg) | ||||
if defaults and i >= firstdefault: | ||||
spec = spec + formatvalue(defaults[i - firstdefault]) | ||||
specs.append(spec) | ||||
if varargs is not None: | ||||
specs.append(formatvarargs(formatargandannotation(varargs))) | ||||
else: | ||||
if kwonlyargs: | ||||
specs.append('*') | ||||
if kwonlyargs: | ||||
for kwonlyarg in kwonlyargs: | ||||
spec = formatargandannotation(kwonlyarg) | ||||
if kwonlydefaults and kwonlyarg in kwonlydefaults: | ||||
spec += formatvalue(kwonlydefaults[kwonlyarg]) | ||||
specs.append(spec) | ||||
if varkw is not None: | ||||
specs.append(formatvarkw(formatargandannotation(varkw))) | ||||
result = '(' + ', '.join(specs) + ')' | ||||
if 'return' in annotations: | ||||
result += formatreturns(formatannotation(annotations['return'])) | ||||
return result | ||||
# NOTE: inject for python3.11 | ||||
inspect.formatargspec = backport_inspect_formatargspec | ||||
return inspect | ||||
r1 | def inspect_getargspec(): | |||
""" | ||||
r2351 | Pyramid rely on inspect.getargspec to lookup the signature of | |||
r1 | view functions. This is not compatible with cython, therefore we replace | |||
getargspec with a custom version. | ||||
Code is inspired by the inspect module from Python-3.4 | ||||
""" | ||||
import inspect | ||||
def _isCython(func): | ||||
""" | ||||
Private helper that checks if a function is a cython function. | ||||
""" | ||||
return func.__class__.__name__ == 'cython_function_or_method' | ||||
def unwrap(func): | ||||
""" | ||||
Get the object wrapped by *func*. | ||||
Follows the chain of :attr:`__wrapped__` attributes returning the last | ||||
object in the chain. | ||||
*stop* is an optional callback accepting an object in the wrapper chain | ||||
as its sole argument that allows the unwrapping to be terminated early | ||||
if the callback returns a true value. If the callback never returns a | ||||
true value, the last object in the chain is returned as usual. For | ||||
example, :func:`signature` uses this to stop unwrapping if any object | ||||
in the chain has a ``__signature__`` attribute defined. | ||||
:exc:`ValueError` is raised if a cycle is encountered. | ||||
""" | ||||
f = func # remember the original func for error reporting | ||||
memo = {id(f)} # Memoise by id to tolerate non-hashable objects | ||||
while hasattr(func, '__wrapped__'): | ||||
func = func.__wrapped__ | ||||
id_func = id(func) | ||||
if id_func in memo: | ||||
r5095 | raise ValueError(f'wrapper loop when unwrapping {f!r}') | |||
r1 | memo.add(id_func) | |||
return func | ||||
def custom_getargspec(func): | ||||
""" | ||||
Get the names and default values of a function's arguments. | ||||
A tuple of four things is returned: (args, varargs, varkw, defaults). | ||||
'args' is a list of the argument names (it may contain nested lists). | ||||
'varargs' and 'varkw' are the names of the * and ** arguments or None. | ||||
'defaults' is an n-tuple of the default values of the last n arguments. | ||||
""" | ||||
func = unwrap(func) | ||||
if inspect.ismethod(func): | ||||
func = func.im_func | ||||
if not inspect.isfunction(func): | ||||
if not _isCython(func): | ||||
raise TypeError('{!r} is not a Python or Cython function' | ||||
.format(func)) | ||||
args, varargs, varkw = inspect.getargs(func.func_code) | ||||
return inspect.ArgSpec(args, varargs, varkw, func.func_defaults) | ||||
r5102 | # NOTE: inject for python3.11 | |||
inspect.getargspec = inspect.getfullargspec | ||||
r4184 | ||||
return inspect | ||||