varname 0.12.1__tar.gz → 0.13.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: varname
3
- Version: 0.12.1
3
+ Version: 0.13.0
4
4
  Summary: Dark magics about variable names in python.
5
5
  Home-page: https://github.com/pwwang/python-varname
6
6
  License: MIT
@@ -54,6 +54,7 @@ Note if you use `python < 3.8`, install `varname < 0.11`
54
54
  - A decorator to register `__varname__` to functions/classes, using `register`
55
55
  - A helper function to create dict without explicitly specifying the key-value pairs, using `jsobj`
56
56
  - A `debug` function to print variables with their names and values
57
+ - `exec_code` to replace `exec` where source code is available at runtime
57
58
 
58
59
  ## Credits
59
60
 
@@ -221,7 +222,7 @@ Special thanks to [@HanyuuLu][2] to give up the name `varname` in pypi for this
221
222
  func = function2() # func == 'func'
222
223
 
223
224
  a = lambda: 0
224
- a.b = function() # a.b == 'b'
225
+ a.b = function() # a.b == 'a.b'
225
226
  ```
226
227
 
227
228
  ### The decorator way to register `__varname__` to functions/classes
@@ -346,7 +347,7 @@ func4(y, x, c=z) # prints: ('x', 'z')
346
347
  # __getattr__/__getitem__/__setattr/__setitem__/__add__/__lt__, etc.
347
348
  class Foo:
348
349
  def __setattr__(self, name, value):
349
- print(argname("name", "value"))
350
+ print(argname("name", "value", func=self.__setattr__))
350
351
 
351
352
  Foo().a = 1 # prints: ("'a'", '1')
352
353
 
@@ -406,6 +407,26 @@ debug(a+a)
406
407
  debug(a+a, vars_only=True) # ImproperUseError
407
408
  ```
408
409
 
410
+ ### Replacing `exec` with `exec_code`
411
+
412
+ ```python
413
+ from varname import argname
414
+ from varname.helpers import exec_code
415
+
416
+ class Obj:
417
+ def __init__(self):
418
+ self.argnames = []
419
+
420
+ def receive(self, arg):
421
+ self.argnames.append(argname('arg', func=self.receive))
422
+
423
+ obj = Obj()
424
+ # exec('obj.receive(1)') # Error
425
+ exec_code('obj.receive(1)')
426
+ exec_code('obj.receive(2)')
427
+ obj.argnames # ['1', '2']
428
+ ```
429
+
409
430
  ## Reliability and limitations
410
431
 
411
432
  `varname` is all depending on `executing` package to look for the node.
@@ -31,6 +31,7 @@ Note if you use `python < 3.8`, install `varname < 0.11`
31
31
  - A decorator to register `__varname__` to functions/classes, using `register`
32
32
  - A helper function to create dict without explicitly specifying the key-value pairs, using `jsobj`
33
33
  - A `debug` function to print variables with their names and values
34
+ - `exec_code` to replace `exec` where source code is available at runtime
34
35
 
35
36
  ## Credits
36
37
 
@@ -198,7 +199,7 @@ Special thanks to [@HanyuuLu][2] to give up the name `varname` in pypi for this
198
199
  func = function2() # func == 'func'
199
200
 
200
201
  a = lambda: 0
201
- a.b = function() # a.b == 'b'
202
+ a.b = function() # a.b == 'a.b'
202
203
  ```
203
204
 
204
205
  ### The decorator way to register `__varname__` to functions/classes
@@ -323,7 +324,7 @@ func4(y, x, c=z) # prints: ('x', 'z')
323
324
  # __getattr__/__getitem__/__setattr/__setitem__/__add__/__lt__, etc.
324
325
  class Foo:
325
326
  def __setattr__(self, name, value):
326
- print(argname("name", "value"))
327
+ print(argname("name", "value", func=self.__setattr__))
327
328
 
328
329
  Foo().a = 1 # prints: ("'a'", '1')
329
330
 
@@ -383,6 +384,26 @@ debug(a+a)
383
384
  debug(a+a, vars_only=True) # ImproperUseError
384
385
  ```
385
386
 
387
+ ### Replacing `exec` with `exec_code`
388
+
389
+ ```python
390
+ from varname import argname
391
+ from varname.helpers import exec_code
392
+
393
+ class Obj:
394
+ def __init__(self):
395
+ self.argnames = []
396
+
397
+ def receive(self, arg):
398
+ self.argnames.append(argname('arg', func=self.receive))
399
+
400
+ obj = Obj()
401
+ # exec('obj.receive(1)') # Error
402
+ exec_code('obj.receive(1)')
403
+ exec_code('obj.receive(2)')
404
+ obj.argnames # ['1', '2']
405
+ ```
406
+
386
407
  ## Reliability and limitations
387
408
 
388
409
  `varname` is all depending on `executing` package to look for the node.
@@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "varname"
7
- version = "0.12.1"
7
+ version = "0.13.0"
8
8
  description = "Dark magics about variable names in python."
9
9
  authors = [ "pwwang <pwwang@pwwang.com>",]
10
10
  license = "MIT"
@@ -42,6 +42,6 @@ show_error_codes = true
42
42
  strict_optional = false
43
43
 
44
44
  [tool.black]
45
- line-length = 80
45
+ line-length = 88
46
46
  target-version = ['py37', 'py38', 'py39', 'py310']
47
47
  include = '\.pyi?$'
@@ -0,0 +1,34 @@
1
+ # -*- coding: utf-8 -*-
2
+ from setuptools import setup
3
+
4
+ packages = \
5
+ ['varname']
6
+
7
+ package_data = \
8
+ {'': ['*']}
9
+
10
+ install_requires = \
11
+ ['executing>=2.0,<3.0']
12
+
13
+ extras_require = \
14
+ {'all': ['asttokens==2.*', 'pure_eval==0.*']}
15
+
16
+ setup_kwargs = {
17
+ 'name': 'varname',
18
+ 'version': '0.13.0',
19
+ 'description': 'Dark magics about variable names in python.',
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 </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
+ 'author': 'pwwang',
22
+ 'author_email': 'pwwang@pwwang.com',
23
+ 'maintainer': 'None',
24
+ 'maintainer_email': 'None',
25
+ 'url': 'https://github.com/pwwang/python-varname',
26
+ 'packages': packages,
27
+ 'package_data': package_data,
28
+ 'install_requires': install_requires,
29
+ 'extras_require': extras_require,
30
+ 'python_requires': '>=3.8,<4.0',
31
+ }
32
+
33
+
34
+ setup(**setup_kwargs)
@@ -13,4 +13,4 @@ from .utils import (
13
13
  )
14
14
  from .core import varname, nameof, will, argname
15
15
 
16
- __version__ = "0.12.1"
16
+ __version__ = "0.13.0"
@@ -1,9 +1,13 @@
1
1
  """Some helper functions builtin based upon core features"""
2
+ from __future__ import annotations
3
+
2
4
  import inspect
3
5
  from functools import partial, wraps
6
+ from os import PathLike
4
7
  from typing import Any, Callable, Dict, Tuple, Type, Union
5
8
 
6
9
  from .utils import IgnoreType
10
+ from .ignore import IgnoreList
7
11
  from .core import argname, varname
8
12
 
9
13
 
@@ -220,3 +224,74 @@ def debug(
220
224
  else:
221
225
  for name_and_value in name_and_values:
222
226
  print(f"{prefix}{name_and_value}")
227
+
228
+
229
+ def exec_code(
230
+ code: str,
231
+ globals: Dict[str, Any] = None,
232
+ locals: Dict[str, Any] = None,
233
+ /,
234
+ sourcefile: PathLike | str = None,
235
+ frame: int = 1,
236
+ ignore: IgnoreType = None,
237
+ **kwargs: Any,
238
+ ) -> None:
239
+ """Execute code where source code is visible at runtime.
240
+
241
+ This function is useful when you want to execute some code, where you want to
242
+ retrieve the AST node of the code at runtime. This function will create a
243
+ temporary file and write the code into it, then execute the code in the
244
+ file.
245
+
246
+ Examples:
247
+ >>> from varname import varname
248
+ >>> def func(): return varname()
249
+ >>> exec('var = func()') # VarnameRetrievingError:
250
+ >>> # Unable to retrieve the ast node.
251
+ >>> from varname.helpers import code_exec
252
+ >>> code_exec('var = func()') # var == 'var'
253
+
254
+ Args:
255
+ code: The code to execute.
256
+ globals: The globals to use.
257
+ locals: The locals to use.
258
+ sourcefile: The source file to write the code into.
259
+ if not given, a temporary file will be used.
260
+ This file will be deleted after the code is executed.
261
+ frame: The call stack index. You can understand this as the number of
262
+ wrappers around this function. This is used to fetch `globals` and
263
+ `locals` from where the destination function (include the wrappers
264
+ of this function)
265
+ is called.
266
+ ignore: The intermediate calls to be ignored. See `varname.ignore`
267
+ Note that if both `globals` and `locals` are given, `frame` and
268
+ `ignore` will be ignored.
269
+ **kwargs: The keyword arguments to pass to `exec`.
270
+ """
271
+ if sourcefile is None:
272
+ import tempfile
273
+
274
+ with tempfile.NamedTemporaryFile(
275
+ mode="w", suffix=".py", delete=False
276
+ ) as f:
277
+ f.write(code)
278
+ sourcefile = f.name
279
+ else:
280
+ sourcefile = str(sourcefile)
281
+ with open(sourcefile, "w") as f:
282
+ f.write(code)
283
+
284
+ if globals is None or locals is None:
285
+ ignore_list = IgnoreList.create(ignore)
286
+ frame_info = ignore_list.get_frame(frame)
287
+ if globals is None:
288
+ globals = frame_info.f_globals
289
+ if locals is None:
290
+ locals = frame_info.f_locals
291
+
292
+ try:
293
+ exec(compile(code, sourcefile, "exec"), globals, locals, **kwargs)
294
+ finally:
295
+ import os
296
+
297
+ os.remove(sourcefile)
@@ -185,7 +185,10 @@ def lookfor_parent_assign(node: ast.AST, strict: bool = True) -> AssignType:
185
185
  return None
186
186
 
187
187
 
188
- def node_name(node: ast.AST) -> Union[str, Tuple[Union[str, Tuple], ...]]:
188
+ def node_name(
189
+ node: ast.AST,
190
+ subscript_slice: bool = False,
191
+ ) -> Union[str, Tuple[Union[str, Tuple], ...]]:
189
192
  """Get the node node name.
190
193
 
191
194
  Raises ImproperUseError when failed
@@ -193,15 +196,50 @@ def node_name(node: ast.AST) -> Union[str, Tuple[Union[str, Tuple], ...]]:
193
196
  if isinstance(node, ast.Name):
194
197
  return node.id
195
198
  if isinstance(node, ast.Attribute):
196
- return node.attr
197
- if isinstance(node, (ast.List, ast.Tuple)):
199
+ return f"{node_name(node.value)}.{node.attr}"
200
+ if isinstance(node, ast.Constant):
201
+ return repr(node.value)
202
+ if isinstance(node, (ast.List, ast.Tuple)) and not subscript_slice:
198
203
  return tuple(node_name(elem) for elem in node.elts)
204
+ if isinstance(node, ast.List):
205
+ return f"[{', '.join(node_name(elem) for elem in node.elts)}]" # type: ignore
206
+ if isinstance(node, ast.Tuple):
207
+ if len(node.elts) == 1:
208
+ return f"({node_name(node.elts[0])},)"
209
+ return f"({', '.join(node_name(elem) for elem in node.elts)})" # type: ignore
199
210
  if isinstance(node, ast.Starred):
200
211
  return f"*{node_name(node.value)}"
212
+ if isinstance(node, ast.Slice):
213
+ return (
214
+ f"{node_name(node.lower)}:{node_name(node.upper)}:{node_name(node.step)}"
215
+ if node.lower is not None
216
+ and node.upper is not None
217
+ and node.step is not None
218
+ else f"{node_name(node.lower)}:{node_name(node.upper)}"
219
+ if node.lower is not None and node.upper is not None
220
+ else f"{node_name(node.lower)}:"
221
+ if node.lower is not None
222
+ else f":{node_name(node.upper)}"
223
+ if node.upper is not None
224
+ else ":"
225
+ )
226
+
227
+ name = type(node).__name__
228
+ if isinstance(node, ast.Subscript):
229
+ try:
230
+ return f"{node_name(node.value)}[{node_name(node.slice, True)}]"
231
+ except ImproperUseError:
232
+ name = f"{node_name(node.value)}[{type(node.slice).__name__}]"
201
233
 
202
234
  raise ImproperUseError(
203
- f"Can only get name of a variable or attribute, "
204
- f"not {ast.dump(node)}"
235
+ f"Node {name!r} detected, but only following nodes are supported: \n"
236
+ " - ast.Name (e.g. x)\n"
237
+ " - ast.Attribute (e.g. x.y, x be other supported nodes)\n"
238
+ " - ast.Constant (e.g. 1, 'a')\n"
239
+ " - ast.List (e.g. [x, y, z])\n"
240
+ " - ast.Tuple (e.g. (x, y, z))\n"
241
+ " - ast.Starred (e.g. *x)\n"
242
+ " - ast.Subscript with slice of the above nodes (e.g. x[y])"
205
243
  )
206
244
 
207
245
 
varname-0.12.1/setup.py DELETED
@@ -1,34 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
- from setuptools import setup
3
-
4
- packages = \
5
- ['varname']
6
-
7
- package_data = \
8
- {'': ['*']}
9
-
10
- install_requires = \
11
- ['executing>=2.0,<3.0']
12
-
13
- extras_require = \
14
- {'all': ['asttokens==2.*', 'pure_eval==0.*']}
15
-
16
- setup_kwargs = {
17
- 'name': 'varname',
18
- 'version': '0.12.1',
19
- 'description': 'Dark magics about variable names in python.',
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\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 </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 == \'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"))\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## 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
- 'author': 'pwwang',
22
- 'author_email': 'pwwang@pwwang.com',
23
- 'maintainer': 'None',
24
- 'maintainer_email': 'None',
25
- 'url': 'https://github.com/pwwang/python-varname',
26
- 'packages': packages,
27
- 'package_data': package_data,
28
- 'install_requires': install_requires,
29
- 'extras_require': extras_require,
30
- 'python_requires': '>=3.8,<4.0',
31
- }
32
-
33
-
34
- setup(**setup_kwargs)
File without changes
File without changes
File without changes