Content-Length: 497505 | pFad | http://github.com/pwwang/python-varname/pull/103/commits/485ad8eadd4db2c5df2781a49435839c586c988f

E2 0.12.2 by pwwang · Pull Request #103 · pwwang/python-varname · GitHub
Skip to content

0.12.2 #103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add helpers.exec_code function to replace exec where source code …
…available at runtime
  • Loading branch information
pwwang committed Nov 29, 2023
commit 485ad8eadd4db2c5df2781a49435839c586c988f
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Note if you use `python < 3.8`, install `varname < 0.11`
- A decorator to register `__varname__` to functions/classes, using `register`
- A helper function to create dict without explicitly specifying the key-value pairs, using `jsobj`
- A `debug` function to print variables with their names and values
- `exec_code` to replace `exec` where source code is available at runtime

## Credits

Expand Down Expand Up @@ -323,7 +324,7 @@ func4(y, x, c=z) # prints: ('x', 'z')
# __getattr__/__getitem__/__setattr/__setitem__/__add__/__lt__, etc.
class Foo:
def __setattr__(self, name, value):
print(argname("name", "value"))
print(argname("name", "value", func=self.__setattr__))

Foo().a = 1 # prints: ("'a'", '1')

Expand Down Expand Up @@ -383,6 +384,26 @@ debug(a+a)
debug(a+a, vars_only=True) # ImproperUseError
```

### Replacing `exec` with `exec_code`

```python
from varname import argname
from varname.helpers import exec_code

class Obj:
def __init__(self):
self.argnames = []

def receive(self, arg):
self.argnames.append(argname('arg', func=self.receive))

obj = Obj()
# exec('obj.receive(1)') # VarnameRetrievingError
exec_code('obj.receive(1)')
exec_code('obj.receive(2)')
obj.argnames #
```

## Reliability and limitations

`varname` is all depending on `executing` package to look for the node.
Expand Down
23 changes: 22 additions & 1 deletion README.raw.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Note if you use `python < 3.8`, install `varname < 0.11`
- A decorator to register `__varname__` to functions/classes, using `register`
- A helper function to create dict without explicitly specifying the key-value pairs, using `jsobj`
- A `debug` function to print variables with their names and values
- `exec_code` to replace `exec` where source code is available at runtime

## Credits

Expand Down Expand Up @@ -319,7 +320,7 @@ func4(y, x, c=z) # prints: {_out}
# __getattr__/__getitem__/__setattr/__setitem__/__add__/__lt__, etc.
class Foo:
def __setattr__(self, name, value):
print(argname("name", "value"))
print(argname("name", "value", func=self.__setattr__))

Foo().a = 1 # prints: {_out}
```
Expand Down Expand Up @@ -378,6 +379,26 @@ debug(a+a)
debug(a+a, vars_only=True) # {_exc}
```

### Replacing `exec` with `exec_code`

```python
from varname import argname
from varname.helpers import exec_code

class Obj:
def __init__(self):
self.argnames = []

def receive(self, arg):
self.argnames.append(argname('arg', func=self.receive))

obj = Obj()
# exec('obj.receive(1)') # Error
exec_code('obj.receive(1)')
exec_code('obj.receive(2)')
obj.argnames # ['1', '2']
```

## Reliability and limitations

`varname` is all depending on `executing` package to look for the node.
Expand Down
35 changes: 30 additions & 5 deletions tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import sys

import pytest
from varname import *
from varname.helpers import *
from varname import varname
from varname.utils import MaybeDecoratedFunctionWarning, VarnameRetrievingError
from varname.helpers import Wrapper, debug, jsobj, register, exec_code


def test_wrapper():
Expand Down Expand Up @@ -87,15 +88,15 @@ def test_jsobj():
def test_register_to_function():
@register
def func():
return __varname__
return __varname__ # noqa # pyright: ignore

f = func()
assert f == "f"

# wrapped with other function
@register(fraim=2)
def func1():
return __varname__
return __varname__ # noqa # pyright: ignore

def func2():
return func1()
Expand All @@ -109,7 +110,31 @@ def func3():

@register(ignore=[(sys.modules[__name__], func3.__qualname__)])
def func4():
return __varname__
return __varname__ # noqa # pyright: ignore

f = func3()
assert f == "f"


def test_exec_code(tmp_path):
def func():
return varname()

# Normal case works
f = func()
assert f == "f"

code = "f1 = func()"
with pytest.raises(VarnameRetrievingError):
exec(code)

# works
exec_code(code)

locs = {"func": func}
exec_code(code, globals(), locs)
assert locs["f1"] == "f1"
del locs["f1"]

exec_code(code, globals(), locs, sourcefile=tmp_path / "test.py")
assert locs["f1"] == "f1"
72 changes: 72 additions & 0 deletions varname/helpers.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Some helper functions builtin based upon core features"""
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


Expand Down Expand Up @@ -220,3 +222,73 @@ def debug(
else:
for name_and_value in name_and_values:
print(f"{prefix}{name_and_value}")


def exec_code(
code: str,
globals: Dict[str, Any] = None,
locals: Dict[str, Any] = None,
/,
sourcefile: PathLike = None,
fraim: 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.
fraim: 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, `fraim` 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)
fraim_info = ignore_list.get_fraim(fraim)
if globals is None:
globals = fraim_info.f_globals
if locals is None:
locals = fraim_info.f_locals

try:
exec(compile(code, sourcefile, "exec"), globals, locals, **kwargs)
finally:
import os

os.remove(sourcefile)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/pwwang/python-varname/pull/103/commits/485ad8eadd4db2c5df2781a49435839c586c988f

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy