Stefano Borini
Stefano Borini

Reputation: 143865

How can I use a context manager to define an actual context for instantiation of new classes?

I have the following problem. I have multiple unittests, each one having a context manager to open a browser and do some selenium testing.

I want to ensure that I can run the tests in parallel, and that the browser window is closed in case of error, so I use a context manager:

def test_xxx():
    with webapp() as p:                                                                   
        file_loader = FileLoader(p, id="loader").upload(path)

As you can see, I use a class FileLoader, which takes the context manager web application (basically a wrapper of the selenium driver) and uses it to encapsulate the rigmarole required to upload the file.

My objective would be not to have to specify the p parameter to FileLoader(), so that I could write

def test_xxx():
    with webapp():                                                                   
        file_loader = FileLoader(id="loader").upload(path)

I could use a global that is assigned when the context manager is opened, but this would prevent any isolation when tests run in parallel. Suppose that one test connects to site A, and another test connects to site B. I need two drivers, each connected to a different site.

In other words, how can I design FileLoader to be aware of its enclosing context manager without passing the context variable?

Upvotes: 0

Views: 239

Answers (1)

Serge Ballesta
Serge Ballesta

Reputation: 149075

By using the inspect module, a code can read the local variables of its caller. It is a rather unusual if not dangerous use, because it actually boils down to a non standard and non conventional way of passing a parameter to a function. But if you really want to go that way, this other SO question gives a possible way.

Demo:

class ContextAware:
    """ Class that will copy  the x local variable of its caller if any"""
    def __init__(self):
        # uncomment next line for debugging
        # print("ContextAware", inspect.currentframe().f_back.f_locals)
        self.x = inspect.currentframe().f_back.f_locals.get('x')

        
def foo(x):
    # the value of x is not explicitely passed, yet it will be used...
    return ContextAware()

The object created in foo is aware of the x variable of its caller:

>>> a = foo(4)
>>> a.x
4
>>> a = foo(6)
>>> a.x
6

That means you you could write something close to:

def test_xxx():
    with webapp() as ctx_p:
        file_loader = FileLoader(id="loader").upload(path)

provided the __init__ method of FileLoader spies on the ctx_p local variable of its caller.

Upvotes: 3

Related Questions