I would like to ensure that the class is only instantiated within a \"with\" statement.
i.e. this one is ok:
with X() as x:
...
an
Here is a decorator that automates making sure methods aren't called outside of a context manager:
from functools import wraps
BLACKLIST = dir(object) + ['__enter__']
def context_manager_only(cls):
original_init = cls.__init__
def init(self, *args, **kwargs):
original_init(self, *args, **kwargs)
self._entered = False
cls.__init__ = init
original_enter = cls.__enter__
def enter(self):
self._entered = True
return original_enter(self)
cls.__enter__ = enter
attrs = {name: getattr(cls, name) for name in dir(cls) if name not in BLACKLIST}
methods = {name: method for name, method in attrs.items() if callable(method)}
for name, method in methods.items():
def make_wrapper(method=method):
@wraps(method)
def wrapper_method(self, *args, **kwargs):
if not self._entered:
raise Exception("Didn't get call to __enter__")
return method(self, *args, **kwargs)
return wrapper_method
setattr(cls, name, make_wrapper())
return cls
And here is an example of it in use:
@context_manager_only
class Foo(object):
def func1(self):
print "func1"
def func2(self):
print "func2"
def __enter__(self):
print "enter"
return self
def __exit__(self, *args):
print "exit"
try:
print "trying func1:"
Foo().func1()
except Exception as e:
print e
print "trying enter:"
with Foo() as foo:
print "trying func1:"
foo.func1()
print "trying func2:"
foo.func2()
print "trying exit:"
This was written as an answer to this duplicate question.