Skip to main content

Resource management classes and functions.

Project description

Resource management classes and functions.

Latest release 20240721: The MutliOpen wrapper class is now obsolete (NB: not the MultiOpenMixin class).

Class ClosedError(builtins.Exception)

Exception for operations which are invalid when something is closed.

MultiOpen(openable, finalise_later=False)

OBSOLETE MultiOpen

A context manager class that manages a single-open/close object using a MultiOpenMixin.

 Use:

     mo = MultiOpen(obj)
     ......
     with mo:
          .... use obj ...

 This required `obj` to have a `.open()` method which can
 be called with no arguments (which is pretty uncommon)
 and a `.close()` method.

Class MultiOpenMixin(cs.context.ContextManagerMixin)

A multithread safe mixin to count open and close calls, doing a startup on the first .open and shutdown on the last .close.

If used as a context manager this mixin calls open()/close() from __enter__() and __exit__().

It is recommended that subclass implementations do as little as possible during __init__, and do almost all setup during startup so that the class may perform multiple startup/shutdown iterations.

Classes using this mixin should define a context manager method .startup_shutdown which does the startup actions before yielding and then does the shutdown actions.

Example:

class DatabaseThing(MultiOpenMixin):
    @contextmanager
    def startup_shutdown(self):
        self._db = open_the_database()
        try:
            yield
        finally:
            self._db.close()
...
with DatabaseThing(...) as db_thing:
    ... use db_thing ...

If course, often something like a database open will itself be a context manager and the startup_shutdown method more usually looks like this:

    @contextmanager
    def startup_shutdown(self):
        with open_the_database() as db:
            self._db = db
            yield

Why not just write a plain context manager class? Because in multithreaded or async code one wants to keep the instance "open" while any thread is still using it. This mixin lets threads use an instance in overlapping fashion:

db_thing = DatabaseThing(...)
with db_thing:
    ... kick off threads with access to the db ...
...
thread 1:
with db_thing:
   ... use db_thing ...
thread 2:
with db_thing:
   ... use db_thing ...

TODO:

  • subopens: if true (default false) then .open will return a proxy object with its own .closed attribute set by the proxy's .close.

MultiOpenMixin.MultiOpenMixin_state: The state object for the mixin, something of a hack to avoid providing an __init__.

MultiOpenMixin.close(self, *, enforce_final_close=False, caller_frame=None, unopened_ok=False): Decrement the open count. If the count goes to zero, call self.shutdown() and return its value.

Parameters:

  • enforce_final_close: if true, the caller expects this to be the final close for the object and a RuntimeError is raised if this is not actually the case.
  • caller_frame: used for debugging; the caller may specify this if necessary, otherwise it is computed from cs.py.stack.caller when needed. Presently the caller of the final close is recorded to help debugging extra close calls.
  • unopened_ok: if true, it is not an error if this is not open. This is intended for closing callbacks which might get called even if the original open never happened. (I'm looking at you, cs.resources.RunState.)

MultiOpenMixin.closed: Whether this object has been closed. Note: False if never opened.

MultiOpenMixin.is_open(self): Test whether this object is open.

MultiOpenMixin.is_opened(func): Decorator to wrap MultiOpenMixin proxy object methods which should raise if the object is not yet open.

MultiOpenMixin.join(self): Join this object.

Wait for the internal finalise Condition (if still not None). Normally this is notified at the end of the shutdown procedure unless the object's finalise_later parameter was true.

MultiOpenMixin.open(self, caller_frame=None): Increment the open count. On the first .open call self.startup().

MultiOpenMixin.startup_shutdown(self): Default context manager form of startup/shutdown - just call the distinct .startup() and .shutdown() methods if both are present, do nothing if neither is present.

This supports subclasses always using:

with super().startup_shutdown():

as an outer wrapper.

The .startup check is to support legacy subclasses of MultiOpenMixin which have separate startup() and shutdown() methods. The preferred approach is a single startup_shutdwn() context manager overriding this method.

The usual form looks like this:

@contextmanager
def startup_shutdown(self):
    with super().startup_shutdown():
        ... do some set up ...
        try:
            yield
        finally:
            ... do some tear down ...

MultiOpenMixin.tcm_get_state(self): Support method for TrackedClassMixin.

not_closed(*da, **dkw)

A decorator to wrap methods of objects with a .closed property which should raise when self.closed.

openif(obj)

Context manager to open obj if it has a .open method and also to close it via its .close method. This yields obj.open() if defined, or obj otherwise.

Class Pool

A generic pool of objects on the premise that reuse is cheaper than recreation.

All the pool objects must be suitable for use, so the new_object callable will typically be a closure. For example, here is the init for a per-thread AWS Bucket using a distinct Session:

def __init__(self, bucket_name):
    Pool.__init__(self, lambda: boto3.session.Session().resource('s3').Bucket(bucket_name)

Pool.__init__(self, new_object, max_size=None, lock=None): Initialise the Pool with creator new_object and maximum size max_size.

Parameters:

  • new_object is a callable which returns a new object for the Pool.
  • max_size: The maximum size of the pool of available objects saved for reuse. If omitted or None, defaults to 4. If 0, no upper limit is applied.
  • lock: optional shared Lock; if omitted or None a new Lock is allocated

Pool.instance(self): Context manager returning an object for use, which is returned to the pool afterwards.

Class RunState(cs.fsm.FSM, cs.threads.HasThreadState)

A class to track a running task whose cancellation may be requested.

Its purpose is twofold, to provide easily queriable state around tasks which can start and stop, and to provide control methods to pronounce that a task has started (.start), should stop (.cancel) and has stopped (.stop).

A RunState can be used as a context manager, with the enter and exit methods calling .start and .stop respectively. Note that if the suite raises an exception then the exit method also calls .cancel before the call to .stop.

Monitor or daemon processes can poll the RunState to see when they should terminate, and may also manage the overall state easily using a context manager. Example:

def monitor(self):
    with self.runstate:
        while not self.runstate.cancelled:
            ... main loop body here ...

A RunState has three main methods:

  • .start(): set .running and clear .cancelled
  • .cancel(): set .cancelled
  • .stop(): clear .running

A RunState has the following properties:

  • cancelled: true if .cancel has been called.
  • running: true if the task is running. Further, assigning a true value to it sets .start_time to now. Assigning a false value to it sets .stop_time to now.
  • start_time: the time .running was last set to true.
  • stop_time: the time .running was last set to false.
  • run_time: max(0,.stop_time-.start_time)
  • stopped: true if the task is not running.
  • stopping: true if the task is running but has been cancelled.
  • notify_start: a set of callables called with the RunState instance to be called whenever .running becomes true.
  • notify_end: a set of callables called with the RunState instance to be called whenever .running becomes false.
  • notify_cancel: a set of callables called with the RunState instance to be called whenever .cancel is called.
<title>RunState State Diagram</title> <title>IDLE</title> IDLE <title>IDLE->IDLE</title> cancel <title>RUNNING</title> RUNNING <title>IDLE->RUNNING</title> start <title>STOPPING</title> STOPPING <title>RUNNING->STOPPING</title> cancel <title>STOPPED</title> STOPPED <title>RUNNING->STOPPED</title> stop <title>STOPPING->STOPPING</title> cancel <title>STOPPING->STOPPED</title> stop <title>STOPPED->RUNNING</title> start <title>STOPPED->STOPPED</title> cancel
RunState State Diagram

RunState.__bool__(self): Return true if the task is running.

RunState.__enter_exit__(self): The __enter__/__exit__ generator function:

  • push this RunState via HasThreadState
  • catch signals if we are in the main Thread
  • start
  • yield self => run
  • cancel on exception during the run
  • stop

Note that if the RunState is already running we do not do any of that stuff apart from the yield self because we assume whatever setup should have been done has already been done. In particular, the HasThreadState.Thread factory calls this in the "running" state.

RunState.__nonzero__(self): Return true if the task is running.

RunState.cancel(self): Set the cancelled flag; the associated process should notice and stop.

RunState.cancelled: Test the .cancelled attribute, including a poll if supplied.

RunState.catch_signal(self, sig, call_previous=False, handle_signal=None): Context manager to catch the signal or signals sig and cancel this RunState. Restores the previous handlers on exit. Yield a mapping of sig=>old_handler.

Parameters:

  • sig: an int signal number or an iterable of signal numbers
  • call_previous: optional flag (default False) passed to cs.psutils.signal_handlers

RunState.end(self): OBSOLETE end

Obsolete synonym for .stop().

RunState.fsm_event(self, event: str, **extra): Override FSM.fsm_event to apply side effects to particular transitions.

On 'cancel' set the cancelled flag. On 'start' clear the cancelled flag and set .start_time. On 'stop'set .stop_time.

RunState.handle_signal(self, sig, _): RunState signal handler: cancel the run state. Warn if self.verbose.

RunState.iter(self, it): Iterate over it while not self.cancelled.

RunState.perthread_state

RunState.raiseif(self, msg=None, *a): Raise CancellationError if cancelled. This is the concise way to terminate an operation which honours .cancelled if you're prepared to handle the exception.

Example:

for item in items:
    runstate.raiseif()
    ... process item ...

RunState.run_time: A property returning most recent run time (stop_time-start_time). If still running, use now as the stop time. If not started, return 0.0.

RunState.running: Whether the state is 'RUNNING' or 'STOPPING'.

RunState.start(self, running_ok=False): Start: adjust state, set start_time to now. Sets .cancelled to False and sets .running to True.

RunState.state: OBSOLETE state

The RunState's state as a string. Deprecated, new uses should consult self.fsm_state.

RunState.stop(self): Fire the 'stop' event.

RunState.stopping: Is the process stopping?

Class RunStateMixin

Mixin to provide convenient access to a RunState.

Provides: .runstate, .cancelled, .running, .stopping, .stopped.

RunStateMixin.__init__(self, *, runstate: Union[cs.resources.RunState, str, NoneType] = <function uses_runstate.<locals>.<lambda> at 0x103bf0180>): Initialise the RunStateMixin; sets the .runstate attribute.

Parameters:

  • runstate: optional RunState instance or name. If a str, a new RunState with that name is allocated. If omitted, the default RunState is used.

RunStateMixin.cancel(self): Call .runstate.cancel().

RunStateMixin.cancelled: Test .runstate.cancelled.

RunStateMixin.running: Test .runstate.running.

RunStateMixin.stopped: Test .runstate.stopped.

RunStateMixin.stopping: Test .runstate.stopping.

uses_runstate(*da, **dkw)

A wrapper for @default_params which makes a new thread wide RunState parameter runstate if missing. The optional decorator parameter name may be used to specify a name for the new RunState if one is made. The default comes from the wrapped function's name.

Example:

@uses_runstate
def do_something(blah, *, runstate:RunState):
    ... do something, polling the runstate as approriate ...

Release Log

Release 20240721: The MutliOpen wrapper class is now obsolete (NB: not the MultiOpenMixin class).

Release 20240630:

  • @uses_runstate: now accepts an optional name= parameter which defaults to the name of the function being decorated, supplied to the RunState factory.
  • RunState.FSM_TRANSITIONS: allow IDLE->cancel->IDLE.
  • @not_closed: wrap in @decorator to set the wrapper name etc.
  • RunState: allow STOPPED->cancel->STOPPED transition.

Release 20240522:

  • @uses_runstate: if we make a new RunState, get the default name from the wrapped function.
  • RunState: bug fixes from the recent subclassing of cs.fsm.FSM.

Release 20240519: RunState now subclasses cs.fsm.FSM.

Release 20240423: RunStateMixin: make the optional runstate parameter keyword only.

Release 20240422: dataclass backport for Python < 3.10.

Release 20240412:

  • RunState: new optional thread_wide=False parameter - if true, set this RunState as the Thread-wide default - this mode used by @uses_runstate, unsure about this default.
  • RunState: new .iter(iterable) method which iterates while not RunState.cancelled.
  • MultiOpenMixin: replace __mo_getstate() method with MultiOpenMixin_state property.
  • RunState.init: make most parameters keyword only.

Release 20240316: Fixed release upload artifacts.

Release 20240201: MultiOpenMixin: new .is_open() method to test for opens > 0.

Release 20231221: RunState: new raiseif() method to raise CancellationError if the RunState is cancelled.

Release 20231129:

  • RunStateMixin: runstate parameter may be None, str, RunState.
  • MultiOpenMixin.enter_exit: do not pass caller frame to self.close(), uninformative.

Release 20230503: RunState: new optional poll_cancel Callable parameter, make .cancelled a property.

Release 20230331:

  • @uses_runstate: use the prevailing RunState or create one.
  • MultiOpenMixin: move all the open/close counting logic to the _mom_state class, make several attributes public, drop separate finalise() method and associated Condition.
  • bugfix: _mom_state.open: only set self._teardown when opens==1.

Release 20230217: MultiOpenMixin: repr for the state object.

Release 20230212: RunState: if already running, do not adjust state or catch signals; if not in the main thread do not adjust signals.

Release 20230125: RunState: subclass HasThreadState, adjust @uses_runstate.

Release 20221228:

  • Get error,warning from cs.gimmicks.
  • RunState: get store verbose as self.verbose, drop from catch_signals.

Release 20221118:

  • New RunState.current thread local stackable class attribute.
  • New @uses_runstate decorator for functions using a RunState, defaulting to RunState.current.runstate.

Release 20220918:

  • MultiOpenMixin.close: report caller of underflow close.
  • RunState: new optional handle_signal parameter to override the default method.
  • New openif() context manager to open/close an object if it has a .open method.
  • MultiOpenMixin.startup_shutdown: be silent for missing (obsolete) .startup, require .shutdown if .startup.

Release 20220429: RunState: new catch_signal(sig,verbose=False) context manager method to cancel the RunState on receipt of a signal.

Release 20211208:

  • MultiOpenMixin.startup_shutdown: since this is the fallback for obsolete uses of MultiOpenMixin, warn if there is no .startup/.shutdown method.
  • MultiOpenMixin.startup_shutdown: fix up shutdown logic, was not using a finally clause.
  • MultiOpenMixin: use ContextManagerMixin enter_exit generator method instead of enter and exit.

Release 20210906: MultiOpenMixin: make startup and shutdown optional.

Release 20210731: RunState: tune the sanity checks around whether the state is "running".

Release 20210420: MultiOpenMixin: run startup/shutdown entirely via the new default method @contextmanager(startup_shutdown), paving the way for subclasses to just define their own startup_shutdown context manager methods instead of distinct startup/shutdown methods.

Release 20201025: MultiOpenMixin.__mo_getstate: dereference self.dict because using AttributeError was pulling a state object from another instance, utterly weird.

Release 20200718: MultiOpenMixin: as a hack to avoid having an init, move state into an on demand object accesses by a private method.

Release 20200521: Sweeping removal of cs.obj.O, universally supplanted by types.SimpleNamespace.

Release 20190812:

  • MultiOpenMixin: no longer subclass cs.obj.O.
  • MultiOpenMixin: remove lock param support, the mixin has its own lock.
  • MultiOpen: drop lock param support, no longer used by MultiOpenMixin.
  • MultiOpenMixin: do finalise inside the lock for the same reason as shutdown (competition with open/startup).
  • MultiOpenMixin.close: new unopened_ok=False parameter intended for callback closes which might fire even if the initial open does not occur.

Release 20190617: RunState.exit: if an exception was raised call .canel() before calling .stop().

Release 20190103:

  • Bugfixes for context managers.
  • MultiOpenMixin fixes and changes.
  • RunState improvements.

Release 20171024:

  • bugfix MultiOpenMixin finalise logic and other small logic fixes and checs
  • new class RunState for tracking or controlling a running task

Release 20160828: Use "install_requires" instead of "requires" in DISTINFO.

Release 20160827:

  • BREAKING CHANGE: rename NestingOpenCloseMixin to MultiOpenMixin.
  • New Pool class for generic object reuse.
  • Assorted minor improvements.

Release 20150115: First PyPI release.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cs_resources-20240721.tar.gz (30.0 kB view details)

Uploaded Source

Built Distribution

cs.resources-20240721-py3-none-any.whl (17.8 kB view details)

Uploaded Python 3

File details

Details for the file cs_resources-20240721.tar.gz.

File metadata

  • Download URL: cs_resources-20240721.tar.gz
  • Upload date:
  • Size: 30.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.1.1 CPython/3.12.0

File hashes

Hashes for cs_resources-20240721.tar.gz
Algorithm Hash digest
SHA256 cf7738e947ecdcdc1b208f177752abbc2598b7bed58208ae112340bb80f22fc9
MD5 65547a50e4f5411f053b62a222eb03c9
BLAKE2b-256 2489914cd251034f9a99889355052699d20e3d059bbe7f73fdc4c8ff693282b2

See more details on using hashes here.

File details

Details for the file cs.resources-20240721-py3-none-any.whl.

File metadata

File hashes

Hashes for cs.resources-20240721-py3-none-any.whl
Algorithm Hash digest
SHA256 8920d8b7f3f373e795a9654b1584d38f3177e4857370db33e712358606449b32
MD5 d317ecddfa5a2b4b92d99c133fed7d5c
BLAKE2b-256 c9ab09d42e7a6d8016a9c9dc02fe385ba55d4fd7846dd215aeefed50584431fc

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page