Files
nand2tetris/asm/venv/lib/python3.11/site-packages/spyder_kernels/utils/dochelpers.py
Sven Riwoldt c7bc862c6f asm
2024-04-01 20:30:24 +02:00

377 lines
12 KiB
Python

# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) 2009- Spyder Kernels Contributors
#
# Licensed under the terms of the MIT License
# (see spyder_kernels/__init__.py for details)
# -----------------------------------------------------------------------------
"""Utilities and wrappers around inspect module"""
from __future__ import print_function
import inspect
import re
# Local imports:
from spyder_kernels.py3compat import (is_text_string, builtins, get_meth_func,
get_meth_class_inst, get_meth_class,
get_func_defaults, to_text_string, PY2)
SYMBOLS = r"[^\'\"a-zA-Z0-9_.]"
def getobj(txt, last=False):
"""Return the last valid object name in string"""
txt_end = ""
for startchar, endchar in ["[]", "()"]:
if txt.endswith(endchar):
pos = txt.rfind(startchar)
if pos:
txt_end = txt[pos:]
txt = txt[:pos]
tokens = re.split(SYMBOLS, txt)
token = None
try:
while token is None or re.match(SYMBOLS, token):
token = tokens.pop()
if token.endswith('.'):
token = token[:-1]
if token.startswith('.'):
# Invalid object name
return None
if last:
#XXX: remove this statement as well as the "last" argument
token += txt[ txt.rfind(token) + len(token) ]
token += txt_end
if token:
return token
except IndexError:
return None
def getobjdir(obj):
"""
For standard objects, will simply return dir(obj)
In special cases (e.g. WrapITK package), will return only string elements
of result returned by dir(obj)
"""
return [item for item in dir(obj) if is_text_string(item)]
def getdoc(obj):
"""
Return text documentation from an object. This comes in a form of
dictionary with four keys:
name:
The name of the inspected object
argspec:
It's argspec
note:
A phrase describing the type of object (function or method) we are
inspecting, and the module it belongs to.
docstring:
It's docstring
"""
docstring = inspect.getdoc(obj) or inspect.getcomments(obj) or ''
# Most of the time doc will only contain ascii characters, but there are
# some docstrings that contain non-ascii characters. Not all source files
# declare their encoding in the first line, so querying for that might not
# yield anything, either. So assume the most commonly used
# multi-byte file encoding (which also covers ascii).
try:
docstring = to_text_string(docstring)
except:
pass
# Doc dict keys
doc = {'name': '',
'argspec': '',
'note': '',
'docstring': docstring}
if callable(obj):
try:
name = obj.__name__
except AttributeError:
doc['docstring'] = docstring
return doc
if inspect.ismethod(obj):
imclass = get_meth_class(obj)
if get_meth_class_inst(obj) is not None:
doc['note'] = 'Method of %s instance' \
% get_meth_class_inst(obj).__class__.__name__
else:
doc['note'] = 'Unbound %s method' % imclass.__name__
obj = get_meth_func(obj)
elif hasattr(obj, '__module__'):
doc['note'] = 'Function of %s module' % obj.__module__
else:
doc['note'] = 'Function'
doc['name'] = obj.__name__
if inspect.isfunction(obj):
if PY2:
args, varargs, varkw, defaults = inspect.getargspec(obj)
doc['argspec'] = inspect.formatargspec(
args, varargs, varkw, defaults,
formatvalue=lambda o:'='+repr(o))
else:
# This is necessary to catch errors for objects without a
# signature, like numpy.where.
# Fixes spyder-ide/spyder#21148
try:
sig = inspect.signature(obj)
except ValueError:
sig = getargspecfromtext(doc['docstring'])
if not sig:
sig = '(...)'
doc['argspec'] = str(sig)
if name == '<lambda>':
doc['name'] = name + ' lambda '
doc['argspec'] = doc['argspec'][1:-1] # remove parentheses
else:
argspec = getargspecfromtext(doc['docstring'])
if argspec:
doc['argspec'] = argspec
# Many scipy and numpy docstrings begin with a function
# signature on the first line. This ends up begin redundant
# when we are using title and argspec to create the
# rich text "Definition:" field. We'll carefully remove this
# redundancy but only under a strict set of conditions:
# Remove the starting charaters of the 'doc' portion *iff*
# the non-whitespace characters on the first line
# match *exactly* the combined function title
# and argspec we determined above.
signature = doc['name'] + doc['argspec']
docstring_blocks = doc['docstring'].split("\n\n")
first_block = docstring_blocks[0].strip()
if first_block == signature:
doc['docstring'] = doc['docstring'].replace(
signature, '', 1).lstrip()
else:
doc['argspec'] = '(...)'
# Remove self from argspec
argspec = doc['argspec']
doc['argspec'] = argspec.replace('(self)', '()').replace('(self, ', '(')
return doc
def getsource(obj):
"""Wrapper around inspect.getsource"""
try:
try:
src = to_text_string(inspect.getsource(obj))
except TypeError:
if hasattr(obj, '__class__'):
src = to_text_string(inspect.getsource(obj.__class__))
else:
# Bindings like VTK or ITK require this case
src = getdoc(obj)
return src
except (TypeError, IOError):
return
def getsignaturefromtext(text, objname):
"""Get object signature from text (i.e. object documentation)."""
if isinstance(text, dict):
text = text.get('docstring', '')
# Regexps
args_re = r'(\(.+?\))'
if objname:
signature_re = objname + args_re
else:
identifier_re = r'(\w+)'
signature_re = identifier_re + args_re
# Grabbing signatures
if not text:
text = ''
sigs = re.findall(signature_re, text)
# The most relevant signature is usually the first one. There could be
# others in doctests or other places, but those are not so important.
sig = ''
if sigs:
if PY2:
# We don't have an easy way to check if the identifier detected by
# signature_re is a valid one in Python 2. So, we simply select the
# first match.
sig = sigs[0] if objname else sigs[0][1]
else:
# Default signatures returned by IPython.
# Notes:
# * These are not real signatures but only used to provide a
# placeholder.
# * We skip them if we can find other signatures in `text`.
# * This is necessary because we also use this function in Spyder
# to parse the content of inspect replies that come from the
# kernel, which can include these signatures.
default_ipy_sigs = [
'(*args, **kwargs)',
'(self, /, *args, **kwargs)'
]
if objname:
real_sigs = [s for s in sigs if s not in default_ipy_sigs]
if real_sigs:
sig = real_sigs[0]
else:
sig = sigs[0]
else:
valid_sigs = [s for s in sigs if s[0].isidentifier()]
if valid_sigs:
real_sigs = [
s for s in valid_sigs if s[1] not in default_ipy_sigs
]
if real_sigs:
sig = real_sigs[0][1]
else:
sig = valid_sigs[0][1]
return sig
def getargspecfromtext(text):
"""
Try to get the formatted argspec of a callable from the first block of its
docstring.
This will return something like `(x, y, k=1)`.
"""
blocks = text.split("\n\n")
first_block = blocks[0].strip().replace('\n', '')
return getsignaturefromtext(first_block, '')
def getargsfromtext(text, objname):
"""Get arguments from text (object documentation)."""
signature = getsignaturefromtext(text, objname)
if signature:
argtxt = signature[signature.find('(') + 1:-1]
return argtxt.split(',')
def getargsfromdoc(obj):
"""Get arguments from object doc"""
if obj.__doc__ is not None:
return getargsfromtext(obj.__doc__, obj.__name__)
def getargs(obj):
"""Get the names and default values of a function's arguments"""
if inspect.isfunction(obj) or inspect.isbuiltin(obj):
func_obj = obj
elif inspect.ismethod(obj):
func_obj = get_meth_func(obj)
elif inspect.isclass(obj) and hasattr(obj, '__init__'):
func_obj = getattr(obj, '__init__')
else:
return []
if not hasattr(func_obj, '__code__'):
# Builtin: try to extract info from doc
args = getargsfromdoc(func_obj)
if args is not None:
return args
else:
# Example: PyQt5
return getargsfromdoc(obj)
args, _, _ = inspect.getargs(func_obj.__code__)
if not args:
return getargsfromdoc(obj)
# Supporting tuple arguments in def statement:
for i_arg, arg in enumerate(args):
if isinstance(arg, list):
args[i_arg] = "(%s)" % ", ".join(arg)
defaults = get_func_defaults(func_obj)
if defaults is not None:
for index, default in enumerate(defaults):
args[index + len(args) - len(defaults)] += '=' + repr(default)
if inspect.isclass(obj) or inspect.ismethod(obj):
if len(args) == 1:
return None
# Remove 'self' from args
if 'self' in args:
args.remove('self')
return args
def getargtxt(obj, one_arg_per_line=True):
"""
Get the names and default values of a function's arguments
Return list with separators (', ') formatted for calltips
"""
args = getargs(obj)
if args:
sep = ', '
textlist = None
for i_arg, arg in enumerate(args):
if textlist is None:
textlist = ['']
textlist[-1] += arg
if i_arg < len(args)-1:
textlist[-1] += sep
if len(textlist[-1]) >= 32 or one_arg_per_line:
textlist.append('')
if inspect.isclass(obj) or inspect.ismethod(obj):
if len(textlist) == 1:
return None
if 'self'+sep in textlist:
textlist.remove('self'+sep)
return textlist
def isdefined(obj, force_import=False, namespace=None):
"""Return True if object is defined in namespace
If namespace is None --> namespace = locals()"""
if namespace is None:
namespace = locals()
attr_list = obj.split('.')
base = attr_list.pop(0)
if len(base) == 0:
return False
if base not in builtins.__dict__ and base not in namespace:
if force_import:
try:
module = __import__(base, globals(), namespace)
if base not in globals():
globals()[base] = module
namespace[base] = module
except Exception:
return False
else:
return False
for attr in attr_list:
try:
attr_not_found = not hasattr(eval(base, namespace), attr)
except (AttributeError, SyntaxError, TypeError):
return False
if attr_not_found:
if force_import:
try:
__import__(base+'.'+attr, globals(), namespace)
except (ImportError, SyntaxError):
return False
else:
return False
base += '.'+attr
return True