Python How to force object instantiation via Context Manager?

contextmanager, python

Solution

If you consider that your clients will follow basic python coding principles then you can guarantee that no method from your class will be called if you are not within the context.

Your client is not supposed to call `__enter__` explicitly, therefore if `__enter__` has been called you know your client used a `with` statement and is therefore inside context (`__exit__` will be called).

You just need to have a boolean variable that helps you remember if you are inside or outside context.

class Obj:
    def __init__(self):
        self._inside_context = False

    def __enter__(self):
        self._inside_context = True
        print("Entering context.")
        return self

    def __exit__(self, *exc):
        print("Exiting context.")
        self._inside_context = False

    def some_stuff(self, name):
        if not self._inside_context:
            raise Exception("This method should be called from inside context.")
        print("Doing some stuff with", name)

    def some_other_stuff(self, name):
        if not self._inside_context:
            raise Exception("This method should be called from inside context.")
        print("Doing some other stuff with", name)


with Obj() as inst_a:
    inst_a.some_stuff("A")
    inst_a.some_other_stuff("A")

inst_b = Obj()
with inst_b:
    inst_b.some_stuff("B")
    inst_b.some_other_stuff("B")

inst_c = Obj()
try:
    inst_c.some_stuff("c")
except Exception:
    print("Instance C couldn't do stuff.")
try:
    inst_c.some_other_stuff("c")
except Exception:
    print("Instance C couldn't do some other stuff.")

This will print:

Entering context.
Doing some stuff with A
Doing some other stuff with A
Exiting context.
Entering context.
Doing some stuff with B
Doing some other stuff with B
Exiting context.
Instance C couldn't do stuff.
Instance C couldn't do some other stuff.

Since you'll probably have many methods that you want to "protect" from being called from outside context, then you can write a decorator to avoid repeating the same code to test for your boolean:

def raise_if_outside_context(method):
    def decorator(self, *args, **kwargs):
        if not self._inside_context:
            raise Exception("This method should be called from inside context.")
        return method(self, *args, **kwargs)
    return decorator

Then change your methods to:

@raise_if_outside_context
def some_other_stuff(self, name):
    print("Doing some other stuff with", name)

Problem

I want to force object instantiation via class context manager. So make it impossible to instantiate directly. I implemented this solution, but technically user can still instantiate object. ``` class HessioFile: """ Represents a pyhessio file instance """ def __init__(self, filename=None, from_context_manager=False): if not from_context_manager: raise HessioError('HessioFile can be only use with context manager') ``` And context manager: ``` @contextmanager def open(filename): """ ... """ hessfile = HessioFile(filename, from_context_manager=True) ``` Any better solution ?

Original source