varname 0.12.1__py3-none-any.whl → 0.13.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
varname/__init__.py CHANGED
@@ -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"
varname/helpers.py CHANGED
@@ -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)
varname/utils.py CHANGED
@@ -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
 
@@ -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.
@@ -0,0 +1,9 @@
1
+ varname/__init__.py,sha256=UTuY7fG_AbLwpbhBnup66zRlEvl0WnG6exf5AvOyyp0,369
2
+ varname/core.py,sha256=nntOVpiavXP0EXijTNF6xIe4mxvz_FRpBjrL9z_JHJ0,19311
3
+ varname/helpers.py,sha256=ho5X2t3PiSYDexwpA4TO_l68VWh3HER7bsUKzcbiCNM,9242
4
+ varname/ignore.py,sha256=-VC3oqag44y2UlAw0ErYKHotx616qJL3Sjb_5y7Tw1c,14400
5
+ varname/utils.py,sha256=ByHbUjbdMlHHckW4y77Jr_DHhkSmk8TabTy3INDJWsY,20971
6
+ varname-0.13.0.dist-info/LICENSE,sha256=3bS8O2tMbBPz8rWmZBAOzkHQjcK-b7KwFHyyghEZ-Ak,1063
7
+ varname-0.13.0.dist-info/METADATA,sha256=6YjfqWLcmjJ9TXHN5yLlAn0DHrBg3escnFyzqLgKdWM,12580
8
+ varname-0.13.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
9
+ varname-0.13.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- varname/__init__.py,sha256=wCvOdwu0J6X2OQr3j_LgE5rTFYtVK7gPqDD382qOkVs,369
2
- varname/core.py,sha256=nntOVpiavXP0EXijTNF6xIe4mxvz_FRpBjrL9z_JHJ0,19311
3
- varname/helpers.py,sha256=TUeohi5yCSleXA2betkulAG2_23jsoM_vohHuCEVPQI,6679
4
- varname/ignore.py,sha256=-VC3oqag44y2UlAw0ErYKHotx616qJL3Sjb_5y7Tw1c,14400
5
- varname/utils.py,sha256=f0dDRYfOeebdDvEwVSQVXNpHC1sB3-1qNE8mICg1IcE,19296
6
- varname-0.12.1.dist-info/LICENSE,sha256=3bS8O2tMbBPz8rWmZBAOzkHQjcK-b7KwFHyyghEZ-Ak,1063
7
- varname-0.12.1.dist-info/METADATA,sha256=IrxRHVvT9g4Xtq0w_1tEV9o3_rCeeW1KqkU0dAsg3s4,12074
8
- varname-0.12.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
9
- varname-0.12.1.dist-info/RECORD,,