-
Notifications
You must be signed in to change notification settings - Fork 108
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
ContextManager + access the decorated function in __enter__ #67
Comments
Doing |
Of course, you are right... it does work if you use a different decorator instance but not if you reuse the same decorator. Just for reference, full example: import contextlib
import pathlib
import uuid
import decorator
class ContextManager(contextlib._GeneratorContextManager):
def __init__(self, g, *a, **k):
return contextlib._GeneratorContextManager.__init__(self, g, a, k)
def __call__(self, func):
self.__func__ = func
return decorator.FunctionMaker.create(
func,
"with _self_: return _func_(%(shortsignature)s)",
dict(_self_=self, _func_=func),
__wrapped__=func,
)
class CMFactory(ContextManager):
def __init__(self, default_name=None):
self.default_name = default_name or uuid.uuid4().hex
@property
def name(self):
return getattr(self, "__func__", self.default_name)
def __enter__(self):
print(f"Entering {self.name}")
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
print(f"Exiting {self.name}")
print() This works: @CMFactory()
def func1():
print("inside func")
@CMFactory()
def func2():
print("inside func")
with CMFactory():
print("Inside context")
func1()
func2() This doesn't cm = CMFactory()
@cm
def func1():
print("inside func")
@cm
def func2():
print("inside func")
with cm:
print("Inside context")
func1()
func2() The funny thing is that this works, too, but for the wrong reasons): cm = CMFactory()
@cm
def func1():
print("inside func")
func1()
@cm
def func2():
print("inside func")
func2() I guess, someone could prevent re-using the decorator, but it starts to feel rather hackish + it reduces the scope of the def __call__(self, func):
if hasattr(self, "__func__"):
raise ValueError("You can't reuse this decorator, please create a new instance")
self.__func__ = func
return decorator.FunctionMaker.create(
func,
"with _self_: return _func_(%(shortsignature)s)",
dict(_self_=self, _func_=func),
__wrapped__=func,
) Thank you Michele. If there are no workarounds, feel free to close |
There are solutions, for instance to save in the context manager a dictionary |
From what I've seen so far, I would argue that you shouldn't)
Short answer: I have a context manager that I want to also use as a decorator. The context manager creates some resources in a directory that needs to have a unique name. When it is used as a decorator though, it would make sense to use the decorated function's name to make it easy to associate the directory with the function (e.g. using Longer answer: I am refactoring some tests that use a custom extension framework to class CustomTestCase(unittest.TestCase):
pass
class FooTestCase(CustomTestCase):
def test1(self):
pass
def test2(self):
pass So, I need to create a "session" for each test that among other things, will create a temp directory that will get removed after the test finishes. Ideally, I would also like to be able to optionally keep the directory for inspection (e.g. when the tests are failing). Now, I already have a context manager that implements this. E.g with temp_session(name='my_temp_session", cleanup=True):
# This context manager creates a directory named "my_temp_session" which gets removed on exit
# if `cleanup=False` the directory will not get removed. So I wanted to reuse this as a decorator that would do the same but which instead of a random name for the temp directory would use the name of the decorated function. This would make it easier to associate the directory with each failing test. # contents of tests/test_foo.py
class FooTestCase(unittest.TestCase):
@temp_session() # this would create a directory named "tests_test_foo_Foo_TestCase_test1"
def test1(self):
pass
@temp_session(cleanup=False) # this would create a directory named "tests_test_foo_Foo_TestCase_test2"
def test2(self):
pass Of course I could implement both a context manager and a decorator and move on - which is what I am probably going to do. Or just use random names and be done with it. But the idea of using the same class both for the context manager and the decorator was tempting)
I am probably missing something obvious here - I also thought about creating a registry of some sort - but I think that the problem remains; i.e. you can't access |
I also have tests that require creating a temporary directory. What I did was to introduce a custom subclass of unittest.TestCase with a redefined import os
import tempfile
import unittest
import shutil
tmp = tempfile.gettempdir() # /tmp, but it would be preferable to create a new directory
class BaseTestCase(unittest.TestCase):
def run(self, result=None):
dtemp = os.path.join(tmp, self._testMethodName)
if not os.path.exists(dtemp):
os.mkdir(dtemp)
print('creating', dtemp)
result = super().run()
shutil.rmtree(dtemp)
print('removing', dtemp)
return result
class ExampleTestCase(BaseTestCase):
def test1(self):
pass
def test2(self):
pass that you can run with pytest: $ pytest -s x.py
============================= test session starts ==============================
platform linux -- Python 3.6.7, pytest-4.2.0, py-1.7.0, pluggy-0.8.1
rootdir: /home/michele, inifile:
plugins: xdist-1.27.0, forked-1.0.2, celery-4.1.0
collected 2 items
x.py creating /tmp/test1
removing /tmp/test1
.creating /tmp/test2
removing /tmp/test2
. |
I have a context manager that creates some resources and cleans them up on exit. I would like, if possible, to use it as a decorator, too. But when I do, I also need to have access to the name of the decorated function. I managed to do this by subclassing
decorator.ContextManager
and overriding__call__
:decorator.ContextManager
?The text was updated successfully, but these errors were encountered: