From e7b45c9cb13200f639799cec76e7bd73c4c3aa6e Mon Sep 17 00:00:00 2001 From: <> Date: Wed, 9 Oct 2024 18:43:37 +0000 Subject: [PATCH] Deployed cedb74c with MkDocs version: 1.6.1 --- .nojekyll | 0 404.html | 514 ++ CHANGELOG/index.html | 1842 +++++ api/source/varname.core/index.html | 1098 +++ api/source/varname.helpers/index.html | 870 +++ api/source/varname.ignore/index.html | 971 +++ api/source/varname.utils/index.html | 1289 ++++ api/source/varname/index.html | 577 ++ api/varname.core/index.html | 1139 +++ api/varname.helpers/index.html | 1115 +++ api/varname.ignore/index.html | 3334 ++++++++ api/varname.utils/index.html | 1804 +++++ api/varname/index.html | 1120 +++ assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.525ec568.min.js | 16 + assets/javascripts/bundle.525ec568.min.js.map | 7 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.el.min.js | 1 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.he.min.js | 1 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.6ce7567c.min.js | 42 + .../workers/search.6ce7567c.min.js.map | 7 + assets/stylesheets/main.8c3ca2c6.min.css | 1 + assets/stylesheets/main.8c3ca2c6.min.css.map | 1 + assets/stylesheets/palette.06af60db.min.css | 1 + .../stylesheets/palette.06af60db.min.css.map | 1 + css/mkapi-common.css | 431 ++ favicon.png | Bin 0 -> 3571 bytes index.html | 1232 +++ js/mkapi.js | 12 + logo.png | Bin 0 -> 4550 bytes requirements.txt | 4 + search/search_index.json | 1 + sitemap.xml | 3 + sitemap.xml.gz | Bin 0 -> 127 bytes style.css | 115 + 66 files changed, 24748 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 CHANGELOG/index.html create mode 100644 api/source/varname.core/index.html create mode 100644 api/source/varname.helpers/index.html create mode 100644 api/source/varname.ignore/index.html create mode 100644 api/source/varname.utils/index.html create mode 100644 api/source/varname/index.html create mode 100644 api/varname.core/index.html create mode 100644 api/varname.helpers/index.html create mode 100644 api/varname.ignore/index.html create mode 100644 api/varname.utils/index.html create mode 100644 api/varname/index.html create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.525ec568.min.js create mode 100644 assets/javascripts/bundle.525ec568.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js create mode 100644 assets/javascripts/workers/search.6ce7567c.min.js.map create mode 100644 assets/stylesheets/main.8c3ca2c6.min.css create mode 100644 assets/stylesheets/main.8c3ca2c6.min.css.map create mode 100644 assets/stylesheets/palette.06af60db.min.css create mode 100644 assets/stylesheets/palette.06af60db.min.css.map create mode 100644 css/mkapi-common.css create mode 100644 favicon.png create mode 100644 index.html create mode 100644 js/mkapi.js create mode 100644 logo.png create mode 100644 requirements.txt create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 style.css diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..af09401 --- /dev/null +++ b/404.html @@ -0,0 +1,514 @@ + + + +
+ + + + + + + + + + + + + + +helpers.jsobj()
(#111)helpers.jsobj()
(#110)varname
of a.b
now returns "a.b"
instead of "a"
helpers.exec_code
function to replace exec
so that source code available at runtime✨ Add jsobj
to create dict without explicitly specifying the key-value pairs
from varname.helpers import jsobj
+
+a = 1
+b = 2
+# before
+dict(a=a, b=b, c=3) # {'a': 1, 'b': 2, 'c': 3}
+
+# after
+jsobj(a, b, c=3) # {'a': 1, 'b': 2, 'c': 3}
+
varname(strict=...)
argname2
argname
even when vars_only=True
✨ Support __getattr__/__setattr__
etc for argname
Now you can do:
+from varname import argname
+
+class Foo:
+ def __getattr__(self, name):
+ """Similar for `__getitem__`"""
+ print(argname("name"))
+
+ def __setattr__(self, name, value):
+ """Similar for `__setitem__`"""
+ print(argname("name"))
+ print(argname("value"))
+
+ def __add__(self, other):
+ """Similar for `__sub__`, `__mul__`, `__truediv__`, `__floordiv__`,
+ `__mod__`, `__pow__`, `__lshift__`, `__rshift__`, `__matmul__`,
+ `__and__`, `__xor__`, `__or__`
+ """
+ print(argname("other"))
+
+ def __eq__(self, other):
+ """Similar for `__lt__`, `__le__`, `__gt__`, `__ge__`, `__ne__`
+ """
+ print(argname("other"))
+
+foo = Foo()
+b = 1
+foo.x # prints: 'x' (note the quotes)
+foo.x = b # prints: 'x' and b
+foo + b # prints: b
+foo == b # prints: b
+
This is more of a housekeeping release:
+executing
to 0.8.3 to make varname work with ipython 8+README.md
to add new contributorsflake8
instead of pylint
for lintingCompared to v0.7.3
UsingExecWarning
when exec
is used to retrieve func
for argname()
.NonVariableArgumentError
. Use ImproperUseError
instead.VarnameError
and VarnameWarning
as root for varname-related exceptions and warnings, respectively.strict
to True
for varname()
, helpers.register()
and helpers.Wrapper()
ImproperUseError
happensCompared to v0.7.0
UsingExecWarning
when exec
is used to retrieve func
for argname()
.NonVariableArgumentError
. Use ImproperUseError
instead.VarnameError
and VarnameWarning
as root for varname-related exceptions and warnings, respectively.strict
mode to varname()
, helpers.register()
and helpers.Wrapper()
(#57):=
) (#58)argname()
to accept argument names instead of arguments themselvespos_only
argument from argname()
ignore
argument to argname()
to ignore intermediate framesVarnameRetrievingError
to the situations only when the AST node is not able to be retrieved.ImproperUseError
happens for varname()
(Close #60)VarnameException
and VarnameWarning
as root for all varname-defined exceptions and warnings.strict
mode to varname()
(#57):=
) (#58)ignore
argument to argname2()
**kwargs
.ImproperUseError
is now independent of VarnameRetrievingError
argname
, superseded by argname2
>>> argname(a, b, ...) # before
+ >>> argname2('a', 'b', ...) # after
+
dispatch
argument to argname
/argment2
to be used for single-dispatched functions.sep
argument to helpers.debug()
full
for nameof
, use vars_only
instead. When vars_only=False
, source of the argument returned.# before:
+nameof(a.b, full=True) # 'a.b'
+nameof(x[0], full=True) # unable to fetch
+# after (requires asttoken):
+nameof(a.b, vars_only=False) # 'a.b'
+nameof(x[0], vars_only=False) # 'x[0]'
+
frame
to argname
, so that it can be wrapped.def argname2(arg, *more_args):
+ return argname(arg, *more_args, frame=2)
+
argname
to fetch the source of variable keyword arguments (**kwargs
), which will be an empty dict ({}
) when no keyword arguments passed.def func(a, **kwargs):
+ return argname(a, kwargs)
+# before:
+func(x) # raises error
+# after:
+func(x) # returns ('x', {})
+
pos_only
to argname
to only match the positional arguments# before
+def func(a, b=1):
+ return argname(a)
+func(x) # 'x'
+func(x, b=2) # error since 2 is not ast.Name
+
+# after
+def func(a, b=1):
+ return argname(a, pos_only=True)
+func(x) # 'x'
+func(x, b=2) # 'x'
+
# before
+def func(a, b):
+ return argname(a)
+func(x, 1) # NonVariableArgumentError
+
+# after
+func(x, 1) # 'x'
+
argname
so that argname(*args)
is allowed# before
+def func(arg, *args):
+ return argname(arg, args) # *args not allowed
+x = y = 1
+func(x, y) # ('x', ('y', 1))
+
+# after
+def func(arg, *args):
+ return argname(arg, *args)
+x = y = 1
+func(x, y) # ('x', 'y')
+
vars_only
(defaults to False
) argument to helpers.debug
so source of expression becomes availablea=1
+debug(a+a) # DEBUG: a+a=2
+
argname
to retrieve argument names/sources passed to a functionWrapper
, register
and debug
moved to varname.helpers
caller
changed to frame
across all APIsignore
accepting module, filename, function, (function, num_decorators), (module, qualname) and (filename, qualname)inject
(Use helpers.regiester
instead)inject_varname
(Use helpers.regiester
instead)namedtuple
frame
and ignore
to Wrapper
helpers.register
as a decorator for functionsignore
argument to varname
to ignore frames that are not counted by callerinject_varname
, use register
insteadvarname.varname
to receive multiple variables on the left-hand sidedebug
functionnamedtuple
(will be removed in 0.6.0
)_bytecode_nameof
to nameof
.full
to be used when _bytecode_nameof
needs to be invoked.nameof
to retrieve full name of chained attribute calls__all__
to the module so that only desired APIs are exposed when from varname import *
nameof
being called in a weird way when no soucecode available.raise_exc
to True
for all related APIsvar_0
VarnameRetrievingWarning
.will
for better logicinject
functionnamedtuple
will
from a property callwill
to detect next immediate attribute nameraise_exc
for varname
to raise an exception instead of returning var_<index>
nameof
Wrapper
classvarname
function"""Provide core features for varname"""
+import ast
+import re
+import warnings
+from typing import Any, List, Union, Tuple, Type, Callable, overload
+
+from executing import Source
+
+from .utils import (
+ bytecode_nameof,
+ get_node,
+ get_node_by_frame,
+ lookfor_parent_assign,
+ node_name,
+ get_argument_sources,
+ get_function_called_argname,
+ rich_exc_message,
+ reconstruct_func_node,
+ ArgSourceType,
+ VarnameRetrievingError,
+ ImproperUseError,
+ MultiTargetAssignmentWarning,
+)
+from .ignore import IgnoreList, IgnoreType
+
+
+def varname(DOCS
+ frame: int = 1,
+ ignore: IgnoreType = None,
+ multi_vars: bool = False,
+ raise_exc: bool = True,
+ strict: bool = True,
+) -> Union[str, Tuple[Union[str, Tuple], ...]]:
+ """Get the name of the variable(s) that assigned by function call or
+ class instantiation.
+
+ To debug and specify the right frame and ignore arguments, you can set
+ debug on and see how the frames are ignored or selected:
+
+ >>> from varname import config
+ >>> config.debug = True
+
+ Args:
+ frame: `N`th frame used to retrieve the variable name. This means
+ `N-1` intermediate frames will be skipped. Note that the frames
+ match `ignore` will not be counted. See `ignore` for details.
+ ignore: Frames to be ignored in order to reach the `N`th frame.
+ These frames will not be counted to skip within that `N-1` frames.
+ You can specify:
+ - A module (or filename of a module). Any calls from it and its
+ submodules will be ignored.
+ - A function. If it looks like it might be a decorated function,
+ a `MaybeDecoratedFunctionWarning` will be shown.
+ - Tuple of a function and a number of additional frames that should
+ be skipped just before reaching this function in the stack.
+ This is typically used for functions that have been decorated
+ with a 'classic' decorator that replaces the function with
+ a wrapper. In that case each such decorator involved should
+ be counted in the number that's the second element of the tuple.
+ - Tuple of a module (or filename) and qualified name (qualname).
+ You can use Unix shell-style wildcards to match the qualname.
+ Otherwise the qualname must appear exactly once in the
+ module/file.
+ By default, all calls from `varname` package, python standard
+ libraries and lambda functions are ignored.
+ multi_vars: Whether allow multiple variables on left-hand side (LHS).
+ If `True`, this function returns a tuple of the variable names,
+ even there is only one variable on LHS.
+ If `False`, and multiple variables on LHS, a
+ `ImproperUseError` will be raised.
+ raise_exc: Whether we should raise an exception if failed
+ to retrieve the ast node.
+ Note that set this to `False` will NOT supress the exception when
+ the use of `varname` is improper (i.e. multiple variables on
+ LHS with `multi_vars` is `False`). See `Raises/ImproperUseError`.
+ strict: Whether to only return the variable name(s) if the result of
+ the call is assigned to it/them directly. For example, `a = func()`
+ rather than `a = [func()]`
+
+ Returns:
+ The variable name, or `None` when `raise_exc` is `False` and
+ we failed to retrieve the ast node for the variable(s).
+ A tuple or a hierarchy (tuple of tuples) of variable names
+ when `multi_vars` is `True`.
+
+ Raises:
+ VarnameRetrievingError: When we are unable to retrieve the ast node
+ for the variable(s) and `raise_exc` is set to `True`.
+
+ ImproperUseError: When the use of `varname()` is improper, including:
+ - When LHS is not an `ast.Name` or `ast.Attribute` node or not a
+ list/tuple of them
+ - When there are multiple variables on LHS but `multi_vars` is False
+ - When `strict` is True, but the result is not assigned to
+ variable(s) directly
+
+ Note that `raise_exc=False` will NOT suppress this exception.
+
+ MultiTargetAssignmentWarning: When there are multiple target
+ in the assign node. (e.g: `a = b = func()`, in such a case,
+ `a == 'b'`, may not be the case you want)
+ """
+ # Skip one more frame, as it is supposed to be called
+ # inside another function
+ refnode = get_node(frame + 1, ignore, raise_exc=raise_exc)
+ if not refnode:
+ if raise_exc:
+ raise VarnameRetrievingError("Unable to retrieve the ast node.")
+ return None
+
+ node = lookfor_parent_assign(refnode, strict=strict)
+ if not node: # improper use
+ if strict:
+ msg = "Caller doesn't assign the result directly to variable(s)."
+ else:
+ msg = "Expression is not part of an assignment."
+
+ raise ImproperUseError(rich_exc_message(msg, refnode))
+
+ if isinstance(node, ast.Assign):
+ # Need to actually check that there's just one
+ # give warnings if: a = b = func()
+ if len(node.targets) > 1:
+ warnings.warn(
+ "Multiple targets in assignment, variable name "
+ "on the very right is used. ",
+ MultiTargetAssignmentWarning,
+ )
+ target = node.targets[-1]
+ else:
+ target = node.target
+
+ names = node_name(target)
+
+ if not isinstance(names, tuple):
+ names = (names,)
+
+ if multi_vars:
+ return names
+
+ if len(names) > 1:
+ raise ImproperUseError(
+ rich_exc_message(
+ "Expect a single variable on left-hand side, "
+ f"got {len(names)}.",
+ refnode,
+ )
+ )
+
+ return names[0]
+
+
+def will(frame: int = 1, raise_exc: bool = True) -> str:DOCS
+ """Detect the attribute name right immediately after a function call.
+
+ Examples:
+ >>> class AwesomeClass:
+ >>> def __init__(self):
+ >>> self.will = None
+
+ >>> def permit(self):
+ >>> self.will = will()
+ >>> if self.will == 'do':
+ >>> # let self handle do
+ >>> return self
+ >>> raise AttributeError(
+ >>> 'Should do something with AwesomeClass object'
+ >>> )
+
+ >>> def do(self):
+ >>> if self.will != 'do':
+ >>> raise AttributeError("You don't have permission to do")
+ >>> return 'I am doing!'
+
+ >>> awesome = AwesomeClass()
+ >>> # AttributeError: You don't have permission to do
+ >>> awesome.do()
+ >>> # AttributeError: Should do something with AwesomeClass object
+ >>> awesome.permit()
+ >>> awesome.permit().do() == 'I am doing!'
+
+ Args:
+ frame: At which frame this function is called.
+ raise_exc: Raise exception we failed to detect the ast node
+ This will NOT supress the `ImproperUseError`
+
+ Returns:
+ The attribute name right after the function call.
+ `None` if ast node cannot be retrieved and `raise_exc` is `False`
+
+ Raises:
+ VarnameRetrievingError: When `raise_exc` is `True` and we failed to
+ detect the attribute name (including not having one)
+
+ ImproperUseError: When (the wraper of) this function is not called
+ inside a method/property of a class instance.
+ Note that this exception will not be suppressed by `raise_exc=False`
+ """
+ node = get_node(frame + 1, raise_exc=raise_exc)
+ if not node:
+ if raise_exc:
+ raise VarnameRetrievingError("Unable to retrieve the frame.")
+ return None
+
+ # try to get node inst.attr from inst.attr()
+ node = node.parent
+
+ # see test_will_fail
+ if not isinstance(node, ast.Attribute):
+ if raise_exc:
+ raise ImproperUseError(
+ "Function `will` has to be called within "
+ "a method/property of a class."
+ )
+ return None
+ # ast.Attribute
+ return node.attr
+
+
+@overload
+def nameof(
+ var: Any,
+ *,
+ frame: int = 1,
+ vars_only: bool = True,
+) -> str: # pragma: no cover
+ ...
+
+
+@overload
+def nameof(
+ var: Any,
+ more_var: Any,
+ /, # introduced in python 3.8
+ *more_vars: Any,
+ frame: int = 1,
+ vars_only: bool = True,
+) -> Tuple[str, ...]: # pragma: no cover
+ ...
+
+
+def nameof(DOCS
+ var: Any,
+ *more_vars: Any,
+ frame: int = 1,
+ vars_only: bool = True,
+) -> Union[str, Tuple[str, ...]]:
+ """Get the names of the variables passed in
+
+ Examples:
+ >>> a = 1
+ >>> nameof(a) # 'a'
+
+ >>> b = 2
+ >>> nameof(a, b) # ('a', 'b')
+
+ >>> x = lambda: None
+ >>> x.y = 1
+ >>> nameof(x.y, vars_only=False) # 'x.y'
+
+ Note:
+ This function works with the environments where source code is
+ available, in other words, the callee's node can be retrieved by
+ `executing`. In some cases, for example, running code from python
+ shell/REPL or from `exec`/`eval`, we try to fetch the variable name
+ from the bytecode. This requires only a single variable name is passed
+ to this function and no keyword arguments, meaning that getting full
+ names of attribute calls are not supported in such cases.
+
+ Args:
+ var: The variable to retrieve the name of
+ *more_vars: Other variables to retrieve the names of
+ frame: The this function is called from the wrapper of it. `frame=1`
+ means no wrappers.
+ Note that the calls from standard libraries are ignored.
+ Also note that the wrapper has to have signature as this one.
+ vars_only: Whether only allow variables/attributes as arguments or
+ any expressions. If `False`, then the sources of the arguments
+ will be returned.
+
+ Returns:
+ The names/sources of variables/expressions passed in.
+ If a single argument is passed, return the name/source of it.
+ If multiple variables are passed, return a tuple of their
+ names/sources.
+ If the argument is an attribute (e.g. `a.b`) and `vars_only` is
+ `True`, only `"b"` will returned. Set `vars_only` to `False` to
+ get `"a.b"`.
+
+ Raises:
+ VarnameRetrievingError: When the callee's node cannot be retrieved or
+ trying to retrieve the full name of non attribute series calls.
+ """
+ warnings.warn(
+ "`nameof` is deprecated and will be removed in the future. "
+ "Please use `argname` instead.",
+ DeprecationWarning,
+ )
+ # Frame is anyway used in get_node
+ frameobj = IgnoreList.create(
+ ignore_lambda=False,
+ ignore_varname=False,
+ ).get_frame(frame)
+
+ node = get_node_by_frame(frameobj, raise_exc=True)
+ if not node:
+ # We can't retrieve the node by executing.
+ # It can be due to running code from python/shell, exec/eval or
+ # other environments where sourcecode cannot be reached
+ # make sure we keep it simple (only single variable passed and no
+ # full passed) to use bytecode_nameof
+ #
+ # We don't have to check keyword arguments here, as the instruction
+ # will then be CALL_FUNCTION_KW.
+ if not more_vars:
+ return bytecode_nameof(frameobj.f_code, frameobj.f_lasti)
+
+ # We are anyway raising exceptions, no worries about additional burden
+ # of frame retrieval again
+ source = frameobj.f_code.co_filename
+ if source == "<stdin>":
+ raise VarnameRetrievingError(
+ "Are you trying to call nameof in REPL/python shell? "
+ "In such a case, nameof can only be called with single "
+ "argument and no keyword arguments."
+ )
+ if source == "<string>":
+ raise VarnameRetrievingError(
+ "Are you trying to call nameof from exec/eval? "
+ "In such a case, nameof can only be called with single "
+ "argument and no keyword arguments."
+ )
+ raise VarnameRetrievingError(
+ "Source code unavailable, nameof can only retrieve the name of "
+ "a single variable, and argument `full` should not be specified."
+ )
+
+ out = argname(
+ "var",
+ "*more_vars",
+ func=nameof,
+ frame=frame,
+ vars_only=vars_only,
+ )
+ return out if more_vars else out[0] # type: ignore
+
+
+@overload
+def argname(
+ arg: str,
+ *,
+ func: Callable = None,
+ dispatch: Type = None,
+ frame: int = 1,
+ ignore: IgnoreType = None,
+ vars_only: bool = True,
+) -> ArgSourceType: # pragma: no cover
+ ...
+
+
+@overload
+def argname(
+ arg: str,
+ more_arg: str,
+ /, # introduced in python 3.8
+ *more_args: str,
+ func: Callable = None,
+ dispatch: Type = None,
+ frame: int = 1,
+ ignore: IgnoreType = None,
+ vars_only: bool = True,
+) -> Tuple[ArgSourceType, ...]: # pragma: no cover
+ ...
+
+
+def argname(DOCS
+ arg: str,
+ *more_args: str,
+ func: Callable = None,
+ dispatch: Type = None,
+ frame: int = 1,
+ ignore: IgnoreType = None,
+ vars_only: bool = True,
+) -> Union[ArgSourceType, Tuple[ArgSourceType, ...]]:
+ """Get the names/sources of arguments passed to a function.
+
+ Instead of passing the argument variables themselves to this function
+ (like `argname()` does), you should pass their names instead.
+
+ Args:
+ arg: and
+ *more_args: The names of the arguments that you want to retrieve
+ names/sources of.
+ You can also use subscripts to get parts of the results.
+ >>> def func(*args, **kwargs):
+ >>> return argname('args[0]', 'kwargs[x]') # no quote needed
+
+ Star argument is also allowed:
+ >>> def func(*args, x = 1):
+ >>> return argname('*args', 'x')
+ >>> a = b = c = 1
+ >>> func(a, b, x=c) # ('a', 'b', 'c')
+
+ Note the difference:
+ >>> def func(*args, x = 1):
+ >>> return argname('args', 'x')
+ >>> a = b = c = 1
+ >>> func(a, b, x=c) # (('a', 'b'), 'c')
+
+ func: The target function. If not provided, the AST node of the
+ function call will be used to fetch the function:
+ - If a variable (ast.Name) used as function, the `node.id` will
+ be used to get the function from `locals()` or `globals()`.
+ - If variable (ast.Name), attributes (ast.Attribute),
+ subscripts (ast.Subscript), and combinations of those and
+ literals used as function, `pure_eval` will be used to evaluate
+ the node
+ - If `pure_eval` is not installed or failed to evaluate, `eval`
+ will be used. A warning will be shown since unwanted side
+ effects may happen in this case.
+ You are very encouraged to always pass the function explicitly.
+ dispatch: If a function is a single-dispatched function, you can
+ specify a type for it to dispatch the real function. If this is
+ specified, expect `func` to be the generic function if provided.
+ frame: The frame where target function is called from this call.
+ Calls from python standard libraries are ignored.
+ ignore: The intermediate calls to be ignored. See `varname.ignore`
+ vars_only: Require the arguments to be variables only.
+ If False, `asttokens` is required to retrieve the source.
+
+ Returns:
+ The argument source when no more_args passed, otherwise a tuple of
+ argument sources
+ Note that when an argument is an `ast.Constant`, `repr(arg.value)`
+ is returned, so `argname()` return `'a'` for `func("a")`
+
+ Raises:
+ VarnameRetrievingError: When the ast node where the function is called
+ cannot be retrieved
+ ImproperUseError: When frame or func is incorrectly specified.
+ """
+ ignore_list = IgnoreList.create(
+ ignore,
+ ignore_lambda=False,
+ ignore_varname=False,
+ )
+ # where func(...) is called, skip the argname() call
+ func_frame = ignore_list.get_frame(frame + 1)
+ func_node = get_node_by_frame(func_frame)
+ # Only do it when func_node are available
+ if not func_node:
+ # We can do something at bytecode level, when a single positional
+ # argument passed to both functions (argname and the target function)
+ # However, it's hard to ensure that there is only a single positional
+ # arguments passed to the target function, at bytecode level.
+ raise VarnameRetrievingError(
+ "Cannot retrieve the node where the function is called."
+ )
+
+ func_node = reconstruct_func_node(func_node)
+
+ if not func:
+ func = get_function_called_argname(func_frame, func_node)
+
+ if dispatch:
+ func = func.dispatch(dispatch)
+
+ # don't pass the target arguments so that we can cache the sources in
+ # the same call. For example:
+ # >>> def func(a, b):
+ # >>> a_name = argname(a)
+ # >>> b_name = argname(b)
+ try:
+ argument_sources = get_argument_sources(
+ Source.for_frame(func_frame),
+ func_node,
+ func,
+ vars_only=vars_only,
+ )
+ except Exception as err:
+ raise ImproperUseError(
+ "Have you specified the right `frame` or `func`?"
+ ) from err
+
+ out: List[ArgSourceType] = []
+ farg_star = False
+ for farg in (arg, *more_args):
+
+ farg_name = farg
+ farg_subscript = None # type: str | int
+ match = re.match(r"^([\w_]+)\[(.+)\]$", farg)
+ if match:
+ farg_name = match.group(1)
+ farg_subscript = match.group(2)
+ if farg_subscript.isdigit():
+ farg_subscript = int(farg_subscript)
+ else:
+ match = re.match(r"^\*([\w_]+)$", farg)
+ if match:
+ farg_name = match.group(1)
+ farg_star = True
+
+ if farg_name not in argument_sources:
+ raise ImproperUseError(
+ f"{farg_name!r} is not a valid argument "
+ f"of {func.__qualname__!r}."
+ )
+
+ source = argument_sources[farg_name]
+ if isinstance(source, ast.AST):
+ raise ImproperUseError(
+ f"Argument {ast.dump(source)} is not a variable "
+ "or an attribute."
+ )
+
+ if isinstance(farg_subscript, int) and not isinstance(source, tuple):
+ raise ImproperUseError(
+ f"`{farg_name}` is not a positional argument."
+ )
+
+ if isinstance(farg_subscript, str) and not isinstance(source, dict):
+ raise ImproperUseError(
+ f"`{farg_name}` is not a keyword argument."
+ )
+
+ if farg_subscript is not None:
+ out.append(source[farg_subscript]) # type: ignore
+ elif farg_star:
+ out.extend(source)
+ else:
+ out.append(source)
+
+ return (
+ out[0]
+ if not more_args and not farg_star
+ else tuple(out) # type: ignore
+ )
+
"""Some helper functions builtin based upon core features"""
+from __future__ import annotations
+
+import inspect
+from functools import partial, wraps
+from os import PathLike
+from typing import Any, Callable, Dict, Tuple, Type, Union
+
+from .utils import IgnoreType
+from .ignore import IgnoreList
+from .core import argname, varname
+
+
+def register(DOCS
+ cls_or_func: type = None,
+ frame: int = 1,
+ ignore: IgnoreType = None,
+ multi_vars: bool = False,
+ raise_exc: bool = True,
+ strict: bool = True,
+) -> Union[Type, Callable]:
+ """A decorator to register __varname__ to a class or function
+
+ When registered to a class, it can be accessed by `self.__varname__`;
+ while to a function, it is registered to globals, meaning that it can be
+ accessed directly.
+
+ Args:
+ frame: The call stack index, indicating where this class
+ is instantiated relative to where the variable is finally retrieved
+ multi_vars: Whether allow multiple variables on left-hand side (LHS).
+ If `True`, this function returns a tuple of the variable names,
+ even there is only one variable on LHS.
+ If `False`, and multiple variables on LHS, a
+ `VarnameRetrievingError` will be raised.
+ raise_exc: Whether we should raise an exception if failed
+ to retrieve the name.
+ strict: Whether to only return the variable name if the result of
+ the call is assigned to it directly.
+
+ Examples:
+ >>> @varname.register
+ >>> class Foo: pass
+ >>> foo = Foo()
+ >>> # foo.__varname__ == 'foo'
+ >>>
+ >>> @varname.register
+ >>> def func():
+ >>> return __varname__
+ >>> foo = func() # foo == 'foo'
+
+ Returns:
+ The wrapper function or the class/function itself
+ if it is specified explictly.
+ """
+ if inspect.isclass(cls_or_func):
+ orig_init = cls_or_func.__init__ # type: ignore
+
+ @wraps(cls_or_func.__init__) # type: ignore
+ def wrapped_init(self, *args, **kwargs):
+ """Wrapped init function to replace the original one"""
+ self.__varname__ = varname(
+ frame - 1,
+ ignore=ignore,
+ multi_vars=multi_vars,
+ raise_exc=raise_exc,
+ strict=strict,
+ )
+ orig_init(self, *args, **kwargs)
+
+ cls_or_func.__init__ = wrapped_init # type: ignore
+ return cls_or_func
+
+ if inspect.isfunction(cls_or_func):
+
+ @wraps(cls_or_func)
+ def wrapper(*args, **kwargs):
+ """The wrapper to register `__varname__` to a function"""
+ cls_or_func.__globals__["__varname__"] = varname(
+ frame - 1,
+ ignore=ignore,
+ multi_vars=multi_vars,
+ raise_exc=raise_exc,
+ strict=strict,
+ )
+
+ try:
+ return cls_or_func(*args, **kwargs)
+ finally:
+ del cls_or_func.__globals__["__varname__"]
+
+ return wrapper
+
+ # None, meaning we have other arguments
+ return partial(
+ register,
+ frame=frame,
+ ignore=ignore,
+ multi_vars=multi_vars,
+ raise_exc=raise_exc,
+ strict=strict,
+ )
+
+
+class Wrapper:DOCS
+ """A wrapper with ability to retrieve the variable name
+
+ Examples:
+ >>> foo = Wrapper(True)
+ >>> # foo.name == 'foo'
+ >>> # foo.value == True
+
+ >>> val = {}
+ >>> bar = Wrapper(val)
+ >>> # bar.name == 'bar'
+ >>> # bar.value is val
+
+ Args:
+ value: The value to be wrapped
+ raise_exc: Whether to raise exception when varname is failed to retrieve
+ strict: Whether to only return the variable name if the wrapper is
+ assigned to it directly.
+
+ Attributes:
+ name: The variable name to which the instance is assigned
+ value: The value this wrapper wraps
+ """
+
+ def __init__(
+ self,
+ value: Any,
+ frame: int = 1,
+ ignore: IgnoreType = None,
+ raise_exc: bool = True,
+ strict: bool = True,
+ ):
+ # This call is ignored, since it's inside varname
+ self.name = varname(
+ frame=frame - 1,
+ ignore=ignore,
+ raise_exc=raise_exc,
+ strict=strict,
+ )
+ self.value = value
+
+ def __str__(self) -> str:
+ return repr(self.value)
+
+ def __repr__(self) -> str:
+ return (
+ f"<{self.__class__.__name__} "
+ f"(name={self.name!r}, value={self.value!r})>"
+ )
+
+
+def jsobj(DOCS
+ *args: Any,
+ vars_only: bool = True,
+ frame: int = 1,
+ **kwargs: Any,
+) -> Dict[str, Any]:
+ """A wrapper to create a JavaScript-like object
+
+ When an argument is passed as positional argument, the name of the variable
+ will be used as the key, while the value will be used as the value.
+
+ Examples:
+ >>> obj = jsobj(a=1, b=2)
+ >>> # obj == {'a': 1, 'b': 2}
+ >>> # obj.a == 1
+ >>> # obj.b == 2
+ >>> a = 1
+ >>> b = 2
+ >>> obj = jsobj(a, b, c=3)
+ >>> # obj == {'a': 1, 'b': 2, 'c': 3}
+
+ Args:
+ *args: The positional arguments
+ vars_only: Whether to only include variables in the output
+ frame: The call stack index. You can understand this as the number of
+ wrappers around this function - 1.
+ **kwargs: The keyword arguments
+
+ Returns:
+ A dict-like object
+ """
+ argnames: Tuple[str, ...] = argname(
+ "args",
+ vars_only=vars_only,
+ frame=frame,
+ ) # type: ignore
+ out = dict(zip(argnames, args))
+ out.update(kwargs)
+ return out
+
+
+def debug(DOCS
+ var,
+ *more_vars,
+ prefix: str = "DEBUG: ",
+ merge: bool = False,
+ repr: bool = True,
+ sep: str = "=",
+ vars_only: bool = False,
+) -> None:
+ """Print variable names and values.
+
+ Examples:
+ >>> a = 1
+ >>> b = object
+ >>> print(f'a={a}') # previously, we have to do
+ >>> print(f'{a=}') # or with python3.8
+ >>> # instead we can do:
+ >>> debug(a) # DEBUG: a=1
+ >>> debug(a, prefix='') # a=1
+ >>> debug(a, b, merge=True) # a=1, b=<object object at 0x2b9a4c89cf00>
+
+ Args:
+ var: The variable to print
+ *more_vars: Other variables to print
+ prefix: A prefix to print for each line
+ merge: Whether merge all variables in one line or not
+ sep: The separator between the variable name and value
+ repr: Print the value as `repr(var)`? otherwise `str(var)`
+ """
+ var_names = argname("var", "*more_vars", vars_only=vars_only, func=debug)
+
+ values = (var, *more_vars)
+ name_and_values = [
+ f"{var_name}{sep}{value!r}" if repr else f"{var_name}{sep}{value}"
+ for var_name, value in zip(var_names, values) # type: ignore
+ ]
+
+ if merge:
+ print(f"{prefix}{', '.join(name_and_values)}")
+ else:
+ for name_and_value in name_and_values:
+ print(f"{prefix}{name_and_value}")
+
+
+def exec_code(DOCS
+ code: str,
+ globals: Dict[str, Any] = None,
+ locals: Dict[str, Any] = None,
+ /,
+ sourcefile: PathLike | str = None,
+ frame: int = 1,
+ ignore: IgnoreType = None,
+ **kwargs: Any,
+) -> None:
+ """Execute code where source code is visible at runtime.
+
+ This function is useful when you want to execute some code, where you want to
+ retrieve the AST node of the code at runtime. This function will create a
+ temporary file and write the code into it, then execute the code in the
+ file.
+
+ Examples:
+ >>> from varname import varname
+ >>> def func(): return varname()
+ >>> exec('var = func()') # VarnameRetrievingError:
+ >>> # Unable to retrieve the ast node.
+ >>> from varname.helpers import code_exec
+ >>> code_exec('var = func()') # var == 'var'
+
+ Args:
+ code: The code to execute.
+ globals: The globals to use.
+ locals: The locals to use.
+ sourcefile: The source file to write the code into.
+ if not given, a temporary file will be used.
+ This file will be deleted after the code is executed.
+ frame: The call stack index. You can understand this as the number of
+ wrappers around this function. This is used to fetch `globals` and
+ `locals` from where the destination function (include the wrappers
+ of this function)
+ is called.
+ ignore: The intermediate calls to be ignored. See `varname.ignore`
+ Note that if both `globals` and `locals` are given, `frame` and
+ `ignore` will be ignored.
+ **kwargs: The keyword arguments to pass to `exec`.
+ """
+ if sourcefile is None:
+ import tempfile
+
+ with tempfile.NamedTemporaryFile(
+ mode="w", suffix=".py", delete=False
+ ) as f:
+ f.write(code)
+ sourcefile = f.name
+ else:
+ sourcefile = str(sourcefile)
+ with open(sourcefile, "w") as f:
+ f.write(code)
+
+ if globals is None or locals is None:
+ ignore_list = IgnoreList.create(ignore)
+ frame_info = ignore_list.get_frame(frame)
+ if globals is None:
+ globals = frame_info.f_globals
+ if locals is None:
+ locals = frame_info.f_locals
+
+ try:
+ exec(compile(code, sourcefile, "exec"), globals, locals, **kwargs)
+ finally:
+ import os
+
+ os.remove(sourcefile)
+
"""The frame ignoring system for varname
+
+There 4 mechanisms to ignore intermediate frames to determine the desired one
+so that a variable name should be retrieved at that frame.
+
+1. Ignore frames by a given module. Any calls inside it and inside its
+ submodules will be ignored. A filename (path) to a module is also acceptable
+ and recommended when code is executed by `exec` without module available.
+2. Ignore frames by a given pair of module and a qualified name (qualname).
+ See 1) for acceptable modules. The qualname should be unique in that module.
+3. Ignore frames by a (non-decorated) function.
+4. Ignore frames by a decorated function. In this case, you can specified a
+ tuple with the function and the number of decorators of it. The decorators
+ on the wrapper function inside the decorators should also be counted.
+
+Any frames in `varname`, standard libraries, and frames of any expressions like
+<lambda> are ignored by default.
+
+"""
+import sys
+import inspect
+import warnings
+from os import path
+from pathlib import Path
+from fnmatch import fnmatch
+from abc import ABC, abstractmethod
+from typing import List, Union
+from types import FrameType, ModuleType, FunctionType
+
+from executing import Source
+
+try:
+ import sysconfig # 3.10+
+except ImportError: # pragma: no cover
+ from distutils import sysconfig
+ STANDLIB_PATH = sysconfig.get_python_lib(standard_lib=True)
+else:
+ STANDLIB_PATH = sysconfig.get_path('stdlib')
+
+from .utils import (
+ IgnoreElemType,
+ IgnoreType,
+ MaybeDecoratedFunctionWarning,
+ cached_getmodule,
+ attach_ignore_id_to_module,
+ frame_matches_module_by_ignore_id,
+ check_qualname_by_source,
+ debug_ignore_frame,
+)
+
+
+class IgnoreElem(ABC):DOCS
+ """An element of the ignore list"""
+
+ def __init_subclass__(cls, attrs: List[str]) -> None:DOCS
+ """Define different attributes for subclasses"""
+
+ def subclass_init(
+ self,
+ # IgnoreModule: ModuleType
+ # IgnoreFilename/IgnoreDirname: str
+ # IgnoreFunction: FunctionType
+ # IgnoreDecorated: FunctionType, int
+ # IgnoreModuleQualname/IgnoreFilenameQualname:
+ # ModuleType/str, str
+ # IgnoreOnlyQualname: None, str
+ *ign_args: Union[str, int, ModuleType, FunctionType],
+ ) -> None:
+ """__init__ function for subclasses"""
+ for attr, arg in zip(attrs, ign_args):
+ setattr(self, attr, arg)
+
+ self._post_init()
+
+ # save it for __repr__
+ cls.attrs = attrs
+ cls.__init__ = subclass_init # type: ignore
+
+ def _post_init(self) -> None:
+ """Setups after __init__"""
+
+ @abstractmethodDOCS
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ """Whether the frame matches the ignore element"""
+
+ def __repr__(self) -> str:DOCS
+ """Representation of the element"""
+ attr_values = (getattr(self, attr) for attr in self.__class__.attrs)
+ # get __name__ if possible
+ attr_values = (
+ repr(getattr(attr_value, "__name__", attr_value))
+ for attr_value in attr_values
+ )
+ attr_values = ", ".join(attr_values)
+ return f"{self.__class__.__name__}({attr_values})"
+
+
+class IgnoreModule(IgnoreElem, attrs=["module"]):DOCS
+ """Ignore calls from a module or its submodules"""
+
+ def _post_init(self) -> None:
+ attach_ignore_id_to_module(self.module)
+
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ frame = frameinfos[frame_no].frame
+ module = cached_getmodule(frame.f_code)
+ if module:
+ return (
+ module.__name__ == self.module.__name__
+ or module.__name__.startswith(f"{self.module.__name__}.")
+ )
+
+ return frame_matches_module_by_ignore_id(frame, self.module)
+
+
+class IgnoreFilename(IgnoreElem, attrs=["filename"]):
+ """Ignore calls from a module by matching its filename"""
+
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ frame = frameinfos[frame_no].frame
+
+ # in case of symbolic links
+ return path.realpath(frame.f_code.co_filename) == path.realpath(
+ self.filename
+ )
+
+
+class IgnoreDirname(IgnoreElem, attrs=["dirname"]):
+ """Ignore calls from modules inside a directory
+
+ Currently used internally to ignore calls from standard libraries."""
+
+ def _post_init(self) -> None:
+
+ # Path object will turn into str here
+ self.dirname = path.realpath(self.dirname) # type: str
+
+ if not self.dirname.endswith(path.sep):
+ self.dirname = f"{self.dirname}{path.sep}"
+
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ frame = frameinfos[frame_no].frame
+ filename = path.realpath(frame.f_code.co_filename)
+
+ return filename.startswith(self.dirname)
+
+
+class IgnoreStdlib(IgnoreDirname, attrs=["dirname"]):
+ """Ignore standard libraries in sysconfig.get_python_lib(standard_lib=True)
+
+ But we need to ignore 3rd-party packages under site-packages/.
+ """
+
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ frame = frameinfos[frame_no].frame
+ third_party_lib = f"{self.dirname}site-packages{path.sep}"
+ filename = path.realpath(frame.f_code.co_filename)
+
+ return (
+ filename.startswith(self.dirname)
+ # Exclude 3rd-party libraries in site-packages
+ and not filename.startswith(third_party_lib)
+ )
+
+
+class IgnoreFunction(IgnoreElem, attrs=["func"]):
+ """Ignore a non-decorated function"""
+
+ def _post_init(self) -> None:
+ if (
+ # without functools.wraps
+ "<locals>" in self.func.__qualname__
+ or self.func.__name__ != self.func.__code__.co_name
+ ):
+ warnings.warn(
+ f"You asked varname to ignore function {self.func.__name__!r}, "
+ "which may be decorated. If it is not intended, you may need "
+ "to ignore all intermediate frames with a tuple of "
+ "the function and the number of its decorators.",
+ MaybeDecoratedFunctionWarning,
+ )
+
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ frame = frameinfos[frame_no].frame
+ return frame.f_code == self.func.__code__
+
+
+class IgnoreDecorated(IgnoreElem, attrs=["func", "n_decor"]):
+ """Ignore a decorated function"""
+
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ try:
+ frame = frameinfos[frame_no + self.n_decor].frame
+ except IndexError:
+ return False
+
+ return frame.f_code == self.func.__code__
+
+
+class IgnoreModuleQualname(IgnoreElem, attrs=["module", "qualname"]):
+ """Ignore calls by qualified name in the module"""
+
+ def _post_init(self) -> None:
+
+ attach_ignore_id_to_module(self.module)
+ # check uniqueness of qualname
+ modfile = getattr(self.module, "__file__", None)
+ if modfile is not None:
+ check_qualname_by_source(
+ Source.for_filename(modfile, self.module.__dict__),
+ self.module.__name__,
+ self.qualname,
+ )
+
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ frame = frameinfos[frame_no].frame
+ module = cached_getmodule(frame.f_code)
+
+ # Return earlier to avoid qualname uniqueness check
+ if module and module != self.module:
+ return False
+
+ if not module and not frame_matches_module_by_ignore_id(
+ frame, self.module
+ ):
+ return False
+
+ source = Source.for_frame(frame)
+ check_qualname_by_source(source, self.module.__name__, self.qualname)
+
+ return fnmatch(source.code_qualname(frame.f_code), self.qualname)
+
+
+class IgnoreFilenameQualname(IgnoreElem, attrs=["filename", "qualname"]):
+ """Ignore calls with given qualname in the module with the filename"""
+
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ frame = frameinfos[frame_no].frame
+
+ frame_filename = path.realpath(frame.f_code.co_filename)
+ preset_filename = path.realpath(self.filename)
+ # return earlier to avoid qualname uniqueness check
+ if frame_filename != preset_filename:
+ return False
+
+ source = Source.for_frame(frame)
+ check_qualname_by_source(source, self.filename, self.qualname)
+
+ return fnmatch(source.code_qualname(frame.f_code), self.qualname)
+
+
+class IgnoreOnlyQualname(IgnoreElem, attrs=["_none", "qualname"]):
+ """Ignore calls that match the given qualname, across all frames."""
+
+ def match(self, frame_no: int, frameinfos: List[inspect.FrameInfo]) -> bool:
+ frame = frameinfos[frame_no].frame
+
+ # module is None, check qualname only
+ return fnmatch(
+ Source.for_frame(frame).code_qualname(frame.f_code), self.qualname
+ )
+
+
+def create_ignore_elem(ignore_elem: IgnoreElemType) -> IgnoreElem:
+ """Create an ignore element according to the type"""
+ if isinstance(ignore_elem, ModuleType):
+ return IgnoreModule(ignore_elem) # type: ignore
+ if isinstance(ignore_elem, (Path, str)):
+ return (
+ IgnoreDirname(ignore_elem) # type: ignore
+ if path.isdir(ignore_elem)
+ else IgnoreFilename(ignore_elem) # type: ignore
+ )
+ if hasattr(ignore_elem, "__code__"):
+ return IgnoreFunction(ignore_elem) # type: ignore
+ if not isinstance(ignore_elem, tuple) or len(ignore_elem) != 2:
+ raise ValueError(f"Unexpected ignore item: {ignore_elem!r}")
+ # is tuple and len == 2
+ if hasattr(ignore_elem[0], "__code__") and isinstance(ignore_elem[1], int):
+ return IgnoreDecorated(*ignore_elem) # type: ignore
+ # otherwise, the second element should be qualname
+ if not isinstance(ignore_elem[1], str):
+ raise ValueError(f"Unexpected ignore item: {ignore_elem!r}")
+
+ if isinstance(ignore_elem[0], ModuleType):
+ return IgnoreModuleQualname(*ignore_elem) # type: ignore
+ if isinstance(ignore_elem[0], (Path, str)):
+ return IgnoreFilenameQualname(*ignore_elem) # type: ignore
+ if ignore_elem[0] is None:
+ return IgnoreOnlyQualname(*ignore_elem)
+
+ raise ValueError(f"Unexpected ignore item: {ignore_elem!r}")
+
+
+class IgnoreList:
+ """The ignore list to match the frames to see if they should be ignored"""
+
+ @classmethod
+ def create(
+ cls,
+ ignore: IgnoreType = None,
+ ignore_lambda: bool = True,
+ ignore_varname: bool = True,
+ ) -> "IgnoreList":
+ """Create an IgnoreList object
+
+ Args:
+ ignore: An element of the ignore list, either
+ A module (or filename of a module)
+ A tuple of module (or filename) and qualified name
+ A function
+ A tuple of function and number of decorators
+ ignore_lambda: whether ignore lambda functions
+ ignore_varname: whether the calls from this package
+
+ Returns:
+ The IgnoreList object
+ """
+ ignore = ignore or []
+ if not isinstance(ignore, list):
+ ignore = [ignore]
+
+ ignore_list = [
+ IgnoreStdlib(STANDLIB_PATH) # type: ignore
+ ] # type: List[IgnoreElem]
+ if ignore_varname:
+ ignore_list.append(create_ignore_elem(sys.modules[__package__]))
+ if ignore_lambda:
+ ignore_list.append(create_ignore_elem((None, "*<lambda>")))
+ for ignore_elem in ignore:
+ ignore_list.append(create_ignore_elem(ignore_elem))
+
+ return cls(ignore_list) # type: ignore
+
+ def __init__(self, ignore_list: List[IgnoreElemType]) -> None:
+ self.ignore_list = ignore_list
+ debug_ignore_frame(">>> IgnoreList initiated <<<")
+
+ def nextframe_to_check(
+ self, frame_no: int, frameinfos: List[inspect.FrameInfo]
+ ) -> int:
+ """Find the next frame to check
+
+ In modst cases, the next frame to check is the next adjacent frame.
+ But for IgnoreDecorated, the next frame to check should be the next
+ `ignore[1]`th frame.
+
+ Args:
+ frame_no: The index of current frame to check
+ frameinfos: The frame info objects
+
+ Returns:
+ A number for Next `N`th frame to check. 0 if no frame matched.
+ """
+ for ignore_elem in self.ignore_list:
+ matched = ignore_elem.match(frame_no, frameinfos) # type: ignore
+ if matched and isinstance(ignore_elem, IgnoreDecorated):
+ debug_ignore_frame(
+ f"Ignored by {ignore_elem!r}", frameinfos[frame_no]
+ )
+ return ignore_elem.n_decor + 1
+
+ if matched:
+ debug_ignore_frame(
+ f"Ignored by {ignore_elem!r}", frameinfos[frame_no]
+ )
+ return 1
+ return 0
+
+ def get_frame(self, frame_no: int) -> FrameType:
+ """Get the right frame by the frame number
+
+ Args:
+ frame_no: The index of the frame to get
+
+ Returns:
+ The desired frame
+
+ Raises:
+ VarnameRetrievingError: if any exceptions raised during the process.
+ """
+ try:
+ # since this function will be called by APIs
+ # so we should skip that
+ frames = inspect.getouterframes(sys._getframe(2), 0)
+ i = 0
+
+ while i < len(frames):
+ nextframe = self.nextframe_to_check(i, frames)
+ # ignored
+ if nextframe > 0:
+ i += nextframe
+ continue
+
+ frame_no -= 1
+ if frame_no == 0:
+ debug_ignore_frame("Gotcha!", frames[i])
+ return frames[i].frame
+
+ debug_ignore_frame(
+ f"Skipping ({frame_no - 1} more to skip)", frames[i]
+ )
+ i += 1
+
+ except Exception as exc:
+ from .utils import VarnameRetrievingError
+
+ raise VarnameRetrievingError from exc
+
+ return None # pragma: no cover
+
"""Some internal utilities for varname
+
+
+Attributes:
+
+ IgnoreElemType: The type for ignore elements
+ IgnoreType: The type for the ignore argument
+ MODULE_IGNORE_ID_NAME: The name of the ignore id injected to the module.
+ Espectially for modules that can't be retrieved by
+ `inspect.getmodule(frame)`
+"""
+import sys
+import dis
+import ast
+import warnings
+import inspect
+from os import path
+from pathlib import Path
+from functools import lru_cache, singledispatch
+from types import ModuleType, FunctionType, CodeType, FrameType
+from typing import Tuple, Union, List, Mapping, Callable, Dict
+
+from executing import Source
+
+OP2MAGIC = {
+ ast.Add: "__add__",
+ ast.Sub: "__sub__",
+ ast.Mult: "__mul__",
+ ast.Div: "__truediv__",
+ ast.FloorDiv: "__floordiv__",
+ ast.Mod: "__mod__",
+ ast.Pow: "__pow__",
+ ast.LShift: "__lshift__",
+ ast.RShift: "__rshift__",
+ ast.BitOr: "__or__",
+ ast.BitXor: "__xor__",
+ ast.BitAnd: "__and__",
+ ast.MatMult: "__matmul__",
+}
+
+CMP2MAGIC = {
+ ast.Eq: "__eq__",
+ ast.NotEq: "__ne__",
+ ast.Lt: "__lt__",
+ ast.LtE: "__le__",
+ ast.Gt: "__gt__",
+ ast.GtE: "__ge__",
+}
+
+IgnoreElemType = Union[
+ # module
+ ModuleType,
+ # filename of a module
+ str,
+ Path,
+ FunctionType,
+ # the module (filename) and qualname
+ # If module is None, then all qualname matches the 2nd element
+ # will be ignored. Used to ignore <lambda> internally
+ Tuple[Union[ModuleType, str], str],
+ # Function and number of its decorators
+ Tuple[FunctionType, int],
+]
+IgnoreType = Union[IgnoreElemType, List[IgnoreElemType]]
+
+ArgSourceType = Union[ast.AST, str]
+ArgSourceType = Union[ArgSourceType, Tuple[ArgSourceType, ...]]
+ArgSourceType = Union[ArgSourceType, Mapping[str, ArgSourceType]]
+
+if sys.version_info >= (3, 8):
+ ASSIGN_TYPES = (ast.Assign, ast.AnnAssign, ast.NamedExpr)
+ AssignType = Union[ASSIGN_TYPES] # type: ignore
+else: # pragma: no cover
+ ASSIGN_TYPES = (ast.Assign, ast.AnnAssign)
+ AssignType = Union[ASSIGN_TYPES] # type: ignore
+
+PY311 = sys.version_info >= (3, 11)
+MODULE_IGNORE_ID_NAME = "__varname_ignore_id__"
+
+
+class config:DOCS
+ """Global configurations for varname
+
+ Attributes:
+ debug: Show debug information for frames being ignored
+ """
+
+ debug = False
+
+
+class VarnameException(Exception):DOCS
+ """Root exception for all varname exceptions"""
+
+
+class VarnameRetrievingError(VarnameException):DOCS
+ """When failed to retrieve the varname"""
+
+
+class QualnameNonUniqueError(VarnameException):DOCS
+ """When a qualified name is used as an ignore element but references to
+ multiple objects in a module"""
+
+
+class ImproperUseError(VarnameException):DOCS
+ """When varname() is improperly used"""
+
+
+class VarnameWarning(Warning):DOCS
+ """Root warning for all varname warnings"""
+
+
+class MaybeDecoratedFunctionWarning(VarnameWarning):DOCS
+ """When a suspecious decorated function used as ignore function directly"""
+
+
+class MultiTargetAssignmentWarning(VarnameWarning):DOCS
+ """When varname tries to retrieve variable name in
+ a multi-target assignment"""
+
+
+class UsingExecWarning(VarnameWarning):DOCS
+ """When exec is used to retrieve function name for `argname()`"""
+
+
+@lru_cache()DOCS
+def cached_getmodule(codeobj: CodeType):
+ """Cached version of inspect.getmodule"""
+ return inspect.getmodule(codeobj)
+
+
+def get_node(DOCS
+ frame: int,
+ ignore: IgnoreType = None,
+ raise_exc: bool = True,
+ ignore_lambda: bool = True,
+) -> ast.AST:
+ """Try to get node from the executing object.
+
+ This can fail when a frame is failed to retrieve.
+ One case should be when python code is executed in
+ R pacakge `reticulate`, where only first frame is kept.
+
+ When the node can not be retrieved, try to return the first statement.
+ """
+ from .ignore import IgnoreList
+
+ ignore = IgnoreList.create(ignore, ignore_lambda=ignore_lambda)
+ try:
+ frameobj = ignore.get_frame(frame)
+ except VarnameRetrievingError:
+ return None
+
+ return get_node_by_frame(frameobj, raise_exc)
+
+
+def get_node_by_frame(frame: FrameType, raise_exc: bool = True) -> ast.AST:DOCS
+ """Get the node by frame, raise errors if possible"""
+ exect = Source.executing(frame)
+
+ if exect.node:
+ # attach the frame for better exception message
+ # (ie. where ImproperUseError happens)
+ exect.node.__frame__ = frame
+ return exect.node
+
+ if exect.source.text and exect.source.tree and raise_exc:
+ raise VarnameRetrievingError(
+ "Couldn't retrieve the call node. "
+ "This may happen if you're using some other AST magic at the "
+ "same time, such as pytest, ipython, macropy, or birdseye."
+ )
+
+ return None
+
+
+def lookfor_parent_assign(node: ast.AST, strict: bool = True) -> AssignType:DOCS
+ """Look for an ast.Assign node in the parents"""
+ while hasattr(node, "parent"):
+ node = node.parent
+
+ if isinstance(node, ASSIGN_TYPES):
+ return node
+
+ if strict:
+ break
+ return None
+
+
+def node_name(DOCS
+ node: ast.AST,
+ subscript_slice: bool = False,
+) -> Union[str, Tuple[Union[str, Tuple], ...]]:
+ """Get the node node name.
+
+ Raises ImproperUseError when failed
+ """
+ if isinstance(node, ast.Name):
+ return node.id
+ if isinstance(node, ast.Attribute):
+ return f"{node_name(node.value)}.{node.attr}"
+ if isinstance(node, ast.Constant):
+ return repr(node.value)
+ if isinstance(node, (ast.List, ast.Tuple)) and not subscript_slice:
+ return tuple(node_name(elem) for elem in node.elts)
+ if isinstance(node, ast.List):
+ return f"[{', '.join(node_name(elem) for elem in node.elts)}]" # type: ignore
+ if isinstance(node, ast.Tuple):
+ if len(node.elts) == 1:
+ return f"({node_name(node.elts[0])},)"
+ return f"({', '.join(node_name(elem) for elem in node.elts)})" # type: ignore
+ if isinstance(node, ast.Starred):
+ return f"*{node_name(node.value)}"
+ if isinstance(node, ast.Slice):
+ return (
+ f"{node_name(node.lower)}:{node_name(node.upper)}:{node_name(node.step)}"
+ if node.lower is not None
+ and node.upper is not None
+ and node.step is not None
+ else f"{node_name(node.lower)}:{node_name(node.upper)}"
+ if node.lower is not None and node.upper is not None
+ else f"{node_name(node.lower)}:"
+ if node.lower is not None
+ else f":{node_name(node.upper)}"
+ if node.upper is not None
+ else ":"
+ )
+
+ name = type(node).__name__
+ if isinstance(node, ast.Subscript):
+ try:
+ return f"{node_name(node.value)}[{node_name(node.slice, True)}]"
+ except ImproperUseError:
+ name = f"{node_name(node.value)}[{type(node.slice).__name__}]"
+
+ raise ImproperUseError(
+ f"Node {name!r} detected, but only following nodes are supported: \n"
+ " - ast.Name (e.g. x)\n"
+ " - ast.Attribute (e.g. x.y, x be other supported nodes)\n"
+ " - ast.Constant (e.g. 1, 'a')\n"
+ " - ast.List (e.g. [x, y, z])\n"
+ " - ast.Tuple (e.g. (x, y, z))\n"
+ " - ast.Starred (e.g. *x)\n"
+ " - ast.Subscript with slice of the above nodes (e.g. x[y])"
+ )
+
+
+@lru_cache()DOCS
+def bytecode_nameof(code: CodeType, offset: int) -> str:
+ """Cached Bytecode version of nameof
+
+ We are trying this version only when the sourcecode is unavisible. In most
+ cases, this will happen when user is trying to run a script in REPL/
+ python shell, with `eval`, or other circumstances where the code is
+ manipulated to run but sourcecode is not available.
+ """
+ kwargs: Dict[str, bool] = (
+ {"show_caches": True} if sys.version_info[:2] >= (3, 11) else {}
+ )
+
+ instructions = list(dis.get_instructions(code, **kwargs))
+ ((current_instruction_index, current_instruction),) = (
+ (index, instruction)
+ for index, instruction in enumerate(instructions)
+ if instruction.offset == offset
+ )
+
+ while current_instruction.opname == "CACHE": # pragma: no cover
+ current_instruction_index -= 1
+ current_instruction = instructions[current_instruction_index]
+
+ pos_only_error = VarnameRetrievingError(
+ "'nameof' can only be called with a single positional argument "
+ "when source code is not avaiable."
+ )
+ if current_instruction.opname in ( # pragma: no cover
+ "CALL_FUNCTION_EX",
+ "CALL_FUNCTION_KW",
+ ):
+ raise pos_only_error
+
+ if current_instruction.opname not in (
+ "CALL_FUNCTION",
+ "CALL_METHOD",
+ "CALL",
+ "CALL_KW",
+ ):
+ raise VarnameRetrievingError("Did you call 'nameof' in a weird way?")
+
+ current_instruction_index -= 1
+ name_instruction = instructions[current_instruction_index]
+ while name_instruction.opname in ("CACHE", "PRECALL"): # pragma: no cover
+ current_instruction_index -= 1
+ name_instruction = instructions[current_instruction_index]
+
+ if name_instruction.opname in ("KW_NAMES", "LOAD_CONST"): # LOAD_CONST python 3.13
+ raise pos_only_error
+
+ if not name_instruction.opname.startswith("LOAD_"):
+ raise VarnameRetrievingError("Argument must be a variable or attribute")
+
+ name = name_instruction.argrepr
+ if not name.isidentifier():
+ raise VarnameRetrievingError(
+ f"Found the variable name {name!r} which is obviously wrong. "
+ "This may happen if you're using some other AST magic at the "
+ "same time, such as pytest, ipython, macropy, or birdseye."
+ )
+
+ return name
+
+
+def attach_ignore_id_to_module(module: ModuleType) -> None:DOCS
+ """Attach the ignore id to module
+
+ This is useful when a module cannot be retrieved by frames using
+ `inspect.getmodule`, then we can use this id, which will exist in
+ `frame.f_globals` to check if the module matches in ignore.
+
+ Do it only when the __file__ is not avaiable or does not exist for
+ the module. Since this probably means the source is not avaiable and
+ `inspect.getmodule` would not work
+ """
+ module_file = getattr(module, "__file__", None)
+ if module_file is not None and path.isfile(module_file):
+ return
+ # or it's already been set
+ if hasattr(module, MODULE_IGNORE_ID_NAME):
+ return
+
+ setattr(module, MODULE_IGNORE_ID_NAME, f"<varname-ignore-{id(module)})")
+
+
+def frame_matches_module_by_ignore_id(DOCS
+ frame: FrameType, module: ModuleType
+) -> bool:
+ """Check if the frame is from the module by ignore id"""
+ ignore_id_attached = getattr(module, MODULE_IGNORE_ID_NAME, object())
+ ignore_id_from_frame = frame.f_globals.get(MODULE_IGNORE_ID_NAME, object())
+ return ignore_id_attached == ignore_id_from_frame
+
+
+@lru_cache()DOCS
+def check_qualname_by_source(
+ source: Source, modname: str, qualname: str
+) -> None:
+ """Check if a qualname in module is unique"""
+ if not source.tree:
+ # no way to check it, skip
+ return
+ nobj = list(source._qualnames.values()).count(qualname)
+ if nobj > 1:
+ raise QualnameNonUniqueError(
+ f"Qualname {qualname!r} in "
+ f"{modname!r} refers to multiple objects."
+ )
+
+
+def debug_ignore_frame(msg: str, frameinfo: inspect.FrameInfo = None) -> None:DOCS
+ """Print the debug message for a given frame info object
+
+ Args:
+ msg: The debugging message
+ frameinfo: The FrameInfo object for the frame
+ """
+ if not config.debug:
+ return
+ if frameinfo is not None:
+ msg = (
+ f"{msg} [In {frameinfo.function!r} at "
+ f"{frameinfo.filename}:{frameinfo.lineno}]"
+ )
+ sys.stderr.write(f"[{__package__}] DEBUG: {msg}\n")
+
+
+def argnode_source(DOCS
+ source: Source, node: ast.AST, vars_only: bool
+) -> Union[str, ast.AST]:
+ """Get the source of an argument node
+
+ Args:
+ source: The executing source object
+ node: The node to get the source from
+ vars_only: Whether only allow variables and attributes
+
+ Returns:
+ The source of the node (node.id for ast.Name,
+ node.attr for ast.Attribute). Or the node itself if the source
+ cannot be fetched.
+ """
+ if isinstance(node, ast.Constant):
+ return repr(node.value)
+
+ if sys.version_info < (3, 9): # pragma: no cover
+ if isinstance(node, ast.Index):
+ node = node.value
+ if isinstance(node, ast.Num):
+ return repr(node.n)
+ if isinstance(node, (ast.Bytes, ast.Str)):
+ return repr(node.s)
+ if isinstance(node, ast.NameConstant):
+ return repr(node.value)
+
+ if vars_only:
+ return (
+ node.id
+ if isinstance(node, ast.Name)
+ else node.attr
+ if isinstance(node, ast.Attribute)
+ else node
+ )
+
+ # requires asttokens
+ return source.asttokens().get_text(node)
+
+
+def get_argument_sources(DOCS
+ source: Source,
+ node: ast.Call,
+ func: Callable,
+ vars_only: bool,
+) -> Mapping[str, ArgSourceType]:
+ """Get the sources for argument from an ast.Call node
+
+ >>> def func(a, b, c, d=4):
+ >>> ...
+ >>> x = y = z = 1
+ >>> func(y, x, c=z)
+ >>> # argument_sources = {'a': 'y', 'b', 'x', 'c': 'z'}
+ >>> func(y, x, c=1)
+ >>> # argument_sources = {'a': 'y', 'b', 'x', 'c': ast.Num(n=1)}
+ """
+ # <Signature (a, b, c, d=4)>
+ signature = inspect.signature(func, follow_wrapped=False)
+ # func(y, x, c=z)
+ # ['y', 'x'], {'c': 'z'}
+ arg_sources = [
+ argnode_source(source, argnode, vars_only) for argnode in node.args
+ ]
+ kwarg_sources = {
+ argnode.arg: argnode_source(source, argnode.value, vars_only)
+ for argnode in node.keywords
+ if argnode.arg is not None
+ }
+ bound_args = signature.bind_partial(*arg_sources, **kwarg_sources)
+ argument_sources = bound_args.arguments
+ # see if *args and **kwargs have anything assigned
+ # if not, assign () and {} to them
+ for parameter in signature.parameters.values():
+ if parameter.kind == inspect.Parameter.VAR_POSITIONAL:
+ argument_sources.setdefault(parameter.name, ())
+ if parameter.kind == inspect.Parameter.VAR_KEYWORD:
+ argument_sources.setdefault(parameter.name, {})
+ return argument_sources
+
+
+def get_function_called_argname(frame: FrameType, node: ast.Call) -> Callable:DOCS
+ """Get the function who called argname"""
+ # variable
+ if isinstance(node.func, ast.Name):
+ func = frame.f_locals.get(
+ node.func.id, frame.f_globals.get(node.func.id)
+ )
+ if func is None: # pragma: no cover
+ # not sure how it would happen but in case
+ raise VarnameRetrievingError(
+ f"Cannot retrieve the function by {node.func.id!r}."
+ )
+ return func
+
+ # use pure_eval
+ pure_eval_fail_msg = None
+ try:
+ from pure_eval import Evaluator, CannotEval
+ except ImportError:
+ pure_eval_fail_msg = "'pure_eval' is not installed."
+ else:
+ try:
+ evaluator = Evaluator.from_frame(frame)
+ return evaluator[node.func]
+ except CannotEval:
+ pure_eval_fail_msg = (
+ f"Cannot evaluate node {ast.dump(node.func)} "
+ "using 'pure_eval'."
+ )
+
+ # try eval
+ warnings.warn(
+ f"{pure_eval_fail_msg} "
+ "Using 'eval' to get the function that calls 'argname'. "
+ "Try calling it using a variable reference to the function, or "
+ "passing the function to 'argname' explicitly.",
+ UsingExecWarning,
+ )
+ expr = ast.Expression(node.func)
+ code = compile(expr, "<ast-call>", "eval")
+ return eval(code, frame.f_globals, frame.f_locals)
+
+
+@singledispatchDOCS
+def reconstruct_func_node(node: ast.AST) -> ast.Call:
+ """Reconstruct the ast.Call node from
+
+ `x.a` to `x.__getattr__('a')`
+ `x.a = b` to `x.__setattr__('a', b)`
+ `x[a]` to `x.__getitem__(a)`
+ `x[a] = b` to `x.__setitem__(a, 1)`
+ `x + a` to `x.__add__(a)`
+ `x < a` to `x.__lt__(a)`
+ """
+ raise VarnameRetrievingError(
+ f"Cannot reconstruct ast.Call node from {type(node).__name__}, "
+ "expecting Call, Attribute, Subscript, BinOp, Compare."
+ )
+
+
+@reconstruct_func_node.register(ast.Call)
+def _(node: ast.Call) -> ast.Call:
+ return node
+
+
+@reconstruct_func_node.register(ast.Attribute)
+@reconstruct_func_node.register(ast.Subscript)
+def _(node: Union[ast.Attribute, ast.Subscript]) -> ast.Call:
+ """Reconstruct the function node for
+ `x.__getitem__/__setitem__/__getattr__/__setattr__`"""
+ nodemeta = {
+ "lineno": node.lineno,
+ "col_offset": node.col_offset,
+ }
+ keynode = (
+ node.slice
+ if isinstance(node, ast.Subscript)
+ else ast.Constant(value=node.attr)
+ )
+
+ # x[1], x.a
+ if isinstance(node.ctx, ast.Load):
+ if PY311:
+ return ast.Call(
+ func=ast.Attribute(
+ value=node.value,
+ attr=(
+ "__getitem__"
+ if isinstance(node, ast.Subscript)
+ else "__getattr__"
+ ),
+ ctx=ast.Load(),
+ **nodemeta,
+ ),
+ args=[keynode],
+ keywords=[],
+ )
+ else:
+ return ast.Call( # type: ignore
+ func=ast.Attribute(
+ value=node.value,
+ attr=(
+ "__getitem__"
+ if isinstance(node, ast.Subscript)
+ else "__getattr__"
+ ),
+ ctx=ast.Load(),
+ **nodemeta,
+ ),
+ args=[keynode],
+ keywords=[],
+ starargs=None,
+ kwargs=None,
+ )
+
+ # x[a] = b, x.a = b
+ if (
+ not hasattr(node, "parent")
+ or not isinstance(node.parent, ast.Assign) # type: ignore
+ or len(node.parent.targets) != 1 # type: ignore
+ ):
+ raise ImproperUseError(
+ rich_exc_message(
+ "Expect `x[a] = b` or `x.a = b` directly, got "
+ f"{ast.dump(node)}.",
+ node,
+ )
+ )
+
+ if PY311:
+ return ast.Call(
+ func=ast.Attribute(
+ value=node.value,
+ attr=(
+ "__setitem__"
+ if isinstance(node, ast.Subscript)
+ else "__setattr__"
+ ),
+ ctx=ast.Load(),
+ **nodemeta,
+ ),
+ args=[keynode, node.parent.value], # type: ignore
+ keywords=[],
+ )
+ else:
+ return ast.Call(
+ func=ast.Attribute(
+ value=node.value,
+ attr=(
+ "__setitem__"
+ if isinstance(node, ast.Subscript)
+ else "__setattr__"
+ ),
+ ctx=ast.Load(),
+ **nodemeta,
+ ),
+ args=[keynode, node.parent.value], # type: ignore
+ keywords=[],
+ starargs=None,
+ kwargs=None,
+ )
+
+
+@reconstruct_func_node.register(ast.Compare)
+def _(node: ast.Compare) -> ast.Call:
+ """Reconstruct the function node for `x < a`"""
+ # When the node is identified by executing, len(ops) is always 1.
+ # Otherwise, the node cannot be identified.
+ assert len(node.ops) == 1
+
+ nodemeta = {
+ "lineno": node.lineno,
+ "col_offset": node.col_offset,
+ }
+ if PY311:
+ return ast.Call(
+ func=ast.Attribute(
+ value=node.left,
+ attr=CMP2MAGIC[type(node.ops[0])],
+ ctx=ast.Load(),
+ **nodemeta,
+ ),
+ args=[node.comparators[0]],
+ keywords=[],
+ )
+ else:
+ return ast.Call( # type: ignore
+ func=ast.Attribute(
+ value=node.left,
+ attr=CMP2MAGIC[type(node.ops[0])],
+ ctx=ast.Load(),
+ **nodemeta,
+ ),
+ args=[node.comparators[0]],
+ keywords=[],
+ starargs=None,
+ kwargs=None,
+ )
+
+
+@reconstruct_func_node.register(ast.BinOp)
+def _(node: ast.BinOp) -> ast.Call:
+ """Reconstruct the function node for `x + a`"""
+ nodemeta = {
+ "lineno": node.lineno,
+ "col_offset": node.col_offset,
+ }
+
+ if PY311:
+ return ast.Call(
+ func=ast.Attribute(
+ value=node.left,
+ attr=OP2MAGIC[type(node.op)],
+ ctx=ast.Load(),
+ **nodemeta,
+ ),
+ args=[node.right],
+ keywords=[],
+ )
+ else:
+ return ast.Call( # type: ignore
+ func=ast.Attribute(
+ value=node.left,
+ attr=OP2MAGIC[type(node.op)],
+ ctx=ast.Load(),
+ **nodemeta,
+ ),
+ args=[node.right],
+ keywords=[],
+ starargs=None,
+ kwargs=None,
+ )
+
+
+def rich_exc_message(msg: str, node: ast.AST, context_lines: int = 4) -> str:DOCS
+ """Attach the source code from the node to message to
+ get a rich message for exceptions
+
+ If package 'rich' is not install or 'node.__frame__' doesn't exist, fall
+ to plain message (with basic information), otherwise show a better message
+ with full information
+ """
+ frame = node.__frame__ # type: FrameType
+ lineno = node.lineno - 1 # type: int
+ col_offset = node.col_offset # type: int
+ filename = frame.f_code.co_filename # type: str
+ try:
+ lines, startlineno = inspect.getsourcelines(frame)
+ except OSError: # pragma: no cover
+ # could not get source code
+ return f"{msg}\n"
+ startlineno = 0 if startlineno == 0 else startlineno - 1
+ line_range = (startlineno + 1, startlineno + len(lines) + 1)
+
+ linenos = tuple(map(str, range(*line_range))) # type: Tuple[str, ...]
+ lineno_width = max(map(len, linenos)) # type: int
+ hiline = lineno - startlineno # type: int
+ codes = [] # type: List[str]
+ for i, lno in enumerate(linenos):
+ if i < hiline - context_lines or i > hiline + context_lines:
+ continue
+ lno = lno.ljust(lineno_width)
+ if i == hiline:
+ codes.append(f" > | {lno} {lines[i]}")
+ codes.append(f" | {' ' * (lineno_width + col_offset + 2)}^\n")
+ else:
+ codes.append(f" | {lno} {lines[i]}")
+
+ return (
+ f"{msg}\n\n"
+ f" {filename}:{lineno + 1}:{col_offset + 1}\n"
+ f"{''.join(codes)}\n"
+ )
+
"""Dark magics about variable names in python"""
+
+from .utils import (
+ config,
+ VarnameException,
+ VarnameRetrievingError,
+ ImproperUseError,
+ QualnameNonUniqueError,
+ VarnameWarning,
+ MultiTargetAssignmentWarning,
+ MaybeDecoratedFunctionWarning,
+ UsingExecWarning,
+)
+from .core import varname, nameof, will, argname
+
+__version__ = "0.13.3"
+
Provide core features for varname
argname
(
arg
, *more_args
, func
, dispatch
, frame
, ignore
, vars_only
)
+(Union(ast, str, (ast or str, ...), , (union(ast, str, (ast or str, ...), ), ...)))
+— Get the names/sources of arguments passed to a function.</>nameof
(
var
, *more_vars
, frame
, vars_only
)
+(Union(str, (str, ...)))
+— Get the names of the variables passed in</>varname
(
frame
, ignore
, multi_vars
, raise_exc
, strict
)
+(Union(str, (str or tuple, ...)))
+— Get the name of the variable(s) that assigned by function call orclass instantiation.
+</>will
(
frame
, raise_exc
)
+(str)
+— Detect the attribute name right immediately after a function call.</>varname.core.
varname
(
frame=1
, ignore=None
, multi_vars=False
, raise_exc=True
, strict=True
)
Get the name of the variable(s) that assigned by function call orclass instantiation.
+To debug and specify the right frame and ignore arguments, you can set +debug on and see how the frames are ignored or selected:
+>>> from varname import config
+>>> config.debug = True
+
frame
+(int, optional)
+— N
th frame used to retrieve the variable name. This meansN-1
intermediate frames will be skipped. Note that the frames
+match ignore
will not be counted. See ignore
for details.
+ignore
+(Union(module, str, path, function, (module or str, str), (function, int), list of union(module, str, path, function, (module or str, str), (function, int))), optional)
+— Frames to be ignored in order to reach the N
th frame.These frames will not be counted to skip within that N-1
frames.
+You can specify:
+MaybeDecoratedFunctionWarning
will be shown.varname
package, python standardlibraries and lambda functions are ignored.
+multi_vars
+(bool, optional)
+— Whether allow multiple variables on left-hand side (LHS).If True
, this function returns a tuple of the variable names,
+even there is only one variable on LHS.
+If False
, and multiple variables on LHS, a
+ImproperUseError
will be raised.
+raise_exc
+(bool, optional)
+— Whether we should raise an exception if failedto retrieve the ast node.
+Note that set this to False
will NOT supress the exception when
+the use of varname
is improper (i.e. multiple variables on
+LHS with multi_vars
is False
). See Raises/ImproperUseError
.
+strict
+(bool, optional)
+— Whether to only return the variable name(s) if the result ofthe call is assigned to it/them directly. For example, a = func()
+rather than a = [func()]
+The variable name, or None
when raise_exc
is False
and we failed to retrieve the ast node for the variable(s).
+A tuple or a hierarchy (tuple of tuples) of variable names
+ when multi_vars
is True
.
ImproperUseError
+
+— When the use of varname()
is improper, including:ast.Name
or ast.Attribute
node or not amulti_vars
is Falsestrict
is True, but the result is not assigned toraise_exc=False
will NOT suppress this exception.MultiTargetAssignmentWarning
+
+— When there are multiple targetin the assign node. (e.g: a = b = func()
, in such a case,
+a == 'b'
, may not be the case you want)
+VarnameRetrievingError
+
+— When we are unable to retrieve the ast nodefor the variable(s) and raise_exc
is set to True
.
+varname.core.
will
(
frame=1
, raise_exc=True
)
Detect the attribute name right immediately after a function call.
>>> class AwesomeClass:>>> def __init__(self):
+>>> self.will = None
+
>>> def permit(self):
+>>> self.will = will()
+>>> if self.will == 'do':
+>>> # let self handle do
+>>> return self
+>>> raise AttributeError(
+>>> 'Should do something with AwesomeClass object'
+>>> )
+
>>> def do(self):
+>>> if self.will != 'do':
+>>> raise AttributeError("You don't have permission to do")
+>>> return 'I am doing!'
+
>>> awesome = AwesomeClass()
+>>> # AttributeError: You don't have permission to do
+>>> awesome.do()
+>>> # AttributeError: Should do something with AwesomeClass object
+>>> awesome.permit()
+>>> awesome.permit().do() == 'I am doing!'
+
frame
+(int, optional)
+— At which frame this function is called.raise_exc
+(bool, optional)
+— Raise exception we failed to detect the ast nodeThis will NOT supress the ImproperUseError
+The attribute name right after the function call.None
if ast node cannot be retrieved and raise_exc
is False
ImproperUseError
+
+— When (the wraper of) this function is not calledinside a method/property of a class instance.
+Note that this exception will not be suppressed by raise_exc=False
+VarnameRetrievingError
+
+— When raise_exc
is True
and we failed todetect the attribute name (including not having one)
+varname.core.
nameof
(
var
, *more_vars
, frame=1
, vars_only=True
)
Get the names of the variables passed in
>>> a = 1>>> nameof(a) # 'a'
+
>>> b = 2
+>>> nameof(a, b) # ('a', 'b')
+
>>> x = lambda: None
+>>> x.y = 1
+>>> nameof(x.y, vars_only=False) # 'x.y'
+
Note
+This function works with the environments where source code is
+available, in other words, the callee's node can be retrieved by
+executing
. In some cases, for example, running code from python
+shell/REPL or from exec
/eval
, we try to fetch the variable name
+from the bytecode. This requires only a single variable name is passed
+to this function and no keyword arguments, meaning that getting full
+names of attribute calls are not supported in such cases.
var
+(any)
+— The variable to retrieve the name of*more_vars
+(any)
+— Other variables to retrieve the names offrame
+(int, optional)
+— The this function is called from the wrapper of it. frame=1
means no wrappers.
+Note that the calls from standard libraries are ignored.
+Also note that the wrapper has to have signature as this one.
+vars_only
+(bool, optional)
+— Whether only allow variables/attributes as arguments orany expressions. If False
, then the sources of the arguments
+will be returned.
+The names/sources of variables/expressions passed in. If a single argument is passed, return the name/source of it.
+ If multiple variables are passed, return a tuple of their
+ names/sources.
+ If the argument is an attribute (e.g. a.b
) and vars_only
is
+ True
, only "b"
will returned. Set vars_only
to False
to
+ get "a.b"
.
VarnameRetrievingError
+
+— When the callee's node cannot be retrieved ortrying to retrieve the full name of non attribute series calls.
+varname.core.
argname
(
arg
, *more_args
, func=None
, dispatch=None
, frame=1
, ignore=None
, vars_only=True
)
Get the names/sources of arguments passed to a function.
Instead of passing the argument variables themselves to this function
+(like argname()
does), you should pass their names instead.
arg
+(str)
+— and*more_args
+(str)
+— The names of the arguments that you want to retrievenames/sources of.
+You can also use subscripts to get parts of the results.++Star argument is also allowed:+++def func(args, *kwargs): + return argname('args[0]', 'kwargs[x]') # no quote needed+
+
++Note the difference:+++def func(args, x = 1): + return argname('args', 'x') +a = b = c = 1 +func(a, b, x=c) # ('a', 'b', 'c')+
+
+++++def func(*args, x = 1): + return argname('args', 'x') +a = b = c = 1 +func(a, b, x=c) # (('a', 'b'), 'c')+
+
func
+(optional)
+— The target function. If not provided, the AST node of thefunction call will be used to fetch the function:
+node.id
willlocals()
or globals()
.pure_eval
will be used to evaluatepure_eval
is not installed or failed to evaluate, eval
dispatch
+(optional)
+— If a function is a single-dispatched function, you canspecify a type for it to dispatch the real function. If this is
+specified, expect func
to be the generic function if provided.
+frame
+(int, optional)
+— The frame where target function is called from this call.Calls from python standard libraries are ignored.
+ignore
+(Union(module, str, path, function, (module or str, str), (function, int), list of union(module, str, path, function, (module or str, str), (function, int))), optional)
+— The intermediate calls to be ignored. See varname.ignore
vars_only
+(bool, optional)
+— Require the arguments to be variables only.If False, asttokens
is required to retrieve the source.
+The argument source when no more_args passed, otherwise a tuple ofargument sources
+Note that when an argument is an ast.Constant
, repr(arg.value)
+is returned, so argname()
return 'a'
for func("a")
ImproperUseError
+
+— When frame or func is incorrectly specified.VarnameRetrievingError
+
+— When the ast node where the function is calledcannot be retrieved
+Some helper functions builtin based upon core features
debug
(
var
, *more_vars
, prefix
, merge
, repr
, sep
, vars_only
)
+
+— Print variable names and values.</>exec_code
(
code
, globals
, locals
, sourcefile
, frame
, ignore
, **kwargs
)
+
+— Execute code where source code is visible at runtime.</>jsobj
(
*args
, vars_only
, frame
, **kwargs
)
+(dict(str: any))
+— A wrapper to create a JavaScript-like object</>register
(
cls_or_func
, frame
, ignore
, multi_vars
, raise_exc
, strict
)
+( or )
+— A decorator to register varname to a class or function</>varname.helpers.
register
(
cls_or_func=None
, frame=1
, ignore=None
, multi_vars=False
, raise_exc=True
, strict=True
)
A decorator to register varname to a class or function
When registered to a class, it can be accessed by self.__varname__
;
+while to a function, it is registered to globals, meaning that it can be
+accessed directly.
frame
+(int, optional)
+— The call stack index, indicating where this classis instantiated relative to where the variable is finally retrieved
+multi_vars
+(bool, optional)
+— Whether allow multiple variables on left-hand side (LHS).If True
, this function returns a tuple of the variable names,
+even there is only one variable on LHS.
+If False
, and multiple variables on LHS, a
+VarnameRetrievingError
will be raised.
+raise_exc
+(bool, optional)
+— Whether we should raise an exception if failedto retrieve the name.
+strict
+(bool, optional)
+— Whether to only return the variable name if the result ofthe call is assigned to it directly.
+>>> @varname.register>>> class Foo: pass
+>>> foo = Foo()
+>>> # foo.__varname__ == 'foo'
+>>>
+>>> @varname.register
+>>> def func():
+>>> return __varname__
+>>> foo = func() # foo == 'foo'
+
The wrapper function or the class/function itselfif it is specified explictly.
+varname.helpers.
Wrapper
(
value
, frame=1
, ignore=None
, raise_exc=True
, strict=True
)
A wrapper with ability to retrieve the variable name
>>> foo = Wrapper(True)>>> # foo.name == 'foo'
+>>> # foo.value == True
+
>>> val = {}
+>>> bar = Wrapper(val)
+>>> # bar.name == 'bar'
+>>> # bar.value is val
+
value
+(any)
+— The value to be wrappedraise_exc
+(bool, optional)
+— Whether to raise exception when varname is failed to retrievestrict
+(bool, optional)
+— Whether to only return the variable name if the wrapper isassigned to it directly.
+name
+
+— The variable name to which the instance is assignedvalue
+
+— The value this wrapper wrapsvarname.helpers.
jsobj
(
*args
, vars_only=True
, frame=1
, **kwargs
)
A wrapper to create a JavaScript-like object
When an argument is passed as positional argument, the name of the variable +will be used as the key, while the value will be used as the value.
+>>> obj = jsobj(a=1, b=2)>>> # obj == {'a': 1, 'b': 2}
+>>> # obj.a == 1
+>>> # obj.b == 2
+>>> a = 1
+>>> b = 2
+>>> obj = jsobj(a, b, c=3)
+>>> # obj == {'a': 1, 'b': 2, 'c': 3}
+
*args
+(any)
+— The positional argumentsvars_only
+(bool, optional)
+— Whether to only include variables in the outputframe
+(int, optional)
+— The call stack index. You can understand this as the number ofwrappers around this function - 1.
+**kwargs
+(any)
+— The keyword argumentsA dict-like object
varname.helpers.
debug
(
var
, *more_vars
, prefix='DEBUG: '
, merge=False
, repr=True
, sep='='
, vars_only=False
)
Print variable names and values.
>>> a = 1>>> b = object
+>>> print(f'a={a}') # previously, we have to do
+>>> print(f'{a=}') # or with python3.8
+>>> # instead we can do:
+>>> debug(a) # DEBUG: a=1
+>>> debug(a, prefix='') # a=1
+>>> debug(a, b, merge=True) # a=1, b=<object object at 0x2b9a4c89cf00>
+
var
+
+— The variable to print*more_vars
+
+— Other variables to printprefix
+(str, optional)
+— A prefix to print for each linemerge
+(bool, optional)
+— Whether merge all variables in one line or notrepr
+(bool, optional)
+— Print the value as repr(var)
? otherwise str(var)
sep
+(str, optional)
+— The separator between the variable name and valuevarname.helpers.
exec_code
(
code
, globals=None
, locals=None
, sourcefile=None
, frame=1
, ignore=None
, **kwargs
)
Execute code where source code is visible at runtime.
This function is useful when you want to execute some code, where you want to +retrieve the AST node of the code at runtime. This function will create a +temporary file and write the code into it, then execute the code in the +file.
+>>> from varname import varname>>> def func(): return varname()
+>>> exec('var = func()') # VarnameRetrievingError:
+>>> # Unable to retrieve the ast node.
+>>> from varname.helpers import code_exec
+>>> code_exec('var = func()') # var == 'var'
+
code
+(str)
+— The code to execute.globals
+(dict(str: any), optional)
+— The globals to use.locals
+(dict(str: any), optional)
+— The locals to use.sourcefile
+(PathLike | str, optional)
+— The source file to write the code into.if not given, a temporary file will be used.
+This file will be deleted after the code is executed.
+frame
+(int, optional)
+— The call stack index. You can understand this as the number ofwrappers around this function. This is used to fetch globals
and
+locals
from where the destination function (include the wrappers
+of this function)
+is called.
+ignore
+(Union(module, str, path, function, (module or str, str), (function, int), list of union(module, str, path, function, (module or str, str), (function, int))), optional)
+— The intermediate calls to be ignored. See varname.ignore
Note that if both globals
and locals
are given, frame
and
+ignore
will be ignored.
+**kwargs
+(any)
+— The keyword arguments to pass to exec
.The frame ignoring system for varname
There 4 mechanisms to ignore intermediate frames to determine the desired one +so that a variable name should be retrieved at that frame.
+exec
without module available.Any frames in varname
, standard libraries, and frames of any expressions like
+
IgnoreElem
(
)
+
+— An element of the ignore list</>IgnoreModule
+
+— Ignore calls from a module or its submodules</>IgnoreFilename
+
+— Ignore calls from a module by matching its filename</>IgnoreDirname
+
+— Ignore calls from modules inside a directory</>IgnoreStdlib
+
+— Ignore standard libraries in sysconfig.get_python_lib(standard_lib=True)</>IgnoreFunction
+
+— Ignore a non-decorated function</>IgnoreDecorated
+
+— Ignore a decorated function</>IgnoreModuleQualname
+
+— Ignore calls by qualified name in the module</>IgnoreFilenameQualname
+
+— Ignore calls with given qualname in the module with the filename</>IgnoreOnlyQualname
+
+— Ignore calls that match the given qualname, across all frames.</>IgnoreList
+
+— The ignore list to match the frames to see if they should be ignored</>create_ignore_elem
(
ignore_elem
)
+(IgnoreElem)
+— Create an ignore element according to the type</>varname.ignore.
IgnoreElem
(
)
An element of the ignore list
__init_subclass__
(
attrs
)
Define different attributes for subclasses
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
__repr__
(
)
→ strRepresentation of the element
varname.ignore.
IgnoreModule
(
*ign_args
)
Ignore calls from a module or its submodules
__init_subclass__
(
attrs
)
+
+— Define different attributes for subclasses</>__repr__
(
)
+(str)
+— Representation of the element</>match
(
frame_no
, frameinfos
)
+(bool)
+— Whether the frame matches the ignore element</>subclass_init
(
*ign_args
)
+
+— init function for subclasses</>__init_subclass__
(
attrs
)
Define different attributes for subclasses
subclass_init
(
*ign_args
)
init function for subclasses
__repr__
(
)
→ strRepresentation of the element
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
varname.ignore.
IgnoreFilename
(
*ign_args
)
Ignore calls from a module by matching its filename
__init_subclass__
(
attrs
)
+
+— Define different attributes for subclasses</>__repr__
(
)
+(str)
+— Representation of the element</>match
(
frame_no
, frameinfos
)
+(bool)
+— Whether the frame matches the ignore element</>subclass_init
(
*ign_args
)
+
+— init function for subclasses</>__init_subclass__
(
attrs
)
Define different attributes for subclasses
subclass_init
(
*ign_args
)
init function for subclasses
__repr__
(
)
→ strRepresentation of the element
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
varname.ignore.
IgnoreDirname
(
*ign_args
)
Ignore calls from modules inside a directory
Currently used internally to ignore calls from standard libraries.
+__init_subclass__
(
attrs
)
+
+— Define different attributes for subclasses</>__repr__
(
)
+(str)
+— Representation of the element</>match
(
frame_no
, frameinfos
)
+(bool)
+— Whether the frame matches the ignore element</>subclass_init
(
*ign_args
)
+
+— init function for subclasses</>__init_subclass__
(
attrs
)
Define different attributes for subclasses
subclass_init
(
*ign_args
)
init function for subclasses
__repr__
(
)
→ strRepresentation of the element
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
varname.ignore.
IgnoreStdlib
(
*ign_args
)
Ignore standard libraries in sysconfig.get_python_lib(standard_lib=True)
But we need to ignore 3rd-party packages under site-packages/.
+__init_subclass__
(
attrs
)
+
+— Define different attributes for subclasses</>__repr__
(
)
+(str)
+— Representation of the element</>match
(
frame_no
, frameinfos
)
+(bool)
+— Whether the frame matches the ignore element</>subclass_init
(
*ign_args
)
+
+— init function for subclasses</>__init_subclass__
(
attrs
)
Define different attributes for subclasses
subclass_init
(
*ign_args
)
init function for subclasses
__repr__
(
)
→ strRepresentation of the element
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
varname.ignore.
IgnoreFunction
(
*ign_args
)
Ignore a non-decorated function
__init_subclass__
(
attrs
)
+
+— Define different attributes for subclasses</>__repr__
(
)
+(str)
+— Representation of the element</>match
(
frame_no
, frameinfos
)
+(bool)
+— Whether the frame matches the ignore element</>subclass_init
(
*ign_args
)
+
+— init function for subclasses</>__init_subclass__
(
attrs
)
Define different attributes for subclasses
subclass_init
(
*ign_args
)
init function for subclasses
__repr__
(
)
→ strRepresentation of the element
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
varname.ignore.
IgnoreDecorated
(
*ign_args
)
Ignore a decorated function
__init_subclass__
(
attrs
)
+
+— Define different attributes for subclasses</>__repr__
(
)
+(str)
+— Representation of the element</>match
(
frame_no
, frameinfos
)
+(bool)
+— Whether the frame matches the ignore element</>subclass_init
(
*ign_args
)
+
+— init function for subclasses</>__init_subclass__
(
attrs
)
Define different attributes for subclasses
subclass_init
(
*ign_args
)
init function for subclasses
__repr__
(
)
→ strRepresentation of the element
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
varname.ignore.
IgnoreModuleQualname
(
*ign_args
)
Ignore calls by qualified name in the module
__init_subclass__
(
attrs
)
+
+— Define different attributes for subclasses</>__repr__
(
)
+(str)
+— Representation of the element</>match
(
frame_no
, frameinfos
)
+(bool)
+— Whether the frame matches the ignore element</>subclass_init
(
*ign_args
)
+
+— init function for subclasses</>__init_subclass__
(
attrs
)
Define different attributes for subclasses
subclass_init
(
*ign_args
)
init function for subclasses
__repr__
(
)
→ strRepresentation of the element
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
varname.ignore.
IgnoreFilenameQualname
(
*ign_args
)
Ignore calls with given qualname in the module with the filename
__init_subclass__
(
attrs
)
+
+— Define different attributes for subclasses</>__repr__
(
)
+(str)
+— Representation of the element</>match
(
frame_no
, frameinfos
)
+(bool)
+— Whether the frame matches the ignore element</>subclass_init
(
*ign_args
)
+
+— init function for subclasses</>__init_subclass__
(
attrs
)
Define different attributes for subclasses
subclass_init
(
*ign_args
)
init function for subclasses
__repr__
(
)
→ strRepresentation of the element
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
varname.ignore.
IgnoreOnlyQualname
(
*ign_args
)
Ignore calls that match the given qualname, across all frames.
__init_subclass__
(
attrs
)
+
+— Define different attributes for subclasses</>__repr__
(
)
+(str)
+— Representation of the element</>match
(
frame_no
, frameinfos
)
+(bool)
+— Whether the frame matches the ignore element</>subclass_init
(
*ign_args
)
+
+— init function for subclasses</>__init_subclass__
(
attrs
)
Define different attributes for subclasses
subclass_init
(
*ign_args
)
init function for subclasses
__repr__
(
)
→ strRepresentation of the element
match
(
frame_no
, frameinfos
)
→ boolWhether the frame matches the ignore element
varname.ignore.
create_ignore_elem
(
ignore_elem
)
→ IgnoreElemCreate an ignore element according to the type
varname.ignore.
IgnoreList
(
ignore_list
)
The ignore list to match the frames to see if they should be ignored
create
(
ignore
, ignore_lambda
, ignore_varname
)
+(IgnoreList)
+— Create an IgnoreList object</>get_frame
(
frame_no
)
+(frame)
+— Get the right frame by the frame number</>nextframe_to_check
(
frame_no
, frameinfos
)
+(int)
+— Find the next frame to check</>create
(
ignore=None
, ignore_lambda=True
, ignore_varname=True
)
Create an IgnoreList object
ignore
+(Union(module, str, path, function, (module or str, str), (function, int), list of union(module, str, path, function, (module or str, str), (function, int))), optional)
+— An element of the ignore list, eitherA module (or filename of a module)
+A tuple of module (or filename) and qualified name
+A function
+A tuple of function and number of decorators
+ignore_lambda
+(bool, optional)
+— whether ignore lambda functionsignore_varname
+(bool, optional)
+— whether the calls from this packageThe IgnoreList object
nextframe_to_check
(
frame_no
, frameinfos
)
Find the next frame to check
In modst cases, the next frame to check is the next adjacent frame.
+But for IgnoreDecorated, the next frame to check should be the next
+ignore[1]
th frame.
frame_no
+(int)
+— The index of current frame to checkframeinfos
+(list of FrameInfo)
+— The frame info objectsA number for Next N
th frame to check. 0 if no frame matched.
get_frame
(
frame_no
)
Get the right frame by the frame number
frame_no
+(int)
+— The index of the frame to getThe desired frame
VarnameRetrievingError
+
+— if any exceptions raised during the process.Some internal utilities for varname
config
+
+— Global configurations for varname</>VarnameException
+
+— Root exception for all varname exceptions</>VarnameRetrievingError
+
+— When failed to retrieve the varname</>QualnameNonUniqueError
+
+— When a qualified name is used as an ignore element but references tomultiple objects in a module
+</>ImproperUseError
+
+— When varname() is improperly used</>VarnameWarning
+
+— Root warning for all varname warnings</>MaybeDecoratedFunctionWarning
+
+— When a suspecious decorated function used as ignore function directly</>MultiTargetAssignmentWarning
+
+— When varname tries to retrieve variable name ina multi-target assignment
+</>UsingExecWarning
+
+— When exec is used to retrieve function name for argname()
</>argnode_source
(
source
, node
, vars_only
)
+(str or AST)
+— Get the source of an argument node</>attach_ignore_id_to_module
(
module
)
+
+— Attach the ignore id to module</>bytecode_nameof
(
code
, offset
)
+(str)
+— Cached Bytecode version of nameof</>cached_getmodule
(
codeobj
)
+
+— Cached version of inspect.getmodule</>check_qualname_by_source
(
source
, modname
, qualname
)
+
+— Check if a qualname in module is unique</>debug_ignore_frame
(
msg
, frameinfo
)
+
+— Print the debug message for a given frame info object</>frame_matches_module_by_ignore_id
(
frame
, module
)
+(bool)
+— Check if the frame is from the module by ignore id</>get_argument_sources
(
source
, node
, func
, vars_only
)
+
+— Get the sources for argument from an ast.Call node</>get_function_called_argname
(
frame
, node
)
+
+— Get the function who called argname</>get_node
(
frame
, ignore
, raise_exc
, ignore_lambda
)
+(AST)
+— Try to get node from the executing object.</>get_node_by_frame
(
frame
, raise_exc
)
+(AST)
+— Get the node by frame, raise errors if possible</>lookfor_parent_assign
(
node
, strict
)
+(Assign, AnnAssign, or NamedExpr)
+— Look for an ast.Assign node in the parents</>node_name
(
node
, subscript_slice
)
+(Union(str, (str or tuple, ...)))
+— Get the node node name.</>reconstruct_func_node
(
node
)
+(Call)
+— Reconstruct the ast.Call node from</>rich_exc_message
(
msg
, node
, context_lines
)
+(str)
+— Attach the source code from the node to message toget a rich message for exceptions
+</>varname.utils.
config
(
)
Global configurations for varname
debug
+
+— Show debug information for frames being ignoredvarname.utils.
VarnameException
(
)
Root exception for all varname exceptions
varname.utils.
VarnameRetrievingError
(
)
When failed to retrieve the varname
varname.utils.
QualnameNonUniqueError
(
)
When a qualified name is used as an ignore element but references tomultiple objects in a module
+varname.utils.
ImproperUseError
(
)
When varname() is improperly used
varname.utils.
VarnameWarning
(
)
Root warning for all varname warnings
varname.utils.
MaybeDecoratedFunctionWarning
(
)
When a suspecious decorated function used as ignore function directly
varname.utils.
MultiTargetAssignmentWarning
(
)
When varname tries to retrieve variable name ina multi-target assignment
+varname.utils.
UsingExecWarning
(
)
When exec is used to retrieve function name for argname()
varname.utils.
cached_getmodule
(
codeobj
)
Cached version of inspect.getmodule
varname.utils.
get_node
(
frame
, ignore=None
, raise_exc=True
, ignore_lambda=True
)
→ ASTTry to get node from the executing object.
This can fail when a frame is failed to retrieve.
+One case should be when python code is executed in
+R pacakge reticulate
, where only first frame is kept.
When the node can not be retrieved, try to return the first statement.
+varname.utils.
get_node_by_frame
(
frame
, raise_exc=True
)
→ ASTGet the node by frame, raise errors if possible
varname.utils.
lookfor_parent_assign
(
node
, strict=True
)
→ Assign, AnnAssign, or NamedExprLook for an ast.Assign node in the parents
varname.utils.
node_name
(
node
, subscript_slice=False
)
→ Union(str, (str or tuple, ...))Get the node node name.
Raises ImproperUseError when failed
+varname.utils.
bytecode_nameof
(
code
, offset
)
→ strCached Bytecode version of nameof
We are trying this version only when the sourcecode is unavisible. In most
+cases, this will happen when user is trying to run a script in REPL/
+python shell, with eval
, or other circumstances where the code is
+manipulated to run but sourcecode is not available.
varname.utils.
attach_ignore_id_to_module
(
module
)
Attach the ignore id to module
This is useful when a module cannot be retrieved by frames using
+inspect.getmodule
, then we can use this id, which will exist in
+frame.f_globals
to check if the module matches in ignore.
Do it only when the file is not avaiable or does not exist for
+the module. Since this probably means the source is not avaiable and
+inspect.getmodule
would not work
varname.utils.
frame_matches_module_by_ignore_id
(
frame
, module
)
→ boolCheck if the frame is from the module by ignore id
varname.utils.
check_qualname_by_source
(
source
, modname
, qualname
)
Check if a qualname in module is unique
varname.utils.
debug_ignore_frame
(
msg
, frameinfo=None
)
Print the debug message for a given frame info object
msg
+(str)
+— The debugging messageframeinfo
+(FrameInfo, optional)
+— The FrameInfo object for the framevarname.utils.
argnode_source
(
source
, node
, vars_only
)
Get the source of an argument node
source
+(Source)
+— The executing source objectnode
+(AST)
+— The node to get the source fromvars_only
+(bool)
+— Whether only allow variables and attributesThe source of the node (node.id for ast.Name, node.attr for ast.Attribute). Or the node itself if the source + cannot be fetched.
+varname.utils.
get_argument_sources
(
source
, node
, func
, vars_only
)
Get the sources for argument from an ast.Call node
>>> def func(a, b, c, d=4):
+>>> ...
+>>> x = y = z = 1
+>>> func(y, x, c=z)
+>>> # argument_sources = {'a': 'y', 'b', 'x', 'c': 'z'}
+>>> func(y, x, c=1)
+>>> # argument_sources = {'a': 'y', 'b', 'x', 'c': ast.Num(n=1)}
+
varname.utils.
get_function_called_argname
(
frame
, node
)
Get the function who called argname
varname.utils.
reconstruct_func_node
(
node
)
→ CallReconstruct the ast.Call node from
x.a
to x.__getattr__('a')
+x.a = b
to x.__setattr__('a', b)
+x[a]
to x.__getitem__(a)
+x[a] = b
to x.__setitem__(a, 1)
+x + a
to x.__add__(a)
+x < a
to x.__lt__(a)
varname.utils.
rich_exc_message
(
msg
, node
, context_lines=4
)
→ strAttach the source code from the node to message toget a rich message for exceptions
+If package 'rich' is not install or 'node.frame' doesn't exist, fall +to plain message (with basic information), otherwise show a better message +with full information
+Dark magics about variable names in python
Some helper functions builtin based upon core features
debug
(
var
, *more_vars
, prefix
, merge
, repr
, sep
, vars_only
)
+
+— Print variable names and values.</>exec_code
(
code
, globals
, locals
, sourcefile
, frame
, ignore
, **kwargs
)
+
+— Execute code where source code is visible at runtime.</>jsobj
(
*args
, vars_only
, frame
, **kwargs
)
+(dict(str: any))
+— A wrapper to create a JavaScript-like object</>register
(
cls_or_func
, frame
, ignore
, multi_vars
, raise_exc
, strict
)
+( or )
+— A decorator to register varname to a class or function</>Some internal utilities for varname
config
+
+— Global configurations for varname</>VarnameException
+
+— Root exception for all varname exceptions</>VarnameRetrievingError
+
+— When failed to retrieve the varname</>QualnameNonUniqueError
+
+— When a qualified name is used as an ignore element but references tomultiple objects in a module
+</>ImproperUseError
+
+— When varname() is improperly used</>VarnameWarning
+
+— Root warning for all varname warnings</>MaybeDecoratedFunctionWarning
+
+— When a suspecious decorated function used as ignore function directly</>MultiTargetAssignmentWarning
+
+— When varname tries to retrieve variable name ina multi-target assignment
+</>UsingExecWarning
+
+— When exec is used to retrieve function name for argname()
</>argnode_source
(
source
, node
, vars_only
)
+(str or AST)
+— Get the source of an argument node</>attach_ignore_id_to_module
(
module
)
+
+— Attach the ignore id to module</>bytecode_nameof
(
code
, offset
)
+(str)
+— Cached Bytecode version of nameof</>cached_getmodule
(
codeobj
)
+
+— Cached version of inspect.getmodule</>check_qualname_by_source
(
source
, modname
, qualname
)
+
+— Check if a qualname in module is unique</>debug_ignore_frame
(
msg
, frameinfo
)
+
+— Print the debug message for a given frame info object</>frame_matches_module_by_ignore_id
(
frame
, module
)
+(bool)
+— Check if the frame is from the module by ignore id</>get_argument_sources
(
source
, node
, func
, vars_only
)
+
+— Get the sources for argument from an ast.Call node</>get_function_called_argname
(
frame
, node
)
+
+— Get the function who called argname</>get_node
(
frame
, ignore
, raise_exc
, ignore_lambda
)
+(AST)
+— Try to get node from the executing object.</>get_node_by_frame
(
frame
, raise_exc
)
+(AST)
+— Get the node by frame, raise errors if possible</>lookfor_parent_assign
(
node
, strict
)
+(Assign, AnnAssign, or NamedExpr)
+— Look for an ast.Assign node in the parents</>node_name
(
node
, subscript_slice
)
+(Union(str, (str or tuple, ...)))
+— Get the node node name.</>reconstruct_func_node
(
node
)
+(Call)
+— Reconstruct the ast.Call node from</>rich_exc_message
(
msg
, node
, context_lines
)
+(str)
+— Attach the source code from the node to message toget a rich message for exceptions
+</>Provide core features for varname
argname
(
arg
, *more_args
, func
, dispatch
, frame
, ignore
, vars_only
)
+(Union(ast, str, (ast or str, ...), , (union(ast, str, (ast or str, ...), ), ...)))
+— Get the names/sources of arguments passed to a function.</>nameof
(
var
, *more_vars
, frame
, vars_only
)
+(Union(str, (str, ...)))
+— Get the names of the variables passed in</>varname
(
frame
, ignore
, multi_vars
, raise_exc
, strict
)
+(Union(str, (str or tuple, ...)))
+— Get the name of the variable(s) that assigned by function call orclass instantiation.
+</>will
(
frame
, raise_exc
)
+(str)
+— Detect the attribute name right immediately after a function call.</>The frame ignoring system for varname
There 4 mechanisms to ignore intermediate frames to determine the desired one +so that a variable name should be retrieved at that frame.
+exec
without module available.Any frames in varname
, standard libraries, and frames of any expressions like
+
IgnoreElem
(
)
+
+— An element of the ignore list</>IgnoreModule
+
+— Ignore calls from a module or its submodules</>IgnoreFilename
+
+— Ignore calls from a module by matching its filename</>IgnoreDirname
+
+— Ignore calls from modules inside a directory</>IgnoreStdlib
+
+— Ignore standard libraries in sysconfig.get_python_lib(standard_lib=True)</>IgnoreFunction
+
+— Ignore a non-decorated function</>IgnoreDecorated
+
+— Ignore a decorated function</>IgnoreModuleQualname
+
+— Ignore calls by qualified name in the module</>IgnoreFilenameQualname
+
+— Ignore calls with given qualname in the module with the filename</>IgnoreOnlyQualname
+
+— Ignore calls that match the given qualname, across all frames.</>IgnoreList
+
+— The ignore list to match the frames to see if they should be ignored</>create_ignore_elem
(
ignore_elem
)
+(IgnoreElem)
+— Create an ignore element according to the type</>