Package: cs.context
Assorted context managers.Latest release 20240212.1:
Minor doc updates.Functioncontextif(flag, cmgr_func, *cmgr_args, **cmgr_kwargs)A context manager to callcmgr_func(*cmgr_args,**cmgr_kwargs)ifflagis true, otherwise to callnullcontext().The driving use case is verbosity dependent status lines or
progress bars, eg:from cs.upd import run_task
with contextif(verbose, run_task, ....) as proxy:
  ... do stuff, updating proxy if not None ...ClassContextManagerMixinA mixin to provide context manager__enter__and__exit__methods
running the first and second steps of a single__enter_exit__generator method.Note: the__enter_exit__method isnota context manager,
but a short generator method.This makes it easy to use context managers inside__enter_exit__as the setup/teardown process, for example:def __enter_exit__(self):
    with open(self.datafile, 'r') as f:
        yield fLike a context manager created via@contextmanagerit performs the setup phase and thenyields the value for thewithstatement.
IfNoneisyielded (as from a bareyield)
thenselfis returned from__enter__.
As with@contextmanager,
if there was an exception in the managed suite
then that exception is raised on return from theyield.However, andunlikea@contextmanagermethod,
the__enter_exit__generatormayalsoyieldan additional true/false value to use as the result
of the__exit__method, to indicate whether the exception was handled.
This extrayieldisoptionaland if it is omitted the__exit__result
will beFalseindicating that an exception was not handled.Here is a sketch of a method which can handle aSomeExceptionspecially:class CMgr(ContextManagerMixin):
    def __enter_exit__(self):
        ... do some setup here ...
        # Returning self is common, but might be any relevant value.
        # Note that if you want `self`, you can just use a bare yield
        # and ContextManagerMixin will provide `self` as the default.
        enter_result = self
        exit_result = False
        try:
            yield enter_result
        except SomeException as e:
            ... handle e ...
            exit_result = True
        finally:
            ... do tear down here ...
        yield exit_resultFunctionpop_cmgr(o, attr)Run the__exit__phase of a context manager commenced withpush_cmgr.
Restoreattras it was beforepush_cmgr.
Return the result of__exit__.Functionpopattrs(o, attr_names, old_values)The "pop" part ofstackattrs.
Restore previous attributes ofonamed byattr_nameswith previous state inold_values.This can be useful in hooks/signals/callbacks,
where you cannot inline a context manager.Functionpopkeys(d, key_names, old_values)The "pop" part ofstackkeys.
Restore previous key values ofdnamed bykey_nameswith previous state inold_values.This can be useful in hooks/signals/callbacks,
where you cannot inline a context manager.Functionpush_cmgr(o, attr, cmgr)A convenience wrapper fortwostep(cmgr)to run the__enter__phase ofcmgrand save its value aso.attr. Return the result of theenter` phase.The__exit__phase is run bypop_cmgr(o,attr),
returning the return value of the exit phase.Example use in a unit test:class TestThing(unittest.TestCase):
    def setUp(self):
        # save the temp dir path as self.dirpath
        push_cmgr(self, 'dirpath', TemporaryDirectory())
    def tearDown(self):
        # clean up the temporary directory, discard self.dirpath
        pop_cmgr(self, 'dirpath')Thecs.testutilsSetupTeardownMixinclass does this
allowing the provision of a singlesetupTeardown()context manager method
for test case setUp/tearDown.Doc test:>>> from os.path import isdir as isdirpath
>>> from tempfile import TemporaryDirectory
>>> from types import SimpleNamespace
>>> obj = SimpleNamespace()
>>> dirpath = push_cmgr(obj, 'path', TemporaryDirectory())
>>> assert dirpath == obj.path
>>> assert isdirpath(dirpath)
>>> pop_cmgr(obj, 'path')
>>> assert not hasattr(obj, 'path')
>>> assert not isdirpath(dirpath)Functionpushattrs(o, **attr_values)The "push" part ofstackattrs.
Pushattr_valuesontooas attributes,
return the previous attribute values in a dict.This can be useful in hooks/signals/callbacks,
where you cannot inline a context manager.Functionpushkeys(d, **key_values)The "push" part ofstackkeys.
Pushkey_valuesontodas key values.
return the previous key values in a dict.This can be useful in hooks/signals/callbacks,
where you cannot inline a context manager.Functionreconfigure_file(f, **kw)Context manager flavour ofTextIOBase.reconfigure.Functionsetup_cmgr(cmgr)Run the set up phase of the context managercmgrand return a callable which runs the tear down phase.This is a convenience wrapper for the lower leveltwostep()function
which produces a two iteration generator from a context manager.Please see thepush_cmgrfunction, a superior wrapper fortwostep().Note:
this function expectscmgrto be an existing context manager.
In particular, if you define some context manager function like this:@contextmanager
def my_cmgr_func(...):
    ...
    yield
    ...then the correct use ofsetup_cmgr()is:teardown = setup_cmgr(my_cmgr_func(...))andnot:cmgr_iter = setup_cmgr(my_cmgr_func)
...The purpose ofsetup_cmgr()is to split any context manager's operation
across two steps when the set up and teardown phases must operate
in different parts of your code.
A common situation is the__enter__and__exit__methods
of another context manager class.The call tosetup_cmgr()performs the "enter" phase
and returns the tear down callable.
Calling that performs the tear down phase.Example use in a class:class SomeClass:
    def __init__(self, foo)
        self.foo = foo
        self._teardown = None
    def __enter__(self):
        self._teardown = setup_cmgr(stackattrs(o, setting=foo))
    def __exit__(self, *_):
        teardown, self._teardown = self._teardown, None
        teardown()Functionstack_signals(signums, handler, additional=False)Context manager to apply a handler function tosignumsusingsignal.signal.
The old handlers are restored on exit from the context manager.If the optionaladditionalargument is true,
apply a handler which calls both the new handler and the old handler.Functionstackattrs(o, **attr_values)Context manager to push new values for the attributes ofoand to restore them afterward.
Returns adictcontaining a mapping of the previous attribute values.
Attributes not present are not present in returned mapping.Restoration includes deleting attributes which were not present
initially.This makes it easy to adjust temporarily some shared context object
without having to pass it through the call stack.Seestackkeysfor a flavour of this for mappings.Seecs.threads.Statefor a convenient wrapper class.Example of fiddling a programme's "verbose" mode:>>> class RunModes:
...     def __init__(self, verbose=False):
...         self.verbose = verbose
...
>>> runmode = RunModes()
>>> if runmode.verbose:
...     print("suppressed message")
...
>>> with stackattrs(runmode, verbose=True):
...     if runmode.verbose:
...         print("revealed message")
...
revealed message
>>> if runmode.verbose:
...     print("another suppressed message")
...Example exhibiting restoration of absent attributes:>>> class O:
...     def __init__(self):
...         self.a = 1
...
>>> o = O()
>>> print(o.a)
1
>>> print(o.b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'O' object has no attribute 'b'
>>> with stackattrs(o, a=3, b=4):
...     print(o.a)
...     print(o.b)
...     o.b = 5
...     print(o.b)
...     delattr(o, 'a')
...
3
4
5
>>> print(o.a)
1
>>> print(o.b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'O' object has no attribute 'b'Functionstackkeys(d, **key_values)Context manager to push new values for the key values ofdand to restore them afterward.
Returns adictcontaining a mapping of the previous key values.
Keys not present are not present in the mapping.Restoration includes deleting key values which were not present
initially.This makes it easy to adjust temporarily some shared context object
without having to pass it through the call stack.Seestackattrsfor a flavour of this for object attributes.Example of making log entries which may reference
some higher level context log entry:>>> import time
>>> global_context = {
...     'parent': None,
... }
>>> def log_entry(desc, **kw):
...     print("log_entry: global_context =", repr(global_context))
...     entry = dict(global_context)
...     entry.update(desc=desc, when=time.time())
...     entry.update(kw)
...     return entry
...
>>> log_entry("stand alone entry")    #doctest: +ELLIPSIS
log_entry: global_context = {'parent': None}
{'parent': None, 'desc': 'stand alone entry', 'when': ...}
>>> context_entry = log_entry("high level entry")
log_entry: global_context = {'parent': None}
>>> context_entry                     #doctest: +ELLIPSIS
{'parent': None, 'desc': 'high level entry', 'when': ...}
>>> with stackkeys(global_context, parent=context_entry): #doctest: +ELLIPSIS
...     print(repr(log_entry("low level event")))
...
log_entry: global_context = {'parent': {'parent': None, 'desc': 'high level entry', 'when': ...}}
{'parent': {'parent': None, 'desc': 'high level entry', 'when': ...}, 'desc': 'low level event', 'when': ...}
>>> log_entry("another standalone entry")    #doctest: +ELLIPSIS
log_entry: global_context = {'parent': None}
{'parent': None, 'desc': 'another standalone entry', 'when': ...}Functionstackset(s, element, lock=None)Context manager to addelementto the setsand remove it on return.
The element is neither added nor removed if it is already present.Functiontwostep(cmgr)Return a generator which operates the context managercmgr.The first iteration performs the "enter" phase and yields the result.
The second iteration performs the "exit" phase and yieldsNone.See also thepush_cmgr(obj,attr,cmgr)function
and its partnerpop_cmgr(obj,attr)which form a convenient wrapper for this low level generator.The purpose oftwostep()is to split any context manager's operation
across two steps when the set up and tear down phases must operate
in different parts of your code.
A common situation is the__enter__and__exit__methods
of another context manager class
or thesetUpandtearDownmethods of a unit test case.Note:
this function expectscmgrto be an existing context manager
andnotthe function which returns the context manager.In particular, if you define some function like this:@contextmanager
def my_cmgr_func(...):
    ...
    yield
    ...then the correct use oftwostep()is:cmgr_iter = twostep(my_cmgr_func(...))
next(cmgr_iter)   # set up
next(cmgr_iter)   # tear downandnot:cmgr_iter = twostep(my_cmgr_func)
next(cmgr_iter)   # set up
next(cmgr_iter)   # tear downExample use in a class (but really you should usepush_cmgr/pop_cmgrinstead):class SomeClass:
    def __init__(self, foo)
        self.foo = foo
        self._cmgr_ = None
    def __enter__(self):
        self._cmgr_stepped = twostep(stackattrs(o, setting=foo))
        self._cmgr = next(self._cmgr_stepped)
        return self._cmgr
    def __exit__(self, *_):
        next(self._cmgr_stepped)
        self._cmgr = NoneRelease LogRelease 20240212.1:
Minor doc updates.Release 20240212:
New reconfigure_file(f,**kw), a context manager flavour ofTextIOBase.reconfigure.Release 20240201:
contextif: require the flag to be a bool.Release 20230331:
stackset: accept optional lock to guard modification of the set.Release 20230212:BREAKING: drop StackableState, superceded by cs.threads.State.New stackset(set,element) to push and then pop an element to a set unless it is already there.Release 20230125:
New ContextManagerMixin.as_contextmanager(cls,self) class method to run theenter_exitfrom a specific class, useful in subclasses.Release 20230109:
New contextif(flag,cmgr_func,...) context manager to use cmgr_func if flag is true otherwise nullcontext.Release 20221118:
stackattrs: improve docstring.Release 20220619:
twostep: the returned "tear down" phase function needs to ignore StopIteration from the context manager, see PEP 479.Release 20220227:
New stack_signals context manager to push signal handlers.Release 20211115.1:
Docstring grammar/phrasing updates.Release 20211115:
Renameenter_exitto__enter_exit__- the user doesn't call this overtly and it aligns better with__enter__and__exit__.Release 20211114.1:
ContextManagerMixin: the defaultenterreturn is self, supporting a trivial bareyieldin the generator.Release 20211114:
New ContextManagerMixin mixin class to implement theenter/exitmethods using a simple generator function named enter_exit.Release 20210727:twostep: iteration 1 now returns the result ofenter, iteration 2 now returns None.New functions push_cmgr(obj,attr,cmgr) and partner pop_cmgr(obj,attr) to run a twostep()ed context manager conveniently, more conveniently than setup_cmgr().Release 20210420.1:
Rerelease after completing stalled merge: docstring updates.Release 20210420:
Docstring corrections and improvements.Release 20210306:New twostep() and setup_cmgr() functions to split a context manager into set up and teardown phases for when these must occur in different parts of the code.New thread local StackableState class which can be called to push attribute changes with stackattrs - intended for use as shared global state to avoiod passing through deep function call chains.Release 20200725.1:
Docstring improvements.Release 20200725:
New stackkeys and components pushkeys and popkeys doing "stackattrs for dicts/mappings".Release 20200517:Addnullcontextlike the one from recent contextlib.stackattrs: expose the push and pop parts as pushattrs() and popattrs().Release 20200228.1:
Initial release with stackattrs context manager.