__author__ = ('Ka-Ping Yee <ping@lfw.org>',
'Yury Selivanov <yselivanov@sprymix.com>')
__all__ = [
"AGEN_CLOSED",
"AGEN_CREATED",
"AGEN_RUNNING",
"AGEN_SUSPENDED",
"ArgInfo",
"Arguments",
"Attribute",
"BlockFinder",
"BoundArguments",
"BufferFlags",
"CORO_CLOSED",
"CORO_CREATED",
"CORO_RUNNING",
"CORO_SUSPENDED",
"CO_ASYNC_GENERATOR",
"CO_COROUTINE",
"CO_GENERATOR",
"CO_ITERABLE_COROUTINE",
"CO_NESTED",
"CO_NEWLOCALS",
"CO_NOFREE",
"CO_OPTIMIZED",
"CO_VARARGS",
"CO_VARKEYWORDS",
"CO_HAS_DOCSTRING",
"CO_METHOD",
"ClassFoundException",
"ClosureVars",
"EndOfBlock",
"FrameInfo",
"FullArgSpec",
"GEN_CLOSED",
"GEN_CREATED",
"GEN_RUNNING",
"GEN_SUSPENDED",
"Parameter",
"Signature",
"TPFLAGS_IS_ABSTRACT",
"Traceback",
"classify_class_attrs",
"cleandoc",
"currentframe",
"findsource",
"formatannotation",
"formatannotationrelativeto",
"formatargvalues",
"get_annotations",
"getabsfile",
"getargs",
"getargvalues",
"getasyncgenlocals",
"getasyncgenstate",
"getattr_static",
"getblock",
"getcallargs",
"getclasstree",
"getclosurevars",
"getcomments",
"getcoroutinelocals",
"getcoroutinestate",
"getdoc",
"getfile",
"getframeinfo",
"getfullargspec",
"getgeneratorlocals",
"getgeneratorstate",
"getinnerframes",
"getlineno",
"getmembers",
"getmembers_static",
"getmodule",
"getmodulename",
"getmro",
"getouterframes",
"getsource",
"getsourcefile",
"getsourcelines",
"indentsize",
"isabstract",
"isasyncgen",
"isasyncgenfunction",
"isawaitable",
"isbuiltin",
"isclass",
"iscode",
"iscoroutine",
"iscoroutinefunction",
"isdatadescriptor",
"isframe",
"isfunction",
"isgenerator",
"isgeneratorfunction",
"isgetsetdescriptor",
"ismemberdescriptor",
"ismethod",
"ismethoddescriptor",
"ismethodwrapper",
"ismodule",
"ispackage",
"isroutine",
"istraceback",
"markcoroutinefunction",
"signature",
"stack",
"trace",
"unwrap",
"walktree",
]
import abc
from annotationlib import Format, ForwardRef
from annotationlib import get_annotations import ast
import dis
import collections.abc
import enum
import importlib.machinery
import itertools
import linecache
import os
import re
import sys
import tokenize
import token
import types
import functools
import builtins
from keyword import iskeyword
from operator import attrgetter
from collections import namedtuple, OrderedDict
from weakref import ref as make_weakref
mod_dict = globals()
for k, v in dis.COMPILER_FLAG_NAMES.items():
mod_dict["CO_" + v] = k
del k, v, mod_dict
TPFLAGS_IS_ABSTRACT = 1 << 20
def ismodule(object):
return isinstance(object, types.ModuleType)
def isclass(object):
return isinstance(object, type)
def ismethod(object):
return isinstance(object, types.MethodType)
def ispackage(object):
return ismodule(object) and hasattr(object, "__path__")
def ismethoddescriptor(object):
if isclass(object) or ismethod(object) or isfunction(object):
return False
tp = type(object)
return (hasattr(tp, "__get__")
and not hasattr(tp, "__set__")
and not hasattr(tp, "__delete__"))
def isdatadescriptor(object):
if isclass(object) or ismethod(object) or isfunction(object):
return False
tp = type(object)
return hasattr(tp, "__set__") or hasattr(tp, "__delete__")
if hasattr(types, 'MemberDescriptorType'):
def ismemberdescriptor(object):
return isinstance(object, types.MemberDescriptorType)
else:
def ismemberdescriptor(object):
return False
if hasattr(types, 'GetSetDescriptorType'):
def isgetsetdescriptor(object):
return isinstance(object, types.GetSetDescriptorType)
else:
def isgetsetdescriptor(object):
return False
def isfunction(object):
return isinstance(object, types.FunctionType)
def _has_code_flag(f, flag):
f = functools._unwrap_partialmethod(f)
while ismethod(f):
f = f.__func__
f = functools._unwrap_partial(f)
if not (isfunction(f) or _signature_is_functionlike(f)):
return False
return bool(f.__code__.co_flags & flag)
def isgeneratorfunction(obj):
return _has_code_flag(obj, CO_GENERATOR)
_is_coroutine_mark = object()
def _has_coroutine_mark(f):
while ismethod(f):
f = f.__func__
f = functools._unwrap_partial(f)
return getattr(f, "_is_coroutine_marker", None) is _is_coroutine_mark
def markcoroutinefunction(func):
if hasattr(func, '__func__'):
func = func.__func__
func._is_coroutine_marker = _is_coroutine_mark
return func
def iscoroutinefunction(obj):
return _has_code_flag(obj, CO_COROUTINE) or _has_coroutine_mark(obj)
def isasyncgenfunction(obj):
return _has_code_flag(obj, CO_ASYNC_GENERATOR)
def isasyncgen(object):
return isinstance(object, types.AsyncGeneratorType)
def isgenerator(object):
return isinstance(object, types.GeneratorType)
def iscoroutine(object):
return isinstance(object, types.CoroutineType)
def isawaitable(object):
return (isinstance(object, types.CoroutineType) or
isinstance(object, types.GeneratorType) and
bool(object.gi_code.co_flags & CO_ITERABLE_COROUTINE) or
isinstance(object, collections.abc.Awaitable))
def istraceback(object):
return isinstance(object, types.TracebackType)
def isframe(object):
return isinstance(object, types.FrameType)
def iscode(object):
return isinstance(object, types.CodeType)
def isbuiltin(object):
return isinstance(object, types.BuiltinFunctionType)
def ismethodwrapper(object):
return isinstance(object, types.MethodWrapperType)
def isroutine(object):
return (isbuiltin(object)
or isfunction(object)
or ismethod(object)
or ismethoddescriptor(object)
or ismethodwrapper(object)
or isinstance(object, functools._singledispatchmethod_get))
def isabstract(object):
if not isinstance(object, type):
return False
if object.__flags__ & TPFLAGS_IS_ABSTRACT:
return True
if not issubclass(type(object), abc.ABCMeta):
return False
if hasattr(object, '__abstractmethods__'):
return False
for name, value in object.__dict__.items():
if getattr(value, "__isabstractmethod__", False):
return True
for base in object.__bases__:
for name in getattr(base, "__abstractmethods__", ()):
value = getattr(object, name, None)
if getattr(value, "__isabstractmethod__", False):
return True
return False
def _getmembers(object, predicate, getter):
results = []
processed = set()
names = dir(object)
if isclass(object):
mro = getmro(object)
try:
for base in object.__bases__:
for k, v in base.__dict__.items():
if isinstance(v, types.DynamicClassAttribute):
names.append(k)
except AttributeError:
pass
else:
mro = ()
for key in names:
try:
value = getter(object, key)
if key in processed:
raise AttributeError
except AttributeError:
for base in mro:
if key in base.__dict__:
value = base.__dict__[key]
break
else:
continue
if not predicate or predicate(value):
results.append((key, value))
processed.add(key)
results.sort(key=lambda pair: pair[0])
return results
def getmembers(object, predicate=None):
return _getmembers(object, predicate, getattr)
def getmembers_static(object, predicate=None):
return _getmembers(object, predicate, getattr_static)
Attribute = namedtuple('Attribute', 'name kind defining_class object')
def classify_class_attrs(cls):
mro = getmro(cls)
metamro = getmro(type(cls)) metamro = tuple(cls for cls in metamro if cls not in (type, object))
class_bases = (cls,) + mro
all_bases = class_bases + metamro
names = dir(cls)
for base in mro:
for k, v in base.__dict__.items():
if isinstance(v, types.DynamicClassAttribute) and v.fget is not None:
names.append(k)
result = []
processed = set()
for name in names:
homecls = None
get_obj = None
dict_obj = None
if name not in processed:
try:
if name == '__dict__':
raise Exception("__dict__ is special, don't want the proxy")
get_obj = getattr(cls, name)
except Exception:
pass
else:
homecls = getattr(get_obj, "__objclass__", homecls)
if homecls not in class_bases:
homecls = None
last_cls = None
for srch_cls in class_bases:
srch_obj = getattr(srch_cls, name, None)
if srch_obj is get_obj:
last_cls = srch_cls
for srch_cls in metamro:
try:
srch_obj = srch_cls.__getattr__(cls, name)
except AttributeError:
continue
if srch_obj is get_obj:
last_cls = srch_cls
if last_cls is not None:
homecls = last_cls
for base in all_bases:
if name in base.__dict__:
dict_obj = base.__dict__[name]
if homecls not in metamro:
homecls = base
break
if homecls is None:
continue
obj = get_obj if get_obj is not None else dict_obj
if isinstance(dict_obj, (staticmethod, types.BuiltinMethodType)):
kind = "static method"
obj = dict_obj
elif isinstance(dict_obj, (classmethod, types.ClassMethodDescriptorType)):
kind = "class method"
obj = dict_obj
elif isinstance(dict_obj, property):
kind = "property"
obj = dict_obj
elif isroutine(obj):
kind = "method"
else:
kind = "data"
result.append(Attribute(name, kind, homecls, obj))
processed.add(name)
return result
def getmro(cls):
"Return tuple of base classes (including cls) in method resolution order."
return cls.__mro__
def unwrap(func, *, stop=None):
f = func memo = {id(f): f}
recursion_limit = sys.getrecursionlimit()
while not isinstance(func, type) and hasattr(func, '__wrapped__'):
if stop is not None and stop(func):
break
func = func.__wrapped__
id_func = id(func)
if (id_func in memo) or (len(memo) >= recursion_limit):
raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
memo[id_func] = func
return func
def indentsize(line):
expline = line.expandtabs()
return len(expline) - len(expline.lstrip())
def _findclass(func):
cls = sys.modules.get(func.__module__)
if cls is None:
return None
for name in func.__qualname__.split('.')[:-1]:
cls = getattr(cls, name)
if not isclass(cls):
return None
return cls
def _finddoc(obj):
if isclass(obj):
for base in obj.__mro__:
if base is not object:
try:
doc = base.__doc__
except AttributeError:
continue
if doc is not None:
return doc
return None
if ismethod(obj):
name = obj.__func__.__name__
self = obj.__self__
if (isclass(self) and
getattr(getattr(self, name, None), '__func__') is obj.__func__):
cls = self
else:
cls = self.__class__
elif isfunction(obj):
name = obj.__name__
cls = _findclass(obj)
if cls is None or getattr(cls, name) is not obj:
return None
elif isbuiltin(obj):
name = obj.__name__
self = obj.__self__
if (isclass(self) and
self.__qualname__ + '.' + name == obj.__qualname__):
cls = self
else:
cls = self.__class__
elif isinstance(obj, property):
name = obj.__name__
cls = _findclass(obj.fget)
if cls is None or getattr(cls, name) is not obj:
return None
elif ismethoddescriptor(obj) or isdatadescriptor(obj):
name = obj.__name__
cls = obj.__objclass__
if getattr(cls, name) is not obj:
return None
if ismemberdescriptor(obj):
slots = getattr(cls, '__slots__', None)
if isinstance(slots, dict) and name in slots:
return slots[name]
else:
return None
for base in cls.__mro__:
try:
doc = getattr(base, name).__doc__
except AttributeError:
continue
if doc is not None:
return doc
return None
def getdoc(object):
try:
doc = object.__doc__
except AttributeError:
return None
if doc is None:
try:
doc = _finddoc(object)
except (AttributeError, TypeError):
return None
if not isinstance(doc, str):
return None
return cleandoc(doc)
def cleandoc(doc):
lines = doc.expandtabs().split('\n')
margin = sys.maxsize
for line in lines[1:]:
content = len(line.lstrip(' '))
if content:
indent = len(line) - content
margin = min(margin, indent)
if lines:
lines[0] = lines[0].lstrip(' ')
if margin < sys.maxsize:
for i in range(1, len(lines)):
lines[i] = lines[i][margin:]
while lines and not lines[-1]:
lines.pop()
while lines and not lines[0]:
lines.pop(0)
return '\n'.join(lines)
def getfile(object):
if ismodule(object):
if getattr(object, '__file__', None):
return object.__file__
raise TypeError('{!r} is a built-in module'.format(object))
if isclass(object):
if hasattr(object, '__module__'):
module = sys.modules.get(object.__module__)
if getattr(module, '__file__', None):
return module.__file__
if object.__module__ == '__main__':
raise OSError('source code not available')
raise TypeError('{!r} is a built-in class'.format(object))
if ismethod(object):
object = object.__func__
if isfunction(object):
object = object.__code__
if istraceback(object):
object = object.tb_frame
if isframe(object):
object = object.f_code
if iscode(object):
return object.co_filename
raise TypeError('module, class, method, function, traceback, frame, or '
'code object was expected, got {}'.format(
type(object).__name__))
def getmodulename(path):
fname = os.path.basename(path)
suffixes = [(-len(suffix), suffix)
for suffix in importlib.machinery.all_suffixes()]
suffixes.sort() for neglen, suffix in suffixes:
if fname.endswith(suffix):
return fname[:neglen]
return None
def getsourcefile(object):
filename = getfile(object)
all_bytecode_suffixes = importlib.machinery.BYTECODE_SUFFIXES[:]
if any(filename.endswith(s) for s in all_bytecode_suffixes):
filename = (os.path.splitext(filename)[0] +
importlib.machinery.SOURCE_SUFFIXES[0])
elif any(filename.endswith(s) for s in
importlib.machinery.EXTENSION_SUFFIXES):
return None
elif filename.endswith(".fwork"):
return None
if filename in linecache.cache:
return filename
if os.path.exists(filename):
return filename
module = getmodule(object, filename)
if getattr(module, '__loader__', None) is not None:
return filename
elif getattr(getattr(module, "__spec__", None), "loader", None) is not None:
return filename
def getabsfile(object, _filename=None):
if _filename is None:
_filename = getsourcefile(object) or getfile(object)
return os.path.normcase(os.path.abspath(_filename))
modulesbyfile = {}
_filesbymodname = {}
def getmodule(object, _filename=None):
if ismodule(object):
return object
if hasattr(object, '__module__'):
return sys.modules.get(object.__module__)
if _filename is not None and _filename in modulesbyfile:
return sys.modules.get(modulesbyfile[_filename])
try:
file = getabsfile(object, _filename)
except (TypeError, FileNotFoundError):
return None
if file in modulesbyfile:
return sys.modules.get(modulesbyfile[file])
for modname, module in sys.modules.copy().items():
if ismodule(module) and hasattr(module, '__file__'):
f = module.__file__
if f == _filesbymodname.get(modname, None):
continue
_filesbymodname[modname] = f
f = getabsfile(module)
modulesbyfile[f] = modulesbyfile[
os.path.realpath(f)] = module.__name__
if file in modulesbyfile:
return sys.modules.get(modulesbyfile[file])
main = sys.modules['__main__']
if not hasattr(object, '__name__'):
return None
if hasattr(main, object.__name__):
mainobject = getattr(main, object.__name__)
if mainobject is object:
return main
builtin = sys.modules['builtins']
if hasattr(builtin, object.__name__):
builtinobject = getattr(builtin, object.__name__)
if builtinobject is object:
return builtin
class ClassFoundException(Exception):
pass
def findsource(object):
file = getsourcefile(object)
if file:
linecache.checkcache(file)
else:
file = getfile(object)
if (not (file.startswith('<') and file.endswith('>'))) or file.endswith('.fwork'):
raise OSError('source code not available')
module = getmodule(object, file)
if module:
lines = linecache.getlines(file, module.__dict__)
if not lines and file.startswith('<') and hasattr(object, "__code__"):
lines = linecache._getlines_from_code(object.__code__)
else:
lines = linecache.getlines(file)
if not lines:
raise OSError('could not get source code')
if ismodule(object):
return lines, 0
if isclass(object):
try:
lnum = vars(object)['__firstlineno__'] - 1
except (TypeError, KeyError):
raise OSError('source code not available')
if lnum >= len(lines):
raise OSError('lineno is out of bounds')
return lines, lnum
if ismethod(object):
object = object.__func__
if isfunction(object):
object = object.__code__
if istraceback(object):
object = object.tb_frame
if isframe(object):
object = object.f_code
if iscode(object):
if not hasattr(object, 'co_firstlineno'):
raise OSError('could not find function definition')
lnum = object.co_firstlineno - 1
if lnum >= len(lines):
raise OSError('lineno is out of bounds')
return lines, lnum
raise OSError('could not find code object')
def getcomments(object):
try:
lines, lnum = findsource(object)
except (OSError, TypeError):
return None
if ismodule(object):
start = 0
if lines and lines[0][:2] == '#!': start = 1
while start < len(lines) and lines[start].strip() in ('', '#'):
start = start + 1
if start < len(lines) and lines[start][:1] == '#':
comments = []
end = start
while end < len(lines) and lines[end][:1] == '#':
comments.append(lines[end].expandtabs())
end = end + 1
return ''.join(comments)
elif lnum > 0:
indent = indentsize(lines[lnum])
end = lnum - 1
if end >= 0 and lines[end].lstrip()[:1] == '#' and \
indentsize(lines[end]) == indent:
comments = [lines[end].expandtabs().lstrip()]
if end > 0:
end = end - 1
comment = lines[end].expandtabs().lstrip()
while comment[:1] == '#' and indentsize(lines[end]) == indent:
comments[:0] = [comment]
end = end - 1
if end < 0: break
comment = lines[end].expandtabs().lstrip()
while comments and comments[0].strip() == '#':
comments[:1] = []
while comments and comments[-1].strip() == '#':
comments[-1:] = []
return ''.join(comments)
class EndOfBlock(Exception): pass
class BlockFinder:
def __init__(self):
self.indent = 0
self.singleline = False
self.started = False
self.passline = False
self.indecorator = False
self.last = 1
self.body_col0 = None
def tokeneater(self, type, token, srowcol, erowcol, line):
if not self.started and not self.indecorator:
if type in (tokenize.INDENT, tokenize.COMMENT, tokenize.NL):
pass
elif token == "async":
pass
elif token == "@":
self.indecorator = True
else:
self.singleline = token not in ("def", "class")
self.started = True
self.passline = True elif type == tokenize.NEWLINE:
self.passline = False self.last = srowcol[0]
if self.singleline:
raise EndOfBlock
if self.indecorator:
self.indecorator = False
elif self.passline:
pass
elif type == tokenize.INDENT:
if self.body_col0 is None and self.started:
self.body_col0 = erowcol[1]
self.indent = self.indent + 1
self.passline = True
elif type == tokenize.DEDENT:
self.indent = self.indent - 1
if self.indent <= 0:
raise EndOfBlock
elif type == tokenize.COMMENT:
if self.body_col0 is not None and srowcol[1] >= self.body_col0:
self.last = srowcol[0]
elif self.indent == 0 and type not in (tokenize.COMMENT, tokenize.NL):
raise EndOfBlock
def getblock(lines):
blockfinder = BlockFinder()
try:
tokens = tokenize.generate_tokens(iter(lines).__next__)
for _token in tokens:
blockfinder.tokeneater(*_token)
except (EndOfBlock, IndentationError):
pass
except SyntaxError as e:
if "unmatched" not in e.msg:
raise e from None
_, *_token_info = _token
try:
blockfinder.tokeneater(tokenize.NEWLINE, *_token_info)
except (EndOfBlock, IndentationError):
pass
return lines[:blockfinder.last]
def getsourcelines(object):
object = unwrap(object)
lines, lnum = findsource(object)
if istraceback(object):
object = object.tb_frame
if (ismodule(object) or
(isframe(object) and object.f_code.co_name == "<module>")):
return lines, 0
else:
return getblock(lines[lnum:]), lnum + 1
def getsource(object):
lines, lnum = getsourcelines(object)
return ''.join(lines)
def walktree(classes, children, parent):
results = []
classes.sort(key=attrgetter('__module__', '__name__'))
for c in classes:
results.append((c, c.__bases__))
if c in children:
results.append(walktree(children[c], children, c))
return results
def getclasstree(classes, unique=False):
children = {}
roots = []
for c in classes:
if c.__bases__:
for parent in c.__bases__:
if parent not in children:
children[parent] = []
if c not in children[parent]:
children[parent].append(c)
if unique and parent in classes: break
elif c not in roots:
roots.append(c)
for parent in children:
if parent not in classes:
roots.append(parent)
return walktree(roots, children, None)
Arguments = namedtuple('Arguments', 'args, varargs, varkw')
def getargs(co):
if not iscode(co):
raise TypeError('{!r} is not a code object'.format(co))
names = co.co_varnames
nargs = co.co_argcount
nkwargs = co.co_kwonlyargcount
args = list(names[:nargs])
kwonlyargs = list(names[nargs:nargs+nkwargs])
nargs += nkwargs
varargs = None
if co.co_flags & CO_VARARGS:
varargs = co.co_varnames[nargs]
nargs = nargs + 1
varkw = None
if co.co_flags & CO_VARKEYWORDS:
varkw = co.co_varnames[nargs]
return Arguments(args + kwonlyargs, varargs, varkw)
FullArgSpec = namedtuple('FullArgSpec',
'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations')
def getfullargspec(func):
try:
sig = _signature_from_callable(func,
follow_wrapper_chains=False,
skip_bound_arg=False,
sigcls=Signature,
eval_str=False)
except Exception as ex:
raise TypeError('unsupported callable') from ex
args = []
varargs = None
varkw = None
posonlyargs = []
kwonlyargs = []
annotations = {}
defaults = ()
kwdefaults = {}
if sig.return_annotation is not sig.empty:
annotations['return'] = sig.return_annotation
for param in sig.parameters.values():
kind = param.kind
name = param.name
if kind is _POSITIONAL_ONLY:
posonlyargs.append(name)
if param.default is not param.empty:
defaults += (param.default,)
elif kind is _POSITIONAL_OR_KEYWORD:
args.append(name)
if param.default is not param.empty:
defaults += (param.default,)
elif kind is _VAR_POSITIONAL:
varargs = name
elif kind is _KEYWORD_ONLY:
kwonlyargs.append(name)
if param.default is not param.empty:
kwdefaults[name] = param.default
elif kind is _VAR_KEYWORD:
varkw = name
if param.annotation is not param.empty:
annotations[name] = param.annotation
if not kwdefaults:
kwdefaults = None
if not defaults:
defaults = None
return FullArgSpec(posonlyargs + args, varargs, varkw, defaults,
kwonlyargs, kwdefaults, annotations)
ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals')
def getargvalues(frame):
args, varargs, varkw = getargs(frame.f_code)
return ArgInfo(args, varargs, varkw, frame.f_locals)
def formatannotation(annotation, base_module=None, *, quote_annotation_strings=True):
if not quote_annotation_strings and isinstance(annotation, str):
return annotation
if getattr(annotation, '__module__', None) == 'typing':
def repl(match):
text = match.group()
return text.removeprefix('typing.')
return re.sub(r'[\w\.]+', repl, repr(annotation))
if isinstance(annotation, types.GenericAlias):
return str(annotation)
if isinstance(annotation, type):
if annotation.__module__ in ('builtins', base_module):
return annotation.__qualname__
return annotation.__module__+'.'+annotation.__qualname__
if isinstance(annotation, ForwardRef):
return annotation.__forward_arg__
return repr(annotation)
def formatannotationrelativeto(object):
module = getattr(object, '__module__', None)
def _formatannotation(annotation):
return formatannotation(annotation, module)
return _formatannotation
def formatargvalues(args, varargs, varkw, locals,
formatarg=str,
formatvarargs=lambda name: '*' + name,
formatvarkw=lambda name: '**' + name,
formatvalue=lambda value: '=' + repr(value)):
def convert(name, locals=locals,
formatarg=formatarg, formatvalue=formatvalue):
return formatarg(name) + formatvalue(locals[name])
specs = []
for i in range(len(args)):
specs.append(convert(args[i]))
if varargs:
specs.append(formatvarargs(varargs) + formatvalue(locals[varargs]))
if varkw:
specs.append(formatvarkw(varkw) + formatvalue(locals[varkw]))
return '(' + ', '.join(specs) + ')'
def _missing_arguments(f_name, argnames, pos, values):
names = [repr(name) for name in argnames if name not in values]
missing = len(names)
if missing == 1:
s = names[0]
elif missing == 2:
s = "{} and {}".format(*names)
else:
tail = ", {} and {}".format(*names[-2:])
del names[-2:]
s = ", ".join(names) + tail
raise TypeError("%s() missing %i required %s argument%s: %s" %
(f_name, missing,
"positional" if pos else "keyword-only",
"" if missing == 1 else "s", s))
def _too_many(f_name, args, kwonly, varargs, defcount, given, values):
atleast = len(args) - defcount
kwonly_given = len([arg for arg in kwonly if arg in values])
if varargs:
plural = atleast != 1
sig = "at least %d" % (atleast,)
elif defcount:
plural = True
sig = "from %d to %d" % (atleast, len(args))
else:
plural = len(args) != 1
sig = str(len(args))
kwonly_sig = ""
if kwonly_given:
msg = " positional argument%s (and %d keyword-only argument%s)"
kwonly_sig = (msg % ("s" if given != 1 else "", kwonly_given,
"s" if kwonly_given != 1 else ""))
raise TypeError("%s() takes %s positional argument%s but %d%s %s given" %
(f_name, sig, "s" if plural else "", given, kwonly_sig,
"was" if given == 1 and not kwonly_given else "were"))
def getcallargs(func, /, *positional, **named):
spec = getfullargspec(func)
args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec
f_name = func.__name__
arg2value = {}
if ismethod(func) and func.__self__ is not None:
positional = (func.__self__,) + positional
num_pos = len(positional)
num_args = len(args)
num_defaults = len(defaults) if defaults else 0
n = min(num_pos, num_args)
for i in range(n):
arg2value[args[i]] = positional[i]
if varargs:
arg2value[varargs] = tuple(positional[n:])
possible_kwargs = set(args + kwonlyargs)
if varkw:
arg2value[varkw] = {}
for kw, value in named.items():
if kw not in possible_kwargs:
if not varkw:
raise TypeError("%s() got an unexpected keyword argument %r" %
(f_name, kw))
arg2value[varkw][kw] = value
continue
if kw in arg2value:
raise TypeError("%s() got multiple values for argument %r" %
(f_name, kw))
arg2value[kw] = value
if num_pos > num_args and not varargs:
_too_many(f_name, args, kwonlyargs, varargs, num_defaults,
num_pos, arg2value)
if num_pos < num_args:
req = args[:num_args - num_defaults]
for arg in req:
if arg not in arg2value:
_missing_arguments(f_name, req, True, arg2value)
for i, arg in enumerate(args[num_args - num_defaults:]):
if arg not in arg2value:
arg2value[arg] = defaults[i]
missing = 0
for kwarg in kwonlyargs:
if kwarg not in arg2value:
if kwonlydefaults and kwarg in kwonlydefaults:
arg2value[kwarg] = kwonlydefaults[kwarg]
else:
missing += 1
if missing:
_missing_arguments(f_name, kwonlyargs, False, arg2value)
return arg2value
ClosureVars = namedtuple('ClosureVars', 'nonlocals globals builtins unbound')
def getclosurevars(func):
if ismethod(func):
func = func.__func__
if not isfunction(func):
raise TypeError("{!r} is not a Python function".format(func))
code = func.__code__
if func.__closure__ is None:
nonlocal_vars = {}
else:
nonlocal_vars = {
var : cell.cell_contents
for var, cell in zip(code.co_freevars, func.__closure__)
}
global_ns = func.__globals__
builtin_ns = global_ns.get("__builtins__", builtins.__dict__)
if ismodule(builtin_ns):
builtin_ns = builtin_ns.__dict__
global_vars = {}
builtin_vars = {}
unbound_names = set()
global_names = set()
for instruction in dis.get_instructions(code):
opname = instruction.opname
name = instruction.argval
if opname == "LOAD_ATTR":
unbound_names.add(name)
elif opname == "LOAD_GLOBAL":
global_names.add(name)
for name in global_names:
try:
global_vars[name] = global_ns[name]
except KeyError:
try:
builtin_vars[name] = builtin_ns[name]
except KeyError:
unbound_names.add(name)
return ClosureVars(nonlocal_vars, global_vars,
builtin_vars, unbound_names)
_Traceback = namedtuple('_Traceback', 'filename lineno function code_context index')
class Traceback(_Traceback):
def __new__(cls, filename, lineno, function, code_context, index, *, positions=None):
instance = super().__new__(cls, filename, lineno, function, code_context, index)
instance.positions = positions
return instance
def __repr__(self):
return ('Traceback(filename={!r}, lineno={!r}, function={!r}, '
'code_context={!r}, index={!r}, positions={!r})'.format(
self.filename, self.lineno, self.function, self.code_context,
self.index, self.positions))
def _get_code_position_from_tb(tb):
code, instruction_index = tb.tb_frame.f_code, tb.tb_lasti
return _get_code_position(code, instruction_index)
def _get_code_position(code, instruction_index):
if instruction_index < 0:
return (None, None, None, None)
positions_gen = code.co_positions()
return next(itertools.islice(positions_gen, instruction_index // 2, None))
def getframeinfo(frame, context=1):
if istraceback(frame):
positions = _get_code_position_from_tb(frame)
lineno = frame.tb_lineno
frame = frame.tb_frame
else:
lineno = frame.f_lineno
positions = _get_code_position(frame.f_code, frame.f_lasti)
if positions[0] is None:
frame, *positions = (frame, lineno, *positions[1:])
else:
frame, *positions = (frame, *positions)
lineno = positions[0]
if not isframe(frame):
raise TypeError('{!r} is not a frame or traceback object'.format(frame))
filename = getsourcefile(frame) or getfile(frame)
if context > 0:
start = lineno - 1 - context//2
try:
lines, lnum = findsource(frame)
except OSError:
lines = index = None
else:
start = max(0, min(start, len(lines) - context))
lines = lines[start:start+context]
index = lineno - 1 - start
else:
lines = index = None
return Traceback(filename, lineno, frame.f_code.co_name, lines,
index, positions=dis.Positions(*positions))
def getlineno(frame):
return frame.f_lineno
_FrameInfo = namedtuple('_FrameInfo', ('frame',) + Traceback._fields)
class FrameInfo(_FrameInfo):
def __new__(cls, frame, filename, lineno, function, code_context, index, *, positions=None):
instance = super().__new__(cls, frame, filename, lineno, function, code_context, index)
instance.positions = positions
return instance
def __repr__(self):
return ('FrameInfo(frame={!r}, filename={!r}, lineno={!r}, function={!r}, '
'code_context={!r}, index={!r}, positions={!r})'.format(
self.frame, self.filename, self.lineno, self.function,
self.code_context, self.index, self.positions))
def getouterframes(frame, context=1):
framelist = []
while frame:
traceback_info = getframeinfo(frame, context)
frameinfo = (frame,) + traceback_info
framelist.append(FrameInfo(*frameinfo, positions=traceback_info.positions))
frame = frame.f_back
return framelist
def getinnerframes(tb, context=1):
framelist = []
while tb:
traceback_info = getframeinfo(tb, context)
frameinfo = (tb.tb_frame,) + traceback_info
framelist.append(FrameInfo(*frameinfo, positions=traceback_info.positions))
tb = tb.tb_next
return framelist
def currentframe():
return sys._getframe(1) if hasattr(sys, "_getframe") else None
def stack(context=1):
return getouterframes(sys._getframe(1), context)
def trace(context=1):
exc = sys.exception()
tb = None if exc is None else exc.__traceback__
return getinnerframes(tb, context)
_sentinel = object()
_static_getmro = type.__dict__['__mro__'].__get__
_get_dunder_dict_of_class = type.__dict__["__dict__"].__get__
def _check_instance(obj, attr):
instance_dict = {}
try:
instance_dict = object.__getattribute__(obj, "__dict__")
except AttributeError:
pass
return dict.get(instance_dict, attr, _sentinel)
def _check_class(klass, attr):
for entry in _static_getmro(klass):
if _shadowed_dict(type(entry)) is _sentinel and attr in entry.__dict__:
return entry.__dict__[attr]
return _sentinel
@functools.lru_cache()
def _shadowed_dict_from_weakref_mro_tuple(*weakref_mro):
for weakref_entry in weakref_mro:
entry = weakref_entry()
dunder_dict = _get_dunder_dict_of_class(entry)
if '__dict__' in dunder_dict:
class_dict = dunder_dict['__dict__']
if not (type(class_dict) is types.GetSetDescriptorType and
class_dict.__name__ == "__dict__" and
class_dict.__objclass__ is entry):
return class_dict
return _sentinel
def _shadowed_dict(klass):
return _shadowed_dict_from_weakref_mro_tuple(
*[make_weakref(entry) for entry in _static_getmro(klass)]
)
def getattr_static(obj, attr, default=_sentinel):
instance_result = _sentinel
objtype = type(obj)
if type not in _static_getmro(objtype):
klass = objtype
dict_attr = _shadowed_dict(klass)
if (dict_attr is _sentinel or
type(dict_attr) is types.MemberDescriptorType):
instance_result = _check_instance(obj, attr)
else:
klass = obj
klass_result = _check_class(klass, attr)
if instance_result is not _sentinel and klass_result is not _sentinel:
if _check_class(type(klass_result), "__get__") is not _sentinel and (
_check_class(type(klass_result), "__set__") is not _sentinel
or _check_class(type(klass_result), "__delete__") is not _sentinel
):
return klass_result
if instance_result is not _sentinel:
return instance_result
if klass_result is not _sentinel:
return klass_result
if obj is klass:
for entry in _static_getmro(type(klass)):
if (
_shadowed_dict(type(entry)) is _sentinel
and attr in entry.__dict__
):
return entry.__dict__[attr]
if default is not _sentinel:
return default
raise AttributeError(attr)
GEN_CREATED = 'GEN_CREATED'
GEN_RUNNING = 'GEN_RUNNING'
GEN_SUSPENDED = 'GEN_SUSPENDED'
GEN_CLOSED = 'GEN_CLOSED'
def getgeneratorstate(generator):
if generator.gi_running:
return GEN_RUNNING
if generator.gi_suspended:
return GEN_SUSPENDED
if generator.gi_frame is None:
return GEN_CLOSED
return GEN_CREATED
def getgeneratorlocals(generator):
if not isgenerator(generator):
raise TypeError("{!r} is not a Python generator".format(generator))
frame = getattr(generator, "gi_frame", None)
if frame is not None:
return generator.gi_frame.f_locals
else:
return {}
CORO_CREATED = 'CORO_CREATED'
CORO_RUNNING = 'CORO_RUNNING'
CORO_SUSPENDED = 'CORO_SUSPENDED'
CORO_CLOSED = 'CORO_CLOSED'
def getcoroutinestate(coroutine):
if coroutine.cr_running:
return CORO_RUNNING
if coroutine.cr_suspended:
return CORO_SUSPENDED
if coroutine.cr_frame is None:
return CORO_CLOSED
return CORO_CREATED
def getcoroutinelocals(coroutine):
frame = getattr(coroutine, "cr_frame", None)
if frame is not None:
return frame.f_locals
else:
return {}
AGEN_CREATED = 'AGEN_CREATED'
AGEN_RUNNING = 'AGEN_RUNNING'
AGEN_SUSPENDED = 'AGEN_SUSPENDED'
AGEN_CLOSED = 'AGEN_CLOSED'
def getasyncgenstate(agen):
if agen.ag_running:
return AGEN_RUNNING
if agen.ag_suspended:
return AGEN_SUSPENDED
if agen.ag_frame is None:
return AGEN_CLOSED
return AGEN_CREATED
def getasyncgenlocals(agen):
if not isasyncgen(agen):
raise TypeError(f"{agen!r} is not a Python async generator")
frame = getattr(agen, "ag_frame", None)
if frame is not None:
return agen.ag_frame.f_locals
else:
return {}
_NonUserDefinedCallables = (types.WrapperDescriptorType,
types.MethodWrapperType,
types.ClassMethodDescriptorType,
types.BuiltinFunctionType)
def _signature_get_user_defined_method(cls, method_name, *, follow_wrapper_chains=True):
if method_name == '__new__':
meth = getattr(cls, method_name, None)
else:
meth = getattr_static(cls, method_name, None)
if meth is None:
return None
unwrapped_meth = None
if follow_wrapper_chains:
unwrapped_meth = unwrap(meth, stop=(lambda m: hasattr(m, "__signature__")
or _signature_is_builtin(m)))
if (isinstance(meth, _NonUserDefinedCallables)
or isinstance(unwrapped_meth, _NonUserDefinedCallables)):
return None
if method_name != '__new__':
meth = _descriptor_get(meth, cls)
return meth
def _signature_get_partial(wrapped_sig, partial, extra_args=()):
old_params = wrapped_sig.parameters
new_params = OrderedDict(old_params.items())
partial_args = partial.args or ()
partial_keywords = partial.keywords or {}
if extra_args:
partial_args = extra_args + partial_args
try:
ba = wrapped_sig.bind_partial(*partial_args, **partial_keywords)
except TypeError as ex:
msg = 'partial object {!r} has incorrect arguments'.format(partial)
raise ValueError(msg) from ex
transform_to_kwonly = False
for param_name, param in old_params.items():
try:
arg_value = ba.arguments[param_name]
except KeyError:
pass
else:
if param.kind is _POSITIONAL_ONLY:
if arg_value is functools.Placeholder:
new_params[param_name] = param.replace(default=_empty)
else:
new_params.pop(param_name)
continue
if param.kind is _POSITIONAL_OR_KEYWORD:
if param_name in partial_keywords:
transform_to_kwonly = True
new_params[param_name] = param.replace(default=arg_value)
else:
if arg_value is functools.Placeholder:
new_param = param.replace(
kind=_POSITIONAL_ONLY,
default=_empty
)
new_params[param_name] = new_param
else:
new_params.pop(param_name)
continue
if param.kind is _KEYWORD_ONLY:
new_params[param_name] = param.replace(default=arg_value)
if transform_to_kwonly:
assert param.kind is not _POSITIONAL_ONLY
if param.kind is _POSITIONAL_OR_KEYWORD:
new_param = new_params[param_name].replace(kind=_KEYWORD_ONLY)
new_params[param_name] = new_param
new_params.move_to_end(param_name)
elif param.kind in (_KEYWORD_ONLY, _VAR_KEYWORD):
new_params.move_to_end(param_name)
elif param.kind is _VAR_POSITIONAL:
new_params.pop(param.name)
return wrapped_sig.replace(parameters=new_params.values())
def _signature_bound_method(sig):
params = tuple(sig.parameters.values())
if not params or params[0].kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
raise ValueError('invalid method signature')
kind = params[0].kind
if kind in (_POSITIONAL_OR_KEYWORD, _POSITIONAL_ONLY):
params = params[1:]
else:
if kind is not _VAR_POSITIONAL:
raise ValueError('invalid argument type')
return sig.replace(parameters=params)
def _signature_is_builtin(obj):
return (isbuiltin(obj) or
ismethoddescriptor(obj) or
isinstance(obj, _NonUserDefinedCallables) or
obj is type or obj is object)
def _signature_is_functionlike(obj):
if not callable(obj) or isclass(obj):
return False
name = getattr(obj, '__name__', None)
code = getattr(obj, '__code__', None)
defaults = getattr(obj, '__defaults__', _void) kwdefaults = getattr(obj, '__kwdefaults__', _void)
return (isinstance(code, types.CodeType) and
isinstance(name, str) and
(defaults is None or isinstance(defaults, tuple)) and
(kwdefaults is None or isinstance(kwdefaults, dict)))
def _signature_strip_non_python_syntax(signature):
if not signature:
return signature, None
self_parameter = None
lines = [l.encode('ascii') for l in signature.split('\n') if l]
generator = iter(lines).__next__
token_stream = tokenize.tokenize(generator)
text = []
add = text.append
current_parameter = 0
OP = token.OP
ERRORTOKEN = token.ERRORTOKEN
t = next(token_stream)
assert t.type == tokenize.ENCODING
for t in token_stream:
type, string = t.type, t.string
if type == OP:
if string == ',':
current_parameter += 1
if (type == OP) and (string == '$'):
assert self_parameter is None
self_parameter = current_parameter
continue
add(string)
if (string == ','):
add(' ')
clean_signature = ''.join(text).strip().replace("\n", "")
return clean_signature, self_parameter
def _signature_fromstr(cls, obj, s, skip_bound_arg=True):
Parameter = cls._parameter_cls
clean_signature, self_parameter = _signature_strip_non_python_syntax(s)
program = "def foo" + clean_signature + ": pass"
try:
module = ast.parse(program)
except SyntaxError:
module = None
if not isinstance(module, ast.Module):
raise ValueError("{!r} builtin has invalid signature".format(obj))
f = module.body[0]
parameters = []
empty = Parameter.empty
module = None
module_dict = {}
module_name = getattr(obj, '__module__', None)
if not module_name:
objclass = getattr(obj, '__objclass__', None)
module_name = getattr(objclass, '__module__', None)
if module_name:
module = sys.modules.get(module_name, None)
if module:
module_dict = module.__dict__
sys_module_dict = sys.modules.copy()
def parse_name(node):
assert isinstance(node, ast.arg)
if node.annotation is not None:
raise ValueError("Annotations are not currently supported")
return node.arg
def wrap_value(s):
try:
value = eval(s, module_dict)
except NameError:
try:
value = eval(s, sys_module_dict)
except NameError:
raise ValueError
if isinstance(value, (str, int, float, bytes, bool, type(None))):
return ast.Constant(value)
raise ValueError
class RewriteSymbolics(ast.NodeTransformer):
def visit_Attribute(self, node):
a = []
n = node
while isinstance(n, ast.Attribute):
a.append(n.attr)
n = n.value
if not isinstance(n, ast.Name):
raise ValueError
a.append(n.id)
value = ".".join(reversed(a))
return wrap_value(value)
def visit_Name(self, node):
if not isinstance(node.ctx, ast.Load):
raise ValueError()
return wrap_value(node.id)
def visit_BinOp(self, node):
left = self.visit(node.left)
right = self.visit(node.right)
if not isinstance(left, ast.Constant) or not isinstance(right, ast.Constant):
raise ValueError
if isinstance(node.op, ast.Add):
return ast.Constant(left.value + right.value)
elif isinstance(node.op, ast.Sub):
return ast.Constant(left.value - right.value)
elif isinstance(node.op, ast.BitOr):
return ast.Constant(left.value | right.value)
raise ValueError
def p(name_node, default_node, default=empty):
name = parse_name(name_node)
if default_node and default_node is not _empty:
try:
default_node = RewriteSymbolics().visit(default_node)
default = ast.literal_eval(default_node)
except ValueError:
raise ValueError("{!r} builtin has invalid signature".format(obj)) from None
parameters.append(Parameter(name, kind, default=default, annotation=empty))
total_non_kw_args = len(f.args.posonlyargs) + len(f.args.args)
required_non_kw_args = total_non_kw_args - len(f.args.defaults)
defaults = itertools.chain(itertools.repeat(None, required_non_kw_args), f.args.defaults)
kind = Parameter.POSITIONAL_ONLY
for (name, default) in zip(f.args.posonlyargs, defaults):
p(name, default)
kind = Parameter.POSITIONAL_OR_KEYWORD
for (name, default) in zip(f.args.args, defaults):
p(name, default)
if f.args.vararg:
kind = Parameter.VAR_POSITIONAL
p(f.args.vararg, empty)
kind = Parameter.KEYWORD_ONLY
for name, default in zip(f.args.kwonlyargs, f.args.kw_defaults):
p(name, default)
if f.args.kwarg:
kind = Parameter.VAR_KEYWORD
p(f.args.kwarg, empty)
if self_parameter is not None:
assert parameters
_self = getattr(obj, '__self__', None)
self_isbound = _self is not None
self_ismodule = ismodule(_self)
if self_isbound and (self_ismodule or skip_bound_arg):
parameters.pop(0)
else:
p = parameters[0].replace(kind=Parameter.POSITIONAL_ONLY)
parameters[0] = p
return cls(parameters, return_annotation=cls.empty)
def _signature_from_builtin(cls, func, skip_bound_arg=True):
if not _signature_is_builtin(func):
raise TypeError("{!r} is not a Python builtin "
"function".format(func))
s = getattr(func, "__text_signature__", None)
if not s:
raise ValueError("no signature found for builtin {!r}".format(func))
return _signature_fromstr(cls, func, s, skip_bound_arg)
def _signature_from_function(cls, func, skip_bound_arg=True,
globals=None, locals=None, eval_str=False,
*, annotation_format=Format.VALUE):
is_duck_function = False
if not isfunction(func):
if _signature_is_functionlike(func):
is_duck_function = True
else:
raise TypeError('{!r} is not a Python function'.format(func))
s = getattr(func, "__text_signature__", None)
if s:
return _signature_fromstr(cls, func, s, skip_bound_arg)
Parameter = cls._parameter_cls
func_code = func.__code__
pos_count = func_code.co_argcount
arg_names = func_code.co_varnames
posonly_count = func_code.co_posonlyargcount
positional = arg_names[:pos_count]
keyword_only_count = func_code.co_kwonlyargcount
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
annotations = get_annotations(func, globals=globals, locals=locals, eval_str=eval_str,
format=annotation_format)
defaults = func.__defaults__
kwdefaults = func.__kwdefaults__
if defaults:
pos_default_count = len(defaults)
else:
pos_default_count = 0
parameters = []
non_default_count = pos_count - pos_default_count
posonly_left = posonly_count
for name in positional[:non_default_count]:
kind = _POSITIONAL_ONLY if posonly_left else _POSITIONAL_OR_KEYWORD
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=kind))
if posonly_left:
posonly_left -= 1
for offset, name in enumerate(positional[non_default_count:]):
kind = _POSITIONAL_ONLY if posonly_left else _POSITIONAL_OR_KEYWORD
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=kind,
default=defaults[offset]))
if posonly_left:
posonly_left -= 1
if func_code.co_flags & CO_VARARGS:
name = arg_names[pos_count + keyword_only_count]
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=_VAR_POSITIONAL))
for name in keyword_only:
default = _empty
if kwdefaults is not None:
default = kwdefaults.get(name, _empty)
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=_KEYWORD_ONLY,
default=default))
if func_code.co_flags & CO_VARKEYWORDS:
index = pos_count + keyword_only_count
if func_code.co_flags & CO_VARARGS:
index += 1
name = arg_names[index]
annotation = annotations.get(name, _empty)
parameters.append(Parameter(name, annotation=annotation,
kind=_VAR_KEYWORD))
return cls(parameters,
return_annotation=annotations.get('return', _empty),
__validate_parameters__=is_duck_function)
def _descriptor_get(descriptor, obj):
if isclass(descriptor):
return descriptor
get = getattr(type(descriptor), '__get__', _sentinel)
if get is _sentinel:
return descriptor
return get(descriptor, obj, type(obj))
def _signature_from_callable(obj, *,
follow_wrapper_chains=True,
skip_bound_arg=True,
globals=None,
locals=None,
eval_str=False,
sigcls,
annotation_format=Format.VALUE):
_get_signature_of = functools.partial(_signature_from_callable,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
globals=globals,
locals=locals,
sigcls=sigcls,
eval_str=eval_str,
annotation_format=annotation_format)
if not callable(obj):
raise TypeError('{!r} is not a callable object'.format(obj))
if isinstance(obj, types.MethodType):
sig = _get_signature_of(obj.__func__)
if skip_bound_arg:
return _signature_bound_method(sig)
else:
return sig
if follow_wrapper_chains:
obj = unwrap(obj, stop=(lambda f: hasattr(f, "__signature__")
or isinstance(f, types.MethodType)))
if isinstance(obj, types.MethodType):
return _get_signature_of(obj)
try:
sig = obj.__signature__
except AttributeError:
pass
else:
if sig is not None:
if not isinstance(sig, Signature):
raise TypeError(
'unexpected object {!r} in __signature__ '
'attribute'.format(sig))
return sig
try:
partialmethod = obj.__partialmethod__
except AttributeError:
pass
else:
if isinstance(partialmethod, functools.partialmethod):
wrapped_sig = _get_signature_of(partialmethod.func)
sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
first_wrapped_param = tuple(wrapped_sig.parameters.values())[0]
if first_wrapped_param.kind is Parameter.VAR_POSITIONAL:
return sig
else:
sig_params = tuple(sig.parameters.values())
assert (not sig_params or
first_wrapped_param is not sig_params[0])
if partialmethod.args.count(functools.Placeholder):
first_wrapped_param = first_wrapped_param.replace(
kind=Parameter.POSITIONAL_ONLY)
new_params = (first_wrapped_param,) + sig_params
return sig.replace(parameters=new_params)
if isinstance(obj, functools.partial):
wrapped_sig = _get_signature_of(obj.func)
return _signature_get_partial(wrapped_sig, obj)
if isfunction(obj) or _signature_is_functionlike(obj):
return _signature_from_function(sigcls, obj,
skip_bound_arg=skip_bound_arg,
globals=globals, locals=locals, eval_str=eval_str,
annotation_format=annotation_format)
if _signature_is_builtin(obj):
return _signature_from_builtin(sigcls, obj,
skip_bound_arg=skip_bound_arg)
if isinstance(obj, type):
call = _signature_get_user_defined_method(
type(obj),
'__call__',
follow_wrapper_chains=follow_wrapper_chains,
)
if call is not None:
return _get_signature_of(call)
new = _signature_get_user_defined_method(
obj,
'__new__',
follow_wrapper_chains=follow_wrapper_chains,
)
init = _signature_get_user_defined_method(
obj,
'__init__',
follow_wrapper_chains=follow_wrapper_chains,
)
for base in obj.__mro__:
if new is not None and '__new__' in base.__dict__:
sig = _get_signature_of(new)
if skip_bound_arg:
sig = _signature_bound_method(sig)
return sig
elif init is not None and '__init__' in base.__dict__:
return _get_signature_of(init)
for base in obj.__mro__[:-1]:
try:
text_sig = base.__text_signature__
except AttributeError:
pass
else:
if text_sig:
return _signature_fromstr(sigcls, base, text_sig)
if type not in obj.__mro__:
obj_init = obj.__init__
obj_new = obj.__new__
if follow_wrapper_chains:
obj_init = unwrap(obj_init)
obj_new = unwrap(obj_new)
if obj_init is object.__init__ and obj_new is object.__new__:
return sigcls.from_callable(object)
else:
raise ValueError(
'no signature found for builtin type {!r}'.format(obj))
else:
call = getattr_static(type(obj), '__call__', None)
if call is not None:
try:
text_sig = obj.__text_signature__
except AttributeError:
pass
else:
if text_sig:
return _signature_fromstr(sigcls, obj, text_sig)
call = _descriptor_get(call, obj)
return _get_signature_of(call)
raise ValueError('callable {!r} is not supported by signature'.format(obj))
class _void:
class _empty:
class _ParameterKind(enum.IntEnum):
POSITIONAL_ONLY = 'positional-only'
POSITIONAL_OR_KEYWORD = 'positional or keyword'
VAR_POSITIONAL = 'variadic positional'
KEYWORD_ONLY = 'keyword-only'
VAR_KEYWORD = 'variadic keyword'
def __new__(cls, description):
value = len(cls.__members__)
member = int.__new__(cls, value)
member._value_ = value
member.description = description
return member
def __str__(self):
return self.name
_POSITIONAL_ONLY = _ParameterKind.POSITIONAL_ONLY
_POSITIONAL_OR_KEYWORD = _ParameterKind.POSITIONAL_OR_KEYWORD
_VAR_POSITIONAL = _ParameterKind.VAR_POSITIONAL
_KEYWORD_ONLY = _ParameterKind.KEYWORD_ONLY
_VAR_KEYWORD = _ParameterKind.VAR_KEYWORD
class Parameter:
__slots__ = ('_name', '_kind', '_default', '_annotation')
POSITIONAL_ONLY = _POSITIONAL_ONLY
POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
VAR_POSITIONAL = _VAR_POSITIONAL
KEYWORD_ONLY = _KEYWORD_ONLY
VAR_KEYWORD = _VAR_KEYWORD
empty = _empty
def __init__(self, name, kind, *, default=_empty, annotation=_empty):
try:
self._kind = _ParameterKind(kind)
except ValueError:
raise ValueError(f'value {kind!r} is not a valid Parameter.kind')
if default is not _empty:
if self._kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
msg = '{} parameters cannot have default values'
msg = msg.format(self._kind.description)
raise ValueError(msg)
self._default = default
self._annotation = annotation
if name is _empty:
raise ValueError('name is a required attribute for Parameter')
if not isinstance(name, str):
msg = 'name must be a str, not a {}'.format(type(name).__name__)
raise TypeError(msg)
if name[0] == '.' and name[1:].isdigit():
if self._kind != _POSITIONAL_OR_KEYWORD:
msg = (
'implicit arguments must be passed as '
'positional or keyword arguments, not {}'
)
msg = msg.format(self._kind.description)
raise ValueError(msg)
self._kind = _POSITIONAL_ONLY
name = 'implicit{}'.format(name[1:])
is_keyword = iskeyword(name) and self._kind is not _POSITIONAL_ONLY
if is_keyword or not name.isidentifier():
raise ValueError('{!r} is not a valid parameter name'.format(name))
self._name = name
def __reduce__(self):
return (type(self),
(self._name, self._kind),
{'_default': self._default,
'_annotation': self._annotation})
def __setstate__(self, state):
self._default = state['_default']
self._annotation = state['_annotation']
@property
def name(self):
return self._name
@property
def default(self):
return self._default
@property
def annotation(self):
return self._annotation
@property
def kind(self):
return self._kind
def replace(self, *, name=_void, kind=_void,
annotation=_void, default=_void):
if name is _void:
name = self._name
if kind is _void:
kind = self._kind
if annotation is _void:
annotation = self._annotation
if default is _void:
default = self._default
return type(self)(name, kind, default=default, annotation=annotation)
def __str__(self):
return self._format()
def _format(self, *, quote_annotation_strings=True):
kind = self.kind
formatted = self._name
if self._annotation is not _empty:
annotation = formatannotation(self._annotation,
quote_annotation_strings=quote_annotation_strings)
formatted = '{}: {}'.format(formatted, annotation)
if self._default is not _empty:
if self._annotation is not _empty:
formatted = '{} = {}'.format(formatted, repr(self._default))
else:
formatted = '{}={}'.format(formatted, repr(self._default))
if kind == _VAR_POSITIONAL:
formatted = '*' + formatted
elif kind == _VAR_KEYWORD:
formatted = '**' + formatted
return formatted
__replace__ = replace
def __repr__(self):
return '<{} "{}">'.format(self.__class__.__name__, self)
def __hash__(self):
return hash((self._name, self._kind, self._annotation, self._default))
def __eq__(self, other):
if self is other:
return True
if not isinstance(other, Parameter):
return NotImplemented
return (self._name == other._name and
self._kind == other._kind and
self._default == other._default and
self._annotation == other._annotation)
class BoundArguments:
__slots__ = ('arguments', '_signature', '__weakref__')
def __init__(self, signature, arguments):
self.arguments = arguments
self._signature = signature
@property
def signature(self):
return self._signature
@property
def args(self):
args = []
for param_name, param in self._signature.parameters.items():
if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
break
try:
arg = self.arguments[param_name]
except KeyError:
break
else:
if param.kind == _VAR_POSITIONAL:
args.extend(arg)
else:
args.append(arg)
return tuple(args)
@property
def kwargs(self):
kwargs = {}
kwargs_started = False
for param_name, param in self._signature.parameters.items():
if not kwargs_started:
if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
kwargs_started = True
else:
if param_name not in self.arguments:
kwargs_started = True
continue
if not kwargs_started:
continue
try:
arg = self.arguments[param_name]
except KeyError:
pass
else:
if param.kind == _VAR_KEYWORD:
kwargs.update(arg)
else:
kwargs[param_name] = arg
return kwargs
def apply_defaults(self):
arguments = self.arguments
new_arguments = []
for name, param in self._signature.parameters.items():
try:
new_arguments.append((name, arguments[name]))
except KeyError:
if param.default is not _empty:
val = param.default
elif param.kind is _VAR_POSITIONAL:
val = ()
elif param.kind is _VAR_KEYWORD:
val = {}
else:
continue
new_arguments.append((name, val))
self.arguments = dict(new_arguments)
def __eq__(self, other):
if self is other:
return True
if not isinstance(other, BoundArguments):
return NotImplemented
return (self.signature == other.signature and
self.arguments == other.arguments)
def __setstate__(self, state):
self._signature = state['_signature']
self.arguments = state['arguments']
def __getstate__(self):
return {'_signature': self._signature, 'arguments': self.arguments}
def __repr__(self):
args = []
for arg, value in self.arguments.items():
args.append('{}={!r}'.format(arg, value))
return '<{} ({})>'.format(self.__class__.__name__, ', '.join(args))
class Signature:
__slots__ = ('_return_annotation', '_parameters')
_parameter_cls = Parameter
_bound_arguments_cls = BoundArguments
empty = _empty
def __init__(self, parameters=None, *, return_annotation=_empty,
__validate_parameters__=True):
if parameters is None:
params = OrderedDict()
else:
if __validate_parameters__:
params = OrderedDict()
top_kind = _POSITIONAL_ONLY
seen_default = False
seen_var_parameters = set()
for param in parameters:
kind = param.kind
name = param.name
if kind in (_VAR_POSITIONAL, _VAR_KEYWORD):
if kind in seen_var_parameters:
msg = f'more than one {kind.description} parameter'
raise ValueError(msg)
seen_var_parameters.add(kind)
if kind < top_kind:
msg = (
'wrong parameter order: {} parameter before {} '
'parameter'
)
msg = msg.format(top_kind.description,
kind.description)
raise ValueError(msg)
elif kind > top_kind:
top_kind = kind
if kind in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD):
if param.default is _empty:
if seen_default:
msg = 'non-default argument follows default ' \
'argument'
raise ValueError(msg)
else:
seen_default = True
if name in params:
msg = 'duplicate parameter name: {!r}'.format(name)
raise ValueError(msg)
params[name] = param
else:
params = OrderedDict((param.name, param) for param in parameters)
self._parameters = types.MappingProxyType(params)
self._return_annotation = return_annotation
@classmethod
def from_callable(cls, obj, *,
follow_wrapped=True, globals=None, locals=None, eval_str=False,
annotation_format=Format.VALUE):
return _signature_from_callable(obj, sigcls=cls,
follow_wrapper_chains=follow_wrapped,
globals=globals, locals=locals, eval_str=eval_str,
annotation_format=annotation_format)
@property
def parameters(self):
return self._parameters
@property
def return_annotation(self):
return self._return_annotation
def replace(self, *, parameters=_void, return_annotation=_void):
if parameters is _void:
parameters = self.parameters.values()
if return_annotation is _void:
return_annotation = self._return_annotation
return type(self)(parameters,
return_annotation=return_annotation)
__replace__ = replace
def _hash_basis(self):
params = tuple(param for param in self.parameters.values()
if param.kind != _KEYWORD_ONLY)
kwo_params = {param.name: param for param in self.parameters.values()
if param.kind == _KEYWORD_ONLY}
return params, kwo_params, self.return_annotation
def __hash__(self):
params, kwo_params, return_annotation = self._hash_basis()
kwo_params = frozenset(kwo_params.values())
return hash((params, kwo_params, return_annotation))
def __eq__(self, other):
if self is other:
return True
if not isinstance(other, Signature):
return NotImplemented
return self._hash_basis() == other._hash_basis()
def _bind(self, args, kwargs, *, partial=False):
arguments = {}
parameters = iter(self.parameters.values())
parameters_ex = ()
arg_vals = iter(args)
pos_only_param_in_kwargs = []
while True:
try:
arg_val = next(arg_vals)
except StopIteration:
try:
param = next(parameters)
except StopIteration:
break
else:
if param.kind == _VAR_POSITIONAL:
break
elif param.name in kwargs:
if param.kind == _POSITIONAL_ONLY:
if param.default is _empty:
msg = f'missing a required positional-only argument: {param.name!r}'
raise TypeError(msg)
pos_only_param_in_kwargs.append(param)
continue
parameters_ex = (param,)
break
elif (param.kind == _VAR_KEYWORD or
param.default is not _empty):
parameters_ex = (param,)
break
else:
if partial:
parameters_ex = (param,)
break
else:
if param.kind == _KEYWORD_ONLY:
argtype = ' keyword-only'
else:
argtype = ''
msg = 'missing a required{argtype} argument: {arg!r}'
msg = msg.format(arg=param.name, argtype=argtype)
raise TypeError(msg) from None
else:
try:
param = next(parameters)
except StopIteration:
raise TypeError('too many positional arguments') from None
else:
if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
raise TypeError(
'too many positional arguments') from None
if param.kind == _VAR_POSITIONAL:
values = [arg_val]
values.extend(arg_vals)
arguments[param.name] = tuple(values)
break
if param.name in kwargs and param.kind != _POSITIONAL_ONLY:
raise TypeError(
'multiple values for argument {arg!r}'.format(
arg=param.name)) from None
arguments[param.name] = arg_val
kwargs_param = None
for param in itertools.chain(parameters_ex, parameters):
if param.kind == _VAR_KEYWORD:
kwargs_param = param
continue
if param.kind == _VAR_POSITIONAL:
continue
param_name = param.name
try:
arg_val = kwargs.pop(param_name)
except KeyError:
if (not partial and param.kind != _VAR_POSITIONAL and
param.default is _empty):
raise TypeError('missing a required argument: {arg!r}'. \
format(arg=param_name)) from None
else:
arguments[param_name] = arg_val
if kwargs:
if kwargs_param is not None:
arguments[kwargs_param.name] = kwargs
elif pos_only_param_in_kwargs:
raise TypeError(
'got some positional-only arguments passed as '
'keyword arguments: {arg!r}'.format(
arg=', '.join(
param.name
for param in pos_only_param_in_kwargs
),
),
)
else:
raise TypeError(
'got an unexpected keyword argument {arg!r}'.format(
arg=next(iter(kwargs))))
return self._bound_arguments_cls(self, arguments)
def bind(self, /, *args, **kwargs):
return self._bind(args, kwargs)
def bind_partial(self, /, *args, **kwargs):
return self._bind(args, kwargs, partial=True)
def __reduce__(self):
return (type(self),
(tuple(self._parameters.values()),),
{'_return_annotation': self._return_annotation})
def __setstate__(self, state):
self._return_annotation = state['_return_annotation']
def __repr__(self):
return '<{} {}>'.format(self.__class__.__name__, self)
def __str__(self):
return self.format()
def format(self, *, max_width=None, quote_annotation_strings=True):
result = []
render_pos_only_separator = False
render_kw_only_separator = True
for param in self.parameters.values():
formatted = param._format(quote_annotation_strings=quote_annotation_strings)
kind = param.kind
if kind == _POSITIONAL_ONLY:
render_pos_only_separator = True
elif render_pos_only_separator:
result.append('/')
render_pos_only_separator = False
if kind == _VAR_POSITIONAL:
render_kw_only_separator = False
elif kind == _KEYWORD_ONLY and render_kw_only_separator:
result.append('*')
render_kw_only_separator = False
result.append(formatted)
if render_pos_only_separator:
result.append('/')
rendered = '({})'.format(', '.join(result))
if max_width is not None and len(rendered) > max_width:
rendered = '(\n {}\n)'.format(',\n '.join(result))
if self.return_annotation is not _empty:
anno = formatannotation(self.return_annotation,
quote_annotation_strings=quote_annotation_strings)
rendered += ' -> {}'.format(anno)
return rendered
def signature(obj, *, follow_wrapped=True, globals=None, locals=None, eval_str=False,
annotation_format=Format.VALUE):
return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
globals=globals, locals=locals, eval_str=eval_str,
annotation_format=annotation_format)
class BufferFlags(enum.IntFlag):
SIMPLE = 0x0
WRITABLE = 0x1
FORMAT = 0x4
ND = 0x8
STRIDES = 0x10 | ND
C_CONTIGUOUS = 0x20 | STRIDES
F_CONTIGUOUS = 0x40 | STRIDES
ANY_CONTIGUOUS = 0x80 | STRIDES
INDIRECT = 0x100 | STRIDES
CONTIG = ND | WRITABLE
CONTIG_RO = ND
STRIDED = STRIDES | WRITABLE
STRIDED_RO = STRIDES
RECORDS = STRIDES | WRITABLE | FORMAT
RECORDS_RO = STRIDES | FORMAT
FULL = INDIRECT | WRITABLE | FORMAT
FULL_RO = INDIRECT | FORMAT
READ = 0x100
WRITE = 0x200
def _main():
import argparse
import importlib
parser = argparse.ArgumentParser(color=True)
parser.add_argument(
'object',
help="The object to be analysed. "
"It supports the 'module:qualname' syntax")
parser.add_argument(
'-d', '--details', action='store_true',
help='Display info about the module rather than its source code')
args = parser.parse_args()
target = args.object
mod_name, has_attrs, attrs = target.partition(":")
try:
obj = module = importlib.import_module(mod_name)
except Exception as exc:
msg = "Failed to import {} ({}: {})".format(mod_name,
type(exc).__name__,
exc)
print(msg, file=sys.stderr)
sys.exit(2)
if has_attrs:
parts = attrs.split(".")
obj = module
for part in parts:
obj = getattr(obj, part)
if module.__name__ in sys.builtin_module_names:
print("Can't get info for builtin modules.", file=sys.stderr)
sys.exit(1)
if args.details:
print('Target: {}'.format(target))
print('Origin: {}'.format(getsourcefile(module)))
print('Cached: {}'.format(module.__cached__))
if obj is module:
print('Loader: {}'.format(repr(module.__loader__)))
if hasattr(module, '__path__'):
print('Submodule search path: {}'.format(module.__path__))
else:
try:
__, lineno = findsource(obj)
except Exception:
pass
else:
print('Line: {}'.format(lineno))
print('\n')
else:
print(getsource(obj))
if __name__ == "__main__":
_main()