Skip to content
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

How to write a wrapper around these functions? #111

Closed
oliversheridanmethven opened this issue Jul 18, 2024 · 6 comments
Closed

How to write a wrapper around these functions? #111

oliversheridanmethven opened this issue Jul 18, 2024 · 6 comments

Comments

@oliversheridanmethven
Copy link

jsobj returns a dictionary. If I wanted to write a decorator which returns the .items() of whatever it is decorating (presumably a function which returns dictionaries), how would I do this, as my naive implementation didn't work. Can the functions in this module even be decorated in "the usual" manner?

My attempt:

from varname.helpers import jsobj
from functools import wraps
import unittest

def return_dict_items(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).items()
    return wrapper

class TestWrapper(unittest.TestCase):
    def test_wrapper(self):
        foo = return_dict_items(jsobj)
        a = 1
        self.assertEqual([("a", 1)], list(foo(a)))


if __name__ == '__main__':
    unittest.main()

However:

Expected :[('a', 1)]
Actual   :[(<ast.Starred object at 0x10b96f850>, 1)]
@pwwang
Copy link
Owner

pwwang commented Jul 18, 2024

Consider how jsobj was actually implemented:

def jsobj(*args: Any, vars_only: bool = True, **kwargs: Any) -> Dict[str, Any]:
    argnames: Tuple[str, ...] = argname("args", vars_only=vars_only)
    out = dict(zip(argnames, args))
    out.update(kwargs)
    return out

When you wrap jsobj, then the default frame passed to argname is not 1 anymore, you will need to implement a new jsobj by yourself by passing frame=2 to argname:

from varname import argname
from varname.helpers import jsobj
from functools import wraps
import unittest


def jsobj_to_be_wrapped(*args, vars_only=True, **kwargs):
    argnames = argname("args", vars_only=vars_only, frame=2)
    out = dict(zip(argnames, args))
    out.update(kwargs)
    return out


def return_dict_items(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs).items()
    return wrapper

class TestWrapper(unittest.TestCase):
    def test_wrapper(self):
        foo = return_dict_items(jsobj_to_be_wrapped)
        a = 1
        self.assertEqual([("a", 1)], list(foo(a)))


if __name__ == '__main__':
    unittest.main()

@oliversheridanmethven
Copy link
Author

Seems a little "off" to have two versions of a function, one that can be wrapped and another which can't, but considering playing about with frames and stacks and the like is always a bit "hacky" it's not the end of the world.

@oliversheridanmethven
Copy link
Author

Also, this has the drawback that this needs to know how many times it will be decorated, and I can't decorate it indefinitely (or not at all).

@pwwang
Copy link
Owner

pwwang commented Jul 18, 2024

The frame argument was added to v0.13.3:

def myjsobj(*args, **kwargs):
    return jsobj(*args, vars_only=False, frame=2, **kwargs)

x = lambda: None
x.b = 2
obj = myjsobj(x.b)
assert obj == {"x.b": 2}

@pwwang
Copy link
Owner

pwwang commented Jul 18, 2024

Here is now it works in your case:

from varname.helpers import jsobj
from functools import wraps
import unittest

def return_dict_items(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs, vars_only=False, frame=2).items()
    return wrapper

class TestWrapper(unittest.TestCase):
    def test_wrapper(self):
        foo = return_dict_items(jsobj)
        a = 1
        self.assertEqual([("a", 1)], list(foo(a)))


if __name__ == '__main__':
    unittest.main()

@oliversheridanmethven
Copy link
Author

Perfect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants