LayeredContext¶
-
class
privex.helpers.common.
LayeredContext
(wrapped_class: K, max_layers: Optional[int] = 1, fail: bool = False)[source]¶ A wrapper class for context manager classes / functions which allows you to control how many
with
layers that a context manager can have - and allow for the previous layer’s context manager__enter__
/yield
result to be passed down whenmax_layers
is hit.(context managers are classes/functions with the methods
__enter__
/__exit__
/__aenter__
/__aexit__
etc.)Works with context manager classes, asyncio context manager classes, and
contextlib.contextmanager()
functions.By default,
LayeredContext
setsmax_layers
to1
, meaning after 1 layer ofwith
orasync with
statements, all additional layers will simply get given the same context result as the 1st layer, plus both__enter__
and__exit__
will only be called once (at the start and end of the first layer).Using with class-based context managers:
>>> class Hello: ... def __enter__(self): ... print('entering Hello') ... return self ... def __exit__(self, exc_type, exc_val, exc_tb): ... print('exiting Hello') >>> ctx_a = LayeredContext(Hello()) >>> with ctx_a as a: ... print('class manager layer 1') ... with ctx_a as b: ... print('class manager layer 2') ... print('back to class layer 1') entering Hello class manager layer 1 class manager layer 2 back to class layer 1 exiting Hello
We can see that
entering Hello
andexiting Hello
were only outputted at the end of the first context blockwith ctx_a as a
, showing thatHello
was only entered/exited as a context manager for the firstwith
block.Using with function-based :func:`contextlib.contextmanager` context managers:
>>> from contextlib import contextmanager >>> @contextmanager >>> def lorem(): ... print('entering lorem contextmanager') ... yield 'hello world' ... print('exiting lorem contextmanager') >>> ctx_b = LayeredContext(lorem()) >>> with ctx_b as c: ... print('function manager layer 1 - context is:', c) ... with ctx_b as d: ... print('function manager layer 2 - context is:', d) ... print('back to function layer 1') entering lorem contextmanager function manager layer 1 - context is: hello world function manager layer 2 - context is: hello world back to function layer 1 exiting lorem contextmanager
We can see the default
max_layers
of1
was respected, as the 2nd layerwith ctx_b as d
only printedfunction manager layer 2
(thuslorem
’s enter/exit methods were not called), and it shows the context is stillhello world
(the context yielded bylorem
in layer 1).Example usage
First we need an example class which can be used as a context manager, so we create
Example
with a very simple__enter__
and__exit__
method, which simply adds and subtracts fromself.ctx_layer
respectively:>>> class Example: ... def __init__(self): ... self.ctx_layer = 0 ... def __enter__(self): ... self.ctx_layer += 1 ... return self ... def __exit__(self, exc_type, exc_val, exc_tb): ... if self.ctx_layer <= 0: raise ValueError('ctx_layer <= 0 !!!') ... self.ctx_layer -= 1 ... return None
If we then create an instance of
Example
, and use it as a context manager in a 2 layer nestedwith exp
, we can seectx_layer
gets increased each time we use it as a context manager, and decreases after the context manager block:>>> exp = Example() >>> with exp as x: ... print(x.ctx_layer) # prints: 1 ... with exp as y: ... print(y.ctx_layer) # prints: 2 ... print(x.ctx_layer) # prints: 1 >>> exp.ctx_layer 0
Now, lets wrap it with
LayeredContext
, and set the maximum amount of layers to1
. If we start usingctx
as a context manager, it works as if we used the example instanceexp
as a context manager. But, unlike the real instance,__enter__
is only really called for the firstwith
block, and__exit__
is only really called once we finish the first layerwith ctx as x
>>> ctx = LayeredContext(exp, max_layers=1) >>> with ctx as x: ... print(x.ctx_layer) # prints: 1 ... with ctx as y: ... print(y.ctx_layer) # prints: 1 ... print(ctx.virtual_layer) # prints: 2 ... print(x.ctx_layer) # prints: 1 ... print(ctx.virtual_layer) # prints: 1 >>> exp.ctx_layer 0 >>> print(ctx.layer, ctx.virtual_layer) 0 0
-
__init__
(wrapped_class: K, max_layers: Optional[int] = 1, fail: bool = False)[source] Construct a
LayeredContext
instance, wrapping the context manager class instance or func:contextlib.contextmanager manager functionwrapped_class
.- Parameters
wrapped_class (K|object) – A context manager class or
contextlib.contextmanager()
manager function to wrapmax_layers (int) – Maximum layers of
(async) with
blocks before silently consuming further attempts to enter/exit the context manager forwrapped_class
fail (bool) – (default:
False
) WhenTrue
, will raiseNestedContextException
when anenter()
call is going to cause more thanmax_layers
context manager layers to be active.
-