varname 0.13.3__tar.gz → 0.13.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: varname
3
- Version: 0.13.3
3
+ Version: 0.13.5
4
4
  Summary: Dark magics about variable names in python.
5
5
  Home-page: https://github.com/pwwang/python-varname
6
6
  License: MIT
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Provides-Extra: all
18
18
  Requires-Dist: asttokens (==2.*) ; extra == "all"
19
- Requires-Dist: executing (>=2.0,<3.0)
19
+ Requires-Dist: executing (>=2.1,<3.0)
20
20
  Requires-Dist: pure_eval (==0.*) ; extra == "all"
21
21
  Project-URL: Repository, https://github.com/pwwang/python-varname
22
22
  Description-Content-Type: text/markdown
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = [ "poetry>=0.12",]
3
- build-backend = "poetry.masonry.api"
2
+ requires = ["poetry-core"]
3
+ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "varname"
7
- version = "0.13.3"
7
+ version = "0.13.5"
8
8
  description = "Dark magics about variable names in python."
9
9
  authors = [ "pwwang <pwwang@pwwang.com>",]
10
10
  license = "MIT"
@@ -17,7 +17,7 @@ generate-setup-file = true
17
17
 
18
18
  [tool.poetry.dependencies]
19
19
  python = "^3.8"
20
- executing = "^2.0"
20
+ executing = "^2.1"
21
21
  asttokens = { version = "2.*", optional = true }
22
22
  pure_eval = { version = "0.*", optional = true }
23
23
 
@@ -43,5 +43,5 @@ strict_optional = false
43
43
 
44
44
  [tool.black]
45
45
  line-length = 88
46
- target-version = ['py37', 'py38', 'py39', 'py310']
46
+ target-version = ['py38', 'py39', 'py310', 'py311', 'py312', 'py313']
47
47
  include = '\.pyi?$'
@@ -8,14 +8,14 @@ package_data = \
8
8
  {'': ['*']}
9
9
 
10
10
  install_requires = \
11
- ['executing>=2.0,<3.0']
11
+ ['executing>=2.1,<3.0']
12
12
 
13
13
  extras_require = \
14
14
  {'all': ['asttokens==2.*', 'pure_eval==0.*']}
15
15
 
16
16
  setup_kwargs = {
17
17
  'name': 'varname',
18
- 'version': '0.13.3',
18
+ 'version': '0.13.5',
19
19
  'description': 'Dark magics about variable names in python.',
20
20
  'long_description': '![varname][7]\n\n[![Pypi][3]][4] [![Github][5]][6] [![PythonVers][8]][4] ![Building][10]\n[![Docs and API][9]][15] [![Codacy][12]][13] [![Codacy coverage][14]][13]\n![Downloads][17]\n\nDark magics about variable names in python\n\n[CHANGELOG][16] | [API][15] | [Playground][11] | :fire: [StackOverflow answer][20]\n\n## Installation\n\n```shell\npip install -U varname\n```\n\nNote if you use `python < 3.8`, install `varname < 0.11`\n\n## Features\n\n- Core features:\n\n - Retrieving names of variables a function/class call is assigned to from inside it, using `varname`.\n - Retrieving variable names directly, using `nameof`\n - Detecting next immediate attribute name, using `will`\n - Fetching argument names/sources passed to a function using `argname`\n\n- Other helper APIs (built based on core features):\n\n - A value wrapper to store the variable name that a value is assigned to, using `Wrapper`\n - A decorator to register `__varname__` to functions/classes, using `register`\n - A helper function to create dict without explicitly specifying the key-value pairs, using `jsobj`\n - A `debug` function to print variables with their names and values\n - `exec_code` to replace `exec` where source code is available at runtime\n\n## Credits\n\nThanks goes to these awesome people/projects:\n\n<table>\n <tr>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/alexmojaki/executing">\n <img src="https://ui-avatars.com/api/?color=3333ff&background=ffffff&bold=true&name=e&size=400" width="50px;" alt=""/>\n <br /><sub><b>executing</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/alexmojaki">\n <img src="https://avatars0.githubusercontent.com/u/3627481?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@alexmojaki</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/breuleux">\n <img src="https://avatars.githubusercontent.com/u/599820?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@breuleux</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/ElCuboNegro">\n <img src="https://avatars.githubusercontent.com/u/5524219?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@ElCuboNegro</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/thewchan">\n <img src="https://avatars.githubusercontent.com/u/49702524?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@thewchan</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/LawsOfSympathy">\n <img src="https://avatars.githubusercontent.com/u/96355982?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@LawsOfSympathy</b></sub>\n </a>\n </td>\n <td align="center" style="min-width: 75px">\n <a href="https://github.com/elliotgunton">\n <img src="https://avatars.githubusercontent.com/u/17798778?s=400&v=4" width="50px;" alt=""/>\n <br /><sub><b>@elliotgunton</b></sub>\n </a>\n </td>\n </tr>\n</table>\n\nSpecial thanks to [@HanyuuLu][2] to give up the name `varname` in pypi for this project.\n\n## Usage\n\n### Retrieving the variable names using `varname(...)`\n\n- From inside a function\n\n ```python\n from varname import varname\n def function():\n return varname()\n\n func = function() # func == \'func\'\n ```\n\n When there are intermediate frames:\n\n ```python\n def wrapped():\n return function()\n\n def function():\n # retrieve the variable name at the 2nd frame from this one\n return varname(frame=2)\n\n func = wrapped() # func == \'func\'\n ```\n\n Or use `ignore` to ignore the wrapped frame:\n\n ```python\n def wrapped():\n return function()\n\n def function():\n return varname(ignore=wrapped)\n\n func = wrapped() # func == \'func\'\n ```\n\n Calls from standard libraries are ignored by default:\n\n ```python\n import asyncio\n\n async def function():\n return varname()\n\n func = asyncio.run(function()) # func == \'func\'\n ```\n\n Use `strict` to control whether the call should be assigned to\n the variable directly:\n\n ```python\n def function(strict):\n return varname(strict=strict)\n\n func = function(True) # OK, direct assignment, func == \'func\'\n\n func = [function(True)] # Not a direct assignment, raises ImproperUseError\n func = [function(False)] # OK, func == [\'func\']\n\n func = function(False), function(False) # OK, func = (\'func\', \'func\')\n ```\n\n- Retrieving name of a class instance\n\n ```python\n class Foo:\n def __init__(self):\n self.id = varname()\n\n def copy(self):\n # also able to fetch inside a method call\n copied = Foo() # copied.id == \'copied\'\n copied.id = varname() # assign id to whatever variable name\n return copied\n\n foo = Foo() # foo.id == \'foo\'\n\n foo2 = foo.copy() # foo2.id == \'foo2\'\n ```\n\n- Multiple variables on Left-hand side\n\n ```python\n # since v0.5.4\n def func():\n return varname(multi_vars=True)\n\n a = func() # a == (\'a\',)\n a, b = func() # (a, b) == (\'a\', \'b\')\n [a, b] = func() # (a, b) == (\'a\', \'b\')\n\n # hierarchy is also possible\n a, (b, c) = func() # (a, b, c) == (\'a\', \'b\', \'c\')\n ```\n\n- Some unusual use\n\n ```python\n def function(**kwargs):\n return varname(strict=False)\n\n func = func1 = function() # func == func1 == \'func1\'\n # if varname < 0.8: func == func1 == \'func\'\n # a warning will be shown\n # since you may not want func to be \'func1\'\n\n x = function(y = function()) # x == \'x\'\n\n # get part of the name\n func_abc = function()[-3:] # func_abc == \'abc\'\n\n # function alias supported now\n function2 = function\n func = function2() # func == \'func\'\n\n a = lambda: 0\n a.b = function() # a.b == \'a.b\'\n ```\n\n### The decorator way to register `__varname__` to functions/classes\n\n- Registering `__varname__` to functions\n\n ```python\n from varname.helpers import register\n\n @register\n def function():\n return __varname__\n\n func = function() # func == \'func\'\n ```\n\n ```python\n # arguments also allowed (frame, ignore and raise_exc)\n @register(frame=2)\n def function():\n return __varname__\n\n def wrapped():\n return function()\n\n func = wrapped() # func == \'func\'\n ```\n\n- Registering `__varname__` as a class property\n\n ```python\n @register\n class Foo:\n ...\n\n foo = Foo()\n # foo.__varname__ == \'foo\'\n ```\n\n### Getting variable names directly using `nameof`\n\n```python\nfrom varname import varname, nameof\n\na = 1\nnameof(a) # \'a\'\n\nb = 2\nnameof(a, b) # (\'a\', \'b\')\n\ndef func():\n return varname() + \'_suffix\'\n\nf = func() # f == \'f_suffix\'\nnameof(f) # \'f\'\n\n# get full names of (chained) attribute calls\nfunc.a = func\nnameof(func.a, vars_only=False) # \'func.a\'\n\nfunc.a.b = 1\nnameof(func.a.b, vars_only=False) # \'func.a.b\'\n```\n\n### Detecting next immediate attribute name\n\n```python\nfrom varname import will\nclass AwesomeClass:\n def __init__(self):\n self.will = None\n\n def permit(self):\n self.will = will(raise_exc=False)\n if self.will == \'do\':\n # let self handle do\n return self\n raise AttributeError(\'Should do something with AwesomeClass object\')\n\n def do(self):\n if self.will != \'do\':\n raise AttributeError("You don\'t have permission to do")\n return \'I am doing!\'\n\nawesome = AwesomeClass()\nawesome.do() # AttributeError: You don\'t have permission to do\nawesome.permit() # AttributeError: Should do something with AwesomeClass object\nawesome.permit().do() == \'I am doing!\'\n```\n\n### Fetching argument names/sources using `argname`\n\n```python\nfrom varname import argname\n\ndef func(a, b=1):\n print(argname(\'a\'))\n\nx = y = z = 2\nfunc(x) # prints: x\n\n\ndef func2(a, b=1):\n print(argname(\'a\', \'b\'))\nfunc2(y, b=x) # prints: (\'y\', \'x\')\n\n\n# allow expressions\ndef func3(a, b=1):\n print(argname(\'a\', \'b\', vars_only=False))\nfunc3(x+y, y+x) # prints: (\'x+y\', \'y+x\')\n\n\n# positional and keyword arguments\ndef func4(*args, **kwargs):\n print(argname(\'args[1]\', \'kwargs[c]\'))\nfunc4(y, x, c=z) # prints: (\'x\', \'z\')\n\n\n# As of 0.9.0 (see: https://pwwang.github.io/python-varname/CHANGELOG/#v090)\n# Can also fetch the source of the argument for\n# __getattr__/__getitem__/__setattr/__setitem__/__add__/__lt__, etc.\nclass Foo:\n def __setattr__(self, name, value):\n print(argname("name", "value", func=self.__setattr__))\n\nFoo().a = 1 # prints: ("\'a\'", \'1\')\n\n```\n\n### Value wrapper\n\n```python\nfrom varname.helpers import Wrapper\n\nfoo = Wrapper(True)\n# foo.name == \'foo\'\n# foo.value == True\nbar = Wrapper(False)\n# bar.name == \'bar\'\n# bar.value == False\n\ndef values_to_dict(*args):\n return {val.name: val.value for val in args}\n\nmydict = values_to_dict(foo, bar)\n# {\'foo\': True, \'bar\': False}\n```\n\n### Creating dictionary using `jsobj`\n\n```python\nfrom varname.helpers import jsobj\n\na = 1\nb = 2\njsobj(a, b) # {\'a\': 1, \'b\': 2}\njsobj(a, b, c=3) # {\'a\': 1, \'b\': 2, \'c\': 3}\n```\n\n### Debugging with `debug`\n\n```python\nfrom varname.helpers import debug\n\na = \'value\'\nb = [\'val\']\ndebug(a)\n# "DEBUG: a=\'value\'\\n"\ndebug(b)\n# "DEBUG: b=[\'val\']\\n"\ndebug(a, b)\n# "DEBUG: a=\'value\'\\nDEBUG: b=[\'val\']\\n"\ndebug(a, b, merge=True)\n# "DEBUG: a=\'value\', b=[\'val\']\\n"\ndebug(a, repr=False, prefix=\'\')\n# \'a=value\\n\'\n# also debug an expression\ndebug(a+a)\n# "DEBUG: a+a=\'valuevalue\'\\n"\n# If you want to disable it:\ndebug(a+a, vars_only=True) # ImproperUseError\n```\n\n### Replacing `exec` with `exec_code`\n\n```python\nfrom varname import argname\nfrom varname.helpers import exec_code\n\nclass Obj:\n def __init__(self):\n self.argnames = []\n\n def receive(self, arg):\n self.argnames.append(argname(\'arg\', func=self.receive))\n\nobj = Obj()\n# exec(\'obj.receive(1)\') # Error\nexec_code(\'obj.receive(1)\')\nexec_code(\'obj.receive(2)\')\nobj.argnames # [\'1\', \'2\']\n```\n\n## Reliability and limitations\n\n`varname` is all depending on `executing` package to look for the node.\nThe node `executing` detects is ensured to be the correct one (see [this][19]).\n\nIt partially works with environments where other AST magics apply, including\n`pytest`, `ipython`, `macropy`, `birdseye`, `reticulate` with `R`, etc. Neither\n`executing` nor `varname` is 100% working with those environments. Use\nit at your own risk.\n\nFor example:\n\n- This will not work with `pytest`:\n\n ```python\n a = 1\n assert nameof(a) == \'a\' # pytest manipulated the ast here\n\n # do this instead\n name_a = nameof(a)\n assert name_a == \'a\'\n ```\n\n[1]: https://github.com/pwwang/python-varname\n[2]: https://github.com/HanyuuLu\n[3]: https://img.shields.io/pypi/v/varname?style=flat-square\n[4]: https://pypi.org/project/varname/\n[5]: https://img.shields.io/github/tag/pwwang/python-varname?style=flat-square\n[6]: https://github.com/pwwang/python-varname\n[7]: logo.png\n[8]: https://img.shields.io/pypi/pyversions/varname?style=flat-square\n[9]: https://img.shields.io/github/actions/workflow/status/pwwang/python-varname/docs.yml?branch=master\n[10]: https://img.shields.io/github/actions/workflow/status/pwwang/python-varname/build.yml?branch=master\n[11]: https://mybinder.org/v2/gh/pwwang/python-varname/dev?filepath=playground%2Fplayground.ipynb\n[12]: https://img.shields.io/codacy/grade/6fdb19c845f74c5c92056e88d44154f7?style=flat-square\n[13]: https://app.codacy.com/gh/pwwang/python-varname/dashboard\n[14]: https://img.shields.io/codacy/coverage/6fdb19c845f74c5c92056e88d44154f7?style=flat-square\n[15]: https://pwwang.github.io/python-varname/api/varname\n[16]: https://pwwang.github.io/python-varname/CHANGELOG/\n[17]: https://img.shields.io/pypi/dm/varname?style=flat-square\n[19]: https://github.com/alexmojaki/executing#is-it-reliable\n[20]: https://stackoverflow.com/a/59364138/5088165\n',
21
21
  'author': 'pwwang',
@@ -13,4 +13,4 @@ from .utils import (
13
13
  )
14
14
  from .core import varname, nameof, will, argname
15
15
 
16
- __version__ = "0.13.3"
16
+ __version__ = "0.13.5"
@@ -291,6 +291,11 @@ def nameof(
291
291
  VarnameRetrievingError: When the callee's node cannot be retrieved or
292
292
  trying to retrieve the full name of non attribute series calls.
293
293
  """
294
+ warnings.warn(
295
+ "`nameof` is deprecated and will be removed in the future. "
296
+ "Please use `argname` instead.",
297
+ DeprecationWarning,
298
+ )
294
299
  # Frame is anyway used in get_node
295
300
  frameobj = IgnoreList.create(
296
301
  ignore_lambda=False,
@@ -74,6 +74,7 @@ else: # pragma: no cover
74
74
  ASSIGN_TYPES = (ast.Assign, ast.AnnAssign)
75
75
  AssignType = Union[ASSIGN_TYPES] # type: ignore
76
76
 
77
+ PY311 = sys.version_info >= (3, 11)
77
78
  MODULE_IGNORE_ID_NAME = "__varname_ignore_id__"
78
79
 
79
80
 
@@ -281,6 +282,7 @@ def bytecode_nameof(code: CodeType, offset: int) -> str:
281
282
  "CALL_FUNCTION",
282
283
  "CALL_METHOD",
283
284
  "CALL",
285
+ "CALL_KW",
284
286
  ):
285
287
  raise VarnameRetrievingError("Did you call 'nameof' in a weird way?")
286
288
 
@@ -290,7 +292,7 @@ def bytecode_nameof(code: CodeType, offset: int) -> str:
290
292
  current_instruction_index -= 1
291
293
  name_instruction = instructions[current_instruction_index]
292
294
 
293
- if name_instruction.opname == "KW_NAMES": # pragma: no cover
295
+ if name_instruction.opname in ("KW_NAMES", "LOAD_CONST"): # LOAD_CONST python 3.13
294
296
  raise pos_only_error
295
297
 
296
298
  if not name_instruction.opname.startswith("LOAD_"):
@@ -533,22 +535,38 @@ def _(node: Union[ast.Attribute, ast.Subscript]) -> ast.Call:
533
535
 
534
536
  # x[1], x.a
535
537
  if isinstance(node.ctx, ast.Load):
536
- return ast.Call(
537
- func=ast.Attribute(
538
- value=node.value,
539
- attr=(
540
- "__getitem__"
541
- if isinstance(node, ast.Subscript)
542
- else "__getattr__"
538
+ if PY311:
539
+ return ast.Call(
540
+ func=ast.Attribute(
541
+ value=node.value,
542
+ attr=(
543
+ "__getitem__"
544
+ if isinstance(node, ast.Subscript)
545
+ else "__getattr__"
546
+ ),
547
+ ctx=ast.Load(),
548
+ **nodemeta,
543
549
  ),
544
- ctx=ast.Load(),
545
- **nodemeta,
546
- ),
547
- args=[keynode],
548
- keywords=[],
549
- starargs=None,
550
- kwargs=None,
551
- )
550
+ args=[keynode],
551
+ keywords=[],
552
+ )
553
+ else:
554
+ return ast.Call( # type: ignore
555
+ func=ast.Attribute(
556
+ value=node.value,
557
+ attr=(
558
+ "__getitem__"
559
+ if isinstance(node, ast.Subscript)
560
+ else "__getattr__"
561
+ ),
562
+ ctx=ast.Load(),
563
+ **nodemeta,
564
+ ),
565
+ args=[keynode],
566
+ keywords=[],
567
+ starargs=None,
568
+ kwargs=None,
569
+ )
552
570
 
553
571
  # x[a] = b, x.a = b
554
572
  if (
@@ -564,22 +582,38 @@ def _(node: Union[ast.Attribute, ast.Subscript]) -> ast.Call:
564
582
  )
565
583
  )
566
584
 
567
- return ast.Call(
568
- func=ast.Attribute(
569
- value=node.value,
570
- attr=(
571
- "__setitem__"
572
- if isinstance(node, ast.Subscript)
573
- else "__setattr__"
585
+ if PY311:
586
+ return ast.Call(
587
+ func=ast.Attribute(
588
+ value=node.value,
589
+ attr=(
590
+ "__setitem__"
591
+ if isinstance(node, ast.Subscript)
592
+ else "__setattr__"
593
+ ),
594
+ ctx=ast.Load(),
595
+ **nodemeta,
574
596
  ),
575
- ctx=ast.Load(),
576
- **nodemeta,
577
- ),
578
- args=[keynode, node.parent.value], # type: ignore
579
- keywords=[],
580
- starargs=None,
581
- kwargs=None,
582
- )
597
+ args=[keynode, node.parent.value], # type: ignore
598
+ keywords=[],
599
+ )
600
+ else:
601
+ return ast.Call(
602
+ func=ast.Attribute(
603
+ value=node.value,
604
+ attr=(
605
+ "__setitem__"
606
+ if isinstance(node, ast.Subscript)
607
+ else "__setattr__"
608
+ ),
609
+ ctx=ast.Load(),
610
+ **nodemeta,
611
+ ),
612
+ args=[keynode, node.parent.value], # type: ignore
613
+ keywords=[],
614
+ starargs=None,
615
+ kwargs=None,
616
+ )
583
617
 
584
618
 
585
619
  @reconstruct_func_node.register(ast.Compare)
@@ -593,18 +627,30 @@ def _(node: ast.Compare) -> ast.Call:
593
627
  "lineno": node.lineno,
594
628
  "col_offset": node.col_offset,
595
629
  }
596
- return ast.Call(
597
- func=ast.Attribute(
598
- value=node.left,
599
- attr=CMP2MAGIC[type(node.ops[0])],
600
- ctx=ast.Load(),
601
- **nodemeta,
602
- ),
603
- args=[node.comparators[0]],
604
- keywords=[],
605
- starargs=None,
606
- kwargs=None,
607
- )
630
+ if PY311:
631
+ return ast.Call(
632
+ func=ast.Attribute(
633
+ value=node.left,
634
+ attr=CMP2MAGIC[type(node.ops[0])],
635
+ ctx=ast.Load(),
636
+ **nodemeta,
637
+ ),
638
+ args=[node.comparators[0]],
639
+ keywords=[],
640
+ )
641
+ else:
642
+ return ast.Call( # type: ignore
643
+ func=ast.Attribute(
644
+ value=node.left,
645
+ attr=CMP2MAGIC[type(node.ops[0])],
646
+ ctx=ast.Load(),
647
+ **nodemeta,
648
+ ),
649
+ args=[node.comparators[0]],
650
+ keywords=[],
651
+ starargs=None,
652
+ kwargs=None,
653
+ )
608
654
 
609
655
 
610
656
  @reconstruct_func_node.register(ast.BinOp)
@@ -614,18 +660,31 @@ def _(node: ast.BinOp) -> ast.Call:
614
660
  "lineno": node.lineno,
615
661
  "col_offset": node.col_offset,
616
662
  }
617
- return ast.Call(
618
- func=ast.Attribute(
619
- value=node.left,
620
- attr=OP2MAGIC[type(node.op)],
621
- ctx=ast.Load(),
622
- **nodemeta,
623
- ),
624
- args=[node.right],
625
- keywords=[],
626
- starargs=None,
627
- kwargs=None,
628
- )
663
+
664
+ if PY311:
665
+ return ast.Call(
666
+ func=ast.Attribute(
667
+ value=node.left,
668
+ attr=OP2MAGIC[type(node.op)],
669
+ ctx=ast.Load(),
670
+ **nodemeta,
671
+ ),
672
+ args=[node.right],
673
+ keywords=[],
674
+ )
675
+ else:
676
+ return ast.Call( # type: ignore
677
+ func=ast.Attribute(
678
+ value=node.left,
679
+ attr=OP2MAGIC[type(node.op)],
680
+ ctx=ast.Load(),
681
+ **nodemeta,
682
+ ),
683
+ args=[node.right],
684
+ keywords=[],
685
+ starargs=None,
686
+ kwargs=None,
687
+ )
629
688
 
630
689
 
631
690
  def rich_exc_message(msg: str, node: ast.AST, context_lines: int = 4) -> str:
File without changes
File without changes
File without changes
File without changes
File without changes