Resource management classes and functions.
*Latest release 20241005*:
* New RunState.sleep(delay[,step]) function to do an interruptable sleep.
* RunState.bg: override HasThreadState.bg to catch CancellationError and just issue a warning.
## <a name="ClosedError"></a>Class `ClosedError(builtins.Exception)`
Exception for operations which are invalid when something is closed.
## <a name="MultiOpen"></a>`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.
## <a name="MultiOpenMixin"></a>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`.
## <a name="not_closed"></a>`not_closed(*da, **dkw)`
A decorator to wrap methods of objects with a `.closed` property
which should raise when `self.closed`.
## <a name="openif"></a>`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.
## <a name="Pool"></a>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.
## <a name="RunState"></a>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.
State diagram:
![RunState State Diagram](data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIKICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8IS0tIEdlbmVyYXRlZCBieSBncmFwaHZpeiB2ZXJzaW9uIDExLjAuMCAoMjAyNDA0MjguMTUyMikKIC0tPgo8IS0tIFRpdGxlOiBSdW5TdGF0ZSBTdGF0ZSBEaWFncmFtIFBhZ2VzOiAxIC0tPgo8c3ZnIHdpZHRoPSIzMDlwdCIgaGVpZ2h0PSIzMTBwdCIKIHZpZXdCb3g9IjAuMDAgMC4wMCAzMDkuMjcgMzA5LjUwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KPGcgaWQ9ImdyYXBoMCIgY2xhc3M9ImdyYXBoIiB0cmFuc2Zvcm09InNjYWxlKDEgMSkgcm90YXRlKDApIHRyYW5zbGF0ZSg0IDMwNS41KSI+Cjx0aXRsZT5SdW5TdGF0ZSBTdGF0ZSBEaWFncmFtPC90aXRsZT4KPHBvbHlnb24gZmlsbD0id2hpdGUiIHN0cm9rZT0ibm9uZSIgcG9pbnRzPSItNCw0IC00LC0zMDUuNSAzMDUuMjcsLTMwNS41IDMwNS4yNyw0IC00LDQiLz4KPCEtLSBJRExFIC0tPgo8ZyBpZD0ibm9kZTEiIGNsYXNzPSJub2RlIj4KPHRpdGxlPklETEU8L3RpdGxlPgo8ZWxsaXBzZSBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBjeD0iMTk3LjkzIiBjeT0iLTI4My41IiByeD0iMzEuOSIgcnk9IjE4Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjE5Ny45MyIgeT0iLTI3OC40NSIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5JRExFPC90ZXh0Pgo8L2c+CjwhLS0gSURMRSYjNDU7Jmd0O0lETEUgLS0+CjxnIGlkPSJlZGdlMSIgY2xhc3M9ImVkZ2UiPgo8dGl0bGU+SURMRSYjNDU7Jmd0O0lETEU8L3RpdGxlPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBkPSJNMjI2LjksLTI5MS43NkMyMzguMzEsLTI5Mi4xNyAyNDcuODQsLTI4OS40MiAyNDcuODQsLTI4My41IDI0Ny44NCwtMjc5LjcxIDI0My45MywtMjc3LjIyIDIzOC4xMiwtMjc2LjAyIi8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjIzOC42MywtMjcyLjU1IDIyOC40MSwtMjc1LjM1IDIzOC4xNSwtMjc5LjUzIDIzOC42MywtMjcyLjU1Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjI2NS4wOSIgeT0iLTI3OC40NSIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5jYW5jZWw8L3RleHQ+CjwvZz4KPCEtLSBSVU5OSU5HIC0tPgo8ZyBpZD0ibm9kZTIiIGNsYXNzPSJub2RlIj4KPHRpdGxlPlJVTk5JTkc8L3RpdGxlPgo8ZWxsaXBzZSBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBjeD0iMTk3LjkzIiBjeT0iLTE5NSIgcng9IjUzLjQiIHJ5PSIxOCIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIxOTcuOTMiIHk9Ii0xODkuOTUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+UlVOTklORzwvdGV4dD4KPC9nPgo8IS0tIElETEUmIzQ1OyZndDtSVU5OSU5HIC0tPgo8ZyBpZD0iZWRnZTIiIGNsYXNzPSJlZGdlIj4KPHRpdGxlPklETEUmIzQ1OyZndDtSVU5OSU5HPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTE5Ny45MywtMjY1LjQxQzE5Ny45MywtMjUzLjc2IDE5Ny45MywtMjM4LjA1IDE5Ny45MywtMjI0LjUyIi8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjIwMS40MywtMjI0Ljg2IDE5Ny45MywtMjE0Ljg2IDE5NC40MywtMjI0Ljg2IDIwMS40MywtMjI0Ljg2Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjIwOS41NiIgeT0iLTIzNC4yIiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPnN0YXJ0PC90ZXh0Pgo8L2c+CjwhLS0gU1RPUFBJTkcgLS0+CjxnIGlkPSJub2RlMyIgY2xhc3M9Im5vZGUiPgo8dGl0bGU+U1RPUFBJTkc8L3RpdGxlPgo8ZWxsaXBzZSBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBjeD0iNTQuOTMiIGN5PSItMTA2LjUiIHJ4PSI1NC45MyIgcnk9IjE4Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjU0LjkzIiB5PSItMTAxLjQ1IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPlNUT1BQSU5HPC90ZXh0Pgo8L2c+CjwhLS0gUlVOTklORyYjNDU7Jmd0O1NUT1BQSU5HIC0tPgo8ZyBpZD0iZWRnZTMiIGNsYXNzPSJlZGdlIj4KPHRpdGxlPlJVTk5JTkcmIzQ1OyZndDtTVE9QUElORzwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0xNzIuNywtMTc4LjczQzE0OS43NiwtMTY0Ljg2IDExNS43LC0xNDQuMjYgOTAuMSwtMTI4Ljc3Ii8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjkyLjA2LC0xMjUuODcgODEuNjksLTEyMy42OSA4OC40NCwtMTMxLjg2IDkyLjA2LC0xMjUuODciLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTU3LjE4IiB5PSItMTQ1LjciIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+Y2FuY2VsPC90ZXh0Pgo8L2c+CjwhLS0gU1RPUFBFRCAtLT4KPGcgaWQ9Im5vZGU0IiBjbGFzcz0ibm9kZSI+Cjx0aXRsZT5TVE9QUEVEPC90aXRsZT4KPGVsbGlwc2UgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgY3g9IjE5Ny45MyIgY3k9Ii0xOCIgcng9IjUwLjg0IiByeT0iMTgiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTk3LjkzIiB5PSItMTIuOTUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+U1RPUFBFRDwvdGV4dD4KPC9nPgo8IS0tIFJVTk5JTkcmIzQ1OyZndDtTVE9QUEVEIC0tPgo8ZyBpZD0iZWRnZTQiIGNsYXNzPSJlZGdlIj4KPHRpdGxlPlJVTk5JTkcmIzQ1OyZndDtTVE9QUEVEPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTE5Ny45MywtMTc2LjU4QzE5Ny45MywtMTQ2LjQ0IDE5Ny45MywtODQuMyAxOTcuOTMsLTQ3LjciLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMjAxLjQzLC00Ny44NiAxOTcuOTMsLTM3Ljg2IDE5NC40MywtNDcuODYgMjAxLjQzLC00Ny44NiIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIyMDkuMTgiIHk9Ii0xMDEuNDUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+c3RvcDwvdGV4dD4KPC9nPgo8IS0tIFNUT1BQSU5HJiM0NTsmZ3Q7U1RPUFBJTkcgLS0+CjxnIGlkPSJlZGdlNSIgY2xhc3M9ImVkZ2UiPgo8dGl0bGU+U1RPUFBJTkcmIzQ1OyZndDtTVE9QUElORzwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0xMDQuMzYsLTExNC43NEMxMTcuNzEsLTExNC4zIDEyNy44NywtMTExLjU2IDEyNy44NywtMTA2LjUgMTI3Ljg3LC0xMDMuMDIgMTIzLjA3LC0xMDAuNjQgMTE1LjczLC05OS4zNSIvPgo8cG9seWdvbiBmaWxsPSJibGFjayIgc3Ryb2tlPSJibGFjayIgcG9pbnRzPSIxMTYuMTYsLTk1Ljg3IDEwNS44NywtOTguNDEgMTE1LjQ5LC0xMDIuODQgMTE2LjE2LC05NS44NyIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIxNDUuMTIiIHk9Ii0xMDEuNDUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+Y2FuY2VsPC90ZXh0Pgo8L2c+CjwhLS0gU1RPUFBJTkcmIzQ1OyZndDtTVE9QUEVEIC0tPgo8ZyBpZD0iZWRnZTYiIGNsYXNzPSJlZGdlIj4KPHRpdGxlPlNUT1BQSU5HJiM0NTsmZ3Q7U1RPUFBFRDwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik04MC4xNywtOTAuMjNDMTAzLjI1LC03Ni4yOCAxMzcuNTksLTU1LjUgMTYzLjI0LC0zOS45OSIvPgo8cG9seWdvbiBmaWxsPSJibGFjayIgc3Ryb2tlPSJibGFjayIgcG9pbnRzPSIxNjQuOTEsLTQzLjA3IDE3MS42NSwtMzQuOSAxNjEuMjksLTM3LjA4IDE2NC45MSwtNDMuMDciLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTUxLjE4IiB5PSItNTcuMiIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5zdG9wPC90ZXh0Pgo8L2c+CjwhLS0gU1RPUFBFRCYjNDU7Jmd0O1JVTk5JTkcgLS0+CjxnIGlkPSJlZGdlOCIgY2xhc3M9ImVkZ2UiPgo8dGl0bGU+U1RPUFBFRCYjNDU7Jmd0O1JVTk5JTkc8L3RpdGxlPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBkPSJNMjA0Ljg0LC0zNi4wNkMyMDcuMTIsLTQxLjc2IDIwOS42NiwtNDguMTQgMjExLjkzLC01NCAyMTcuODcsLTY5LjI3IDIyMiwtNzIuMzggMjI0LjkzLC04OC41IDIyNy44LC0xMDQuMjQgMjI3LjgsLTEwOC43NiAyMjQuOTMsLTEyNC41IDIyMi4zMSwtMTM4Ljg4IDIxNi43NSwtMTU0LjEgMjExLjM1LC0xNjYuNjEiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMjA4LjI5LC0xNjQuOSAyMDcuMzUsLTE3NS40NSAyMTQuNjcsLTE2Ny43OCAyMDguMjksLTE2NC45Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjIzOC41NiIgeT0iLTEwMS40NSIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5zdGFydDwvdGV4dD4KPC9nPgo8IS0tIFNUT1BQRUQmIzQ1OyZndDtTVE9QUEVEIC0tPgo8ZyBpZD0iZWRnZTciIGNsYXNzPSJlZGdlIj4KPHRpdGxlPlNUT1BQRUQmIzQ1OyZndDtTVE9QUEVEPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTI0My42MSwtMjYuMjZDMjU2LjY5LC0yNS45NCAyNjYuNzcsLTIzLjE5IDI2Ni43NywtMTggMjY2Ljc3LC0xNC40MyAyNjIuMDEsLTEyLjAyIDI1NC43OCwtMTAuNzUiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMjU1LjQsLTcuMjkgMjQ1LjEyLC05Ljg3IDI1NC43NiwtMTQuMjYgMjU1LjQsLTcuMjkiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMjg0LjAyIiB5PSItMTIuOTUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+Y2FuY2VsPC90ZXh0Pgo8L2c+CjwvZz4KPC9zdmc+Cg== "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.bg(self, func, **bg_kw)`*:
Override `HasThreadState.bg` to catch CancellationError
and just issue a warning.
*`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.sleep(self, delay, step=1.0)`*:
Sleep for `delay` seconds in increments of `step` (default `1.0`).
`self.raiseif()` is polled between steps.
*`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?
## <a name="RunStateMixin"></a>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 0x102270c20>)`*:
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.
## <a name="uses_runstate"></a>`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 name
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 20241005*:
* New RunState.sleep(delay[,step]) function to do an interruptable sleep.
* RunState.bg: override HasThreadState.bg to catch CancellationError and just issue a warning.
*Release 20240723*:
_MultiOpenMixinOpenCloseState: use an RLock, still to investigate the deadlock with NRLock.
*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.
Raw data
{
"_id": null,
"home_page": null,
"name": "cs.resources",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "python2, python3",
"author": null,
"author_email": "Cameron Simpson <cs@cskk.id.au>",
"download_url": "https://files.pythonhosted.org/packages/4e/24/f3110487e4df9c12a0f26b87c3c6ce81a84a069dfea5ec09516382075ce3/cs_resources-20241005.tar.gz",
"platform": null,
"description": "Resource management classes and functions.\n\n*Latest release 20241005*:\n* New RunState.sleep(delay[,step]) function to do an interruptable sleep.\n* RunState.bg: override HasThreadState.bg to catch CancellationError and just issue a warning.\n\n## <a name=\"ClosedError\"></a>Class `ClosedError(builtins.Exception)`\n\nException for operations which are invalid when something is closed.\n\n## <a name=\"MultiOpen\"></a>`MultiOpen(openable, finalise_later=False)`\n\nOBSOLETE MultiOpen\n\nA context manager class that manages a single-open/close object\n using a `MultiOpenMixin`.\n\n Use:\n\n mo = MultiOpen(obj)\n ......\n with mo:\n .... use obj ...\n\n This required `obj` to have a `.open()` method which can\n be called with no arguments (which is pretty uncommon)\n and a `.close()` method.\n\n## <a name=\"MultiOpenMixin\"></a>Class `MultiOpenMixin(cs.context.ContextManagerMixin)`\n\nA multithread safe mixin to count open and close calls,\ndoing a startup on the first `.open` and shutdown on the last `.close`.\n\nIf used as a context manager this mixin calls `open()`/`close()` from\n`__enter__()` and `__exit__()`.\n\nIt is recommended that subclass implementations do as little\nas possible during `__init__`, and do almost all setup during\nstartup so that the class may perform multiple startup/shutdown\niterations.\n\nClasses using this mixin should define a context manager\nmethod `.startup_shutdown` which does the startup actions\nbefore yielding and then does the shutdown actions.\n\nExample:\n\n class DatabaseThing(MultiOpenMixin):\n @contextmanager\n def startup_shutdown(self):\n self._db = open_the_database()\n try:\n yield\n finally:\n self._db.close()\n ...\n with DatabaseThing(...) as db_thing:\n ... use db_thing ...\n\nIf course, often something like a database open will itself\nbe a context manager and the `startup_shutdown` method more\nusually looks like this:\n\n @contextmanager\n def startup_shutdown(self):\n with open_the_database() as db:\n self._db = db\n yield\n\nWhy not just write a plain context manager class? Because in\nmultithreaded or async code one wants to keep the instance\n\"open\" while any thread is still using it.\nThis mixin lets threads use an instance in overlapping fashion:\n\n db_thing = DatabaseThing(...)\n with db_thing:\n ... kick off threads with access to the db ...\n ...\n thread 1:\n with db_thing:\n ... use db_thing ...\n thread 2:\n with db_thing:\n ... use db_thing ...\n\nTODO:\n* `subopens`: if true (default false) then `.open` will return\n a proxy object with its own `.closed` attribute set by the\n proxy's `.close`.\n\n*`MultiOpenMixin.MultiOpenMixin_state`*:\nThe state object for the mixin,\nsomething of a hack to avoid providing an `__init__`.\n\n*`MultiOpenMixin.close(self, *, enforce_final_close=False, caller_frame=None, unopened_ok=False)`*:\nDecrement the open count.\nIf the count goes to zero, call `self.shutdown()` and return its value.\n\nParameters:\n* `enforce_final_close`: if true, the caller expects this to\n be the final close for the object and a `RuntimeError` is\n raised if this is not actually the case.\n* `caller_frame`: used for debugging; the caller may specify\n this if necessary, otherwise it is computed from\n `cs.py.stack.caller` when needed. Presently the caller of the\n final close is recorded to help debugging extra close calls.\n* `unopened_ok`: if true, it is not an error if this is not open.\n This is intended for closing callbacks which might get called\n even if the original open never happened.\n (I'm looking at you, `cs.resources.RunState`.)\n\n*`MultiOpenMixin.closed`*:\nWhether this object has been closed.\nNote: `False` if never opened.\n\n*`MultiOpenMixin.is_open(self)`*:\nTest whether this object is open.\n\n*`MultiOpenMixin.is_opened(func)`*:\nDecorator to wrap `MultiOpenMixin` proxy object methods which\nshould raise if the object is not yet open.\n\n*`MultiOpenMixin.join(self)`*:\nJoin this object.\n\nWait for the internal finalise `Condition` (if still not `None`).\nNormally this is notified at the end of the shutdown procedure\nunless the object's `finalise_later` parameter was true.\n\n*`MultiOpenMixin.open(self, caller_frame=None)`*:\nIncrement the open count.\nOn the first `.open` call `self.startup()`.\n\n*`MultiOpenMixin.startup_shutdown(self)`*:\nDefault context manager form of startup/shutdown - just\ncall the distinct `.startup()` and `.shutdown()` methods\nif both are present, do nothing if neither is present.\n\nThis supports subclasses always using:\n\n with super().startup_shutdown():\n\nas an outer wrapper.\n\nThe `.startup` check is to support legacy subclasses of\n`MultiOpenMixin` which have separate `startup()` and\n`shutdown()` methods.\nThe preferred approach is a single `startup_shutdwn()`\ncontext manager overriding this method.\n\nThe usual form looks like this:\n\n @contextmanager\n def startup_shutdown(self):\n with super().startup_shutdown():\n ... do some set up ...\n try:\n yield\n finally:\n ... do some tear down ...\n\n*`MultiOpenMixin.tcm_get_state(self)`*:\nSupport method for `TrackedClassMixin`.\n\n## <a name=\"not_closed\"></a>`not_closed(*da, **dkw)`\n\nA decorator to wrap methods of objects with a `.closed` property\nwhich should raise when `self.closed`.\n\n## <a name=\"openif\"></a>`openif(obj)`\n\nContext manager to open `obj` if it has a `.open` method\nand also to close it via its `.close` method.\nThis yields `obj.open()` if defined, or `obj` otherwise.\n\n## <a name=\"Pool\"></a>Class `Pool`\n\nA generic pool of objects on the premise that reuse is cheaper than recreation.\n\nAll the pool objects must be suitable for use, so the\n`new_object` callable will typically be a closure.\nFor example, here is the __init__ for a per-thread AWS Bucket using a\ndistinct Session:\n\n def __init__(self, bucket_name):\n Pool.__init__(self, lambda: boto3.session.Session().resource('s3').Bucket(bucket_name)\n\n*`Pool.__init__(self, new_object, max_size=None, lock=None)`*:\nInitialise the Pool with creator `new_object` and maximum size `max_size`.\n\nParameters:\n* `new_object` is a callable which returns a new object for the Pool.\n* `max_size`: The maximum size of the pool of available objects saved for reuse.\n If omitted or `None`, defaults to 4.\n If 0, no upper limit is applied.\n* `lock`: optional shared Lock; if omitted or `None` a new Lock is allocated\n\n*`Pool.instance(self)`*:\nContext manager returning an object for use, which is returned to the pool afterwards.\n\n## <a name=\"RunState\"></a>Class `RunState(cs.fsm.FSM, cs.threads.HasThreadState)`\n\nA class to track a running task whose cancellation may be requested.\n\nIts purpose is twofold, to provide easily queriable state\naround tasks which can start and stop, and to provide control\nmethods to pronounce that a task has started (`.start`),\nshould stop (`.cancel`)\nand has stopped (`.stop`).\n\nA `RunState` can be used as a context manager, with the enter\nand exit methods calling `.start` and `.stop` respectively.\nNote that if the suite raises an exception\nthen the exit method also calls `.cancel` before the call to `.stop`.\n\nMonitor or daemon processes can poll the `RunState` to see when\nthey should terminate, and may also manage the overall state\neasily using a context manager.\nExample:\n\n def monitor(self):\n with self.runstate:\n while not self.runstate.cancelled:\n ... main loop body here ...\n\nA `RunState` has three main methods:\n* `.start()`: set `.running` and clear `.cancelled`\n* `.cancel()`: set `.cancelled`\n* `.stop()`: clear `.running`\n\nA `RunState` has the following properties:\n* `cancelled`: true if `.cancel` has been called.\n* `running`: true if the task is running.\n Further, assigning a true value to it sets `.start_time` to now.\n Assigning a false value to it sets `.stop_time` to now.\n* `start_time`: the time `.running` was last set to true.\n* `stop_time`: the time `.running` was last set to false.\n* `run_time`: `max(0,.stop_time-.start_time)`\n* `stopped`: true if the task is not running.\n* `stopping`: true if the task is running but has been cancelled.\n* `notify_start`: a set of callables called with the `RunState` instance\n to be called whenever `.running` becomes true.\n* `notify_end`: a set of callables called with the `RunState` instance\n to be called whenever `.running` becomes false.\n* `notify_cancel`: a set of callables called with the `RunState` instance\n to be called whenever `.cancel` is called.\n\nState diagram:\n![RunState State Diagram](data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIKICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPgo8IS0tIEdlbmVyYXRlZCBieSBncmFwaHZpeiB2ZXJzaW9uIDExLjAuMCAoMjAyNDA0MjguMTUyMikKIC0tPgo8IS0tIFRpdGxlOiBSdW5TdGF0ZSBTdGF0ZSBEaWFncmFtIFBhZ2VzOiAxIC0tPgo8c3ZnIHdpZHRoPSIzMDlwdCIgaGVpZ2h0PSIzMTBwdCIKIHZpZXdCb3g9IjAuMDAgMC4wMCAzMDkuMjcgMzA5LjUwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KPGcgaWQ9ImdyYXBoMCIgY2xhc3M9ImdyYXBoIiB0cmFuc2Zvcm09InNjYWxlKDEgMSkgcm90YXRlKDApIHRyYW5zbGF0ZSg0IDMwNS41KSI+Cjx0aXRsZT5SdW5TdGF0ZSBTdGF0ZSBEaWFncmFtPC90aXRsZT4KPHBvbHlnb24gZmlsbD0id2hpdGUiIHN0cm9rZT0ibm9uZSIgcG9pbnRzPSItNCw0IC00LC0zMDUuNSAzMDUuMjcsLTMwNS41IDMwNS4yNyw0IC00LDQiLz4KPCEtLSBJRExFIC0tPgo8ZyBpZD0ibm9kZTEiIGNsYXNzPSJub2RlIj4KPHRpdGxlPklETEU8L3RpdGxlPgo8ZWxsaXBzZSBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBjeD0iMTk3LjkzIiBjeT0iLTI4My41IiByeD0iMzEuOSIgcnk9IjE4Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjE5Ny45MyIgeT0iLTI3OC40NSIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5JRExFPC90ZXh0Pgo8L2c+CjwhLS0gSURMRSYjNDU7Jmd0O0lETEUgLS0+CjxnIGlkPSJlZGdlMSIgY2xhc3M9ImVkZ2UiPgo8dGl0bGU+SURMRSYjNDU7Jmd0O0lETEU8L3RpdGxlPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBkPSJNMjI2LjksLTI5MS43NkMyMzguMzEsLTI5Mi4xNyAyNDcuODQsLTI4OS40MiAyNDcuODQsLTI4My41IDI0Ny44NCwtMjc5LjcxIDI0My45MywtMjc3LjIyIDIzOC4xMiwtMjc2LjAyIi8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjIzOC42MywtMjcyLjU1IDIyOC40MSwtMjc1LjM1IDIzOC4xNSwtMjc5LjUzIDIzOC42MywtMjcyLjU1Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjI2NS4wOSIgeT0iLTI3OC40NSIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5jYW5jZWw8L3RleHQ+CjwvZz4KPCEtLSBSVU5OSU5HIC0tPgo8ZyBpZD0ibm9kZTIiIGNsYXNzPSJub2RlIj4KPHRpdGxlPlJVTk5JTkc8L3RpdGxlPgo8ZWxsaXBzZSBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBjeD0iMTk3LjkzIiBjeT0iLTE5NSIgcng9IjUzLjQiIHJ5PSIxOCIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIxOTcuOTMiIHk9Ii0xODkuOTUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+UlVOTklORzwvdGV4dD4KPC9nPgo8IS0tIElETEUmIzQ1OyZndDtSVU5OSU5HIC0tPgo8ZyBpZD0iZWRnZTIiIGNsYXNzPSJlZGdlIj4KPHRpdGxlPklETEUmIzQ1OyZndDtSVU5OSU5HPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTE5Ny45MywtMjY1LjQxQzE5Ny45MywtMjUzLjc2IDE5Ny45MywtMjM4LjA1IDE5Ny45MywtMjI0LjUyIi8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjIwMS40MywtMjI0Ljg2IDE5Ny45MywtMjE0Ljg2IDE5NC40MywtMjI0Ljg2IDIwMS40MywtMjI0Ljg2Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjIwOS41NiIgeT0iLTIzNC4yIiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPnN0YXJ0PC90ZXh0Pgo8L2c+CjwhLS0gU1RPUFBJTkcgLS0+CjxnIGlkPSJub2RlMyIgY2xhc3M9Im5vZGUiPgo8dGl0bGU+U1RPUFBJTkc8L3RpdGxlPgo8ZWxsaXBzZSBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBjeD0iNTQuOTMiIGN5PSItMTA2LjUiIHJ4PSI1NC45MyIgcnk9IjE4Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjU0LjkzIiB5PSItMTAxLjQ1IiBmb250LWZhbWlseT0iVGltZXMsc2VyaWYiIGZvbnQtc2l6ZT0iMTQuMDAiPlNUT1BQSU5HPC90ZXh0Pgo8L2c+CjwhLS0gUlVOTklORyYjNDU7Jmd0O1NUT1BQSU5HIC0tPgo8ZyBpZD0iZWRnZTMiIGNsYXNzPSJlZGdlIj4KPHRpdGxlPlJVTk5JTkcmIzQ1OyZndDtTVE9QUElORzwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0xNzIuNywtMTc4LjczQzE0OS43NiwtMTY0Ljg2IDExNS43LC0xNDQuMjYgOTAuMSwtMTI4Ljc3Ii8+Cjxwb2x5Z29uIGZpbGw9ImJsYWNrIiBzdHJva2U9ImJsYWNrIiBwb2ludHM9IjkyLjA2LC0xMjUuODcgODEuNjksLTEyMy42OSA4OC40NCwtMTMxLjg2IDkyLjA2LC0xMjUuODciLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTU3LjE4IiB5PSItMTQ1LjciIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+Y2FuY2VsPC90ZXh0Pgo8L2c+CjwhLS0gU1RPUFBFRCAtLT4KPGcgaWQ9Im5vZGU0IiBjbGFzcz0ibm9kZSI+Cjx0aXRsZT5TVE9QUEVEPC90aXRsZT4KPGVsbGlwc2UgZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgY3g9IjE5Ny45MyIgY3k9Ii0xOCIgcng9IjUwLjg0IiByeT0iMTgiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTk3LjkzIiB5PSItMTIuOTUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+U1RPUFBFRDwvdGV4dD4KPC9nPgo8IS0tIFJVTk5JTkcmIzQ1OyZndDtTVE9QUEVEIC0tPgo8ZyBpZD0iZWRnZTQiIGNsYXNzPSJlZGdlIj4KPHRpdGxlPlJVTk5JTkcmIzQ1OyZndDtTVE9QUEVEPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTE5Ny45MywtMTc2LjU4QzE5Ny45MywtMTQ2LjQ0IDE5Ny45MywtODQuMyAxOTcuOTMsLTQ3LjciLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMjAxLjQzLC00Ny44NiAxOTcuOTMsLTM3Ljg2IDE5NC40MywtNDcuODYgMjAxLjQzLC00Ny44NiIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIyMDkuMTgiIHk9Ii0xMDEuNDUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+c3RvcDwvdGV4dD4KPC9nPgo8IS0tIFNUT1BQSU5HJiM0NTsmZ3Q7U1RPUFBJTkcgLS0+CjxnIGlkPSJlZGdlNSIgY2xhc3M9ImVkZ2UiPgo8dGl0bGU+U1RPUFBJTkcmIzQ1OyZndDtTVE9QUElORzwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik0xMDQuMzYsLTExNC43NEMxMTcuNzEsLTExNC4zIDEyNy44NywtMTExLjU2IDEyNy44NywtMTA2LjUgMTI3Ljg3LC0xMDMuMDIgMTIzLjA3LC0xMDAuNjQgMTE1LjczLC05OS4zNSIvPgo8cG9seWdvbiBmaWxsPSJibGFjayIgc3Ryb2tlPSJibGFjayIgcG9pbnRzPSIxMTYuMTYsLTk1Ljg3IDEwNS44NywtOTguNDEgMTE1LjQ5LC0xMDIuODQgMTE2LjE2LC05NS44NyIvPgo8dGV4dCB0ZXh0LWFuY2hvcj0ibWlkZGxlIiB4PSIxNDUuMTIiIHk9Ii0xMDEuNDUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+Y2FuY2VsPC90ZXh0Pgo8L2c+CjwhLS0gU1RPUFBJTkcmIzQ1OyZndDtTVE9QUEVEIC0tPgo8ZyBpZD0iZWRnZTYiIGNsYXNzPSJlZGdlIj4KPHRpdGxlPlNUT1BQSU5HJiM0NTsmZ3Q7U1RPUFBFRDwvdGl0bGU+CjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iYmxhY2siIGQ9Ik04MC4xNywtOTAuMjNDMTAzLjI1LC03Ni4yOCAxMzcuNTksLTU1LjUgMTYzLjI0LC0zOS45OSIvPgo8cG9seWdvbiBmaWxsPSJibGFjayIgc3Ryb2tlPSJibGFjayIgcG9pbnRzPSIxNjQuOTEsLTQzLjA3IDE3MS42NSwtMzQuOSAxNjEuMjksLTM3LjA4IDE2NC45MSwtNDMuMDciLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMTUxLjE4IiB5PSItNTcuMiIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5zdG9wPC90ZXh0Pgo8L2c+CjwhLS0gU1RPUFBFRCYjNDU7Jmd0O1JVTk5JTkcgLS0+CjxnIGlkPSJlZGdlOCIgY2xhc3M9ImVkZ2UiPgo8dGl0bGU+U1RPUFBFRCYjNDU7Jmd0O1JVTk5JTkc8L3RpdGxlPgo8cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImJsYWNrIiBkPSJNMjA0Ljg0LC0zNi4wNkMyMDcuMTIsLTQxLjc2IDIwOS42NiwtNDguMTQgMjExLjkzLC01NCAyMTcuODcsLTY5LjI3IDIyMiwtNzIuMzggMjI0LjkzLC04OC41IDIyNy44LC0xMDQuMjQgMjI3LjgsLTEwOC43NiAyMjQuOTMsLTEyNC41IDIyMi4zMSwtMTM4Ljg4IDIxNi43NSwtMTU0LjEgMjExLjM1LC0xNjYuNjEiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMjA4LjI5LC0xNjQuOSAyMDcuMzUsLTE3NS40NSAyMTQuNjcsLTE2Ny43OCAyMDguMjksLTE2NC45Ii8+Cjx0ZXh0IHRleHQtYW5jaG9yPSJtaWRkbGUiIHg9IjIzOC41NiIgeT0iLTEwMS40NSIgZm9udC1mYW1pbHk9IlRpbWVzLHNlcmlmIiBmb250LXNpemU9IjE0LjAwIj5zdGFydDwvdGV4dD4KPC9nPgo8IS0tIFNUT1BQRUQmIzQ1OyZndDtTVE9QUEVEIC0tPgo8ZyBpZD0iZWRnZTciIGNsYXNzPSJlZGdlIj4KPHRpdGxlPlNUT1BQRUQmIzQ1OyZndDtTVE9QUEVEPC90aXRsZT4KPHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJibGFjayIgZD0iTTI0My42MSwtMjYuMjZDMjU2LjY5LC0yNS45NCAyNjYuNzcsLTIzLjE5IDI2Ni43NywtMTggMjY2Ljc3LC0xNC40MyAyNjIuMDEsLTEyLjAyIDI1NC43OCwtMTAuNzUiLz4KPHBvbHlnb24gZmlsbD0iYmxhY2siIHN0cm9rZT0iYmxhY2siIHBvaW50cz0iMjU1LjQsLTcuMjkgMjQ1LjEyLC05Ljg3IDI1NC43NiwtMTQuMjYgMjU1LjQsLTcuMjkiLz4KPHRleHQgdGV4dC1hbmNob3I9Im1pZGRsZSIgeD0iMjg0LjAyIiB5PSItMTIuOTUiIGZvbnQtZmFtaWx5PSJUaW1lcyxzZXJpZiIgZm9udC1zaXplPSIxNC4wMCI+Y2FuY2VsPC90ZXh0Pgo8L2c+CjwvZz4KPC9zdmc+Cg== \"RunState State Diagram\")\n\n\n*`RunState.__bool__(self)`*:\nReturn true if the task is running.\n\n*`RunState.__enter_exit__(self)`*:\nThe `__enter__`/`__exit__` generator function:\n* push this `RunState` via `HasThreadState`\n* catch signals if we are in the main `Thread`\n* start\n* `yield self` => run\n* cancel on exception during the run\n* stop\n\nNote that if the `RunState` is already running we do not\ndo any of that stuff apart from the `yield self` because\nwe assume whatever setup should have been done has already\nbeen done.\nIn particular, the `HasThreadState.Thread` factory calls this\nin the \"running\" state.\n\n*`RunState.__nonzero__(self)`*:\nReturn true if the task is running.\n\n*`RunState.bg(self, func, **bg_kw)`*:\nOverride `HasThreadState.bg` to catch CancellationError\nand just issue a warning.\n\n*`RunState.cancel(self)`*:\nSet the cancelled flag; the associated process should notice and stop.\n\n*`RunState.cancelled`*:\nTest the .cancelled attribute, including a poll if supplied.\n\n*`RunState.catch_signal(self, sig, call_previous=False, handle_signal=None)`*:\nContext manager to catch the signal or signals `sig` and\ncancel this `RunState`.\nRestores the previous handlers on exit.\nYield a mapping of `sig`=>`old_handler`.\n\nParameters:\n* `sig`: an `int` signal number or an iterable of signal numbers\n* `call_previous`: optional flag (default `False`)\n passed to `cs.psutils.signal_handlers`\n\n*`RunState.end(self)`*:\nOBSOLETE end\n\nObsolete synonym for `.stop()`.\n\n*`RunState.fsm_event(self, event: str, **extra)`*:\nOverride `FSM.fsm_event` to apply side effects to particular transitions.\n\nOn `'cancel'` set the cancelled flag.\nOn `'start'` clear the cancelled flag and set `.start_time`.\nOn `'stop'`set `.stop_time`.\n\n*`RunState.handle_signal(self, sig, _)`*:\n`RunState` signal handler: cancel the run state.\nWarn if `self.verbose`.\n\n*`RunState.iter(self, it)`*:\nIterate over `it` while not `self.cancelled`.\n\n*`RunState.perthread_state`*\n\n*`RunState.raiseif(self, msg=None, *a)`*:\nRaise `CancellationError` if cancelled.\nThis is the concise way to terminate an operation which honours\n`.cancelled` if you're prepared to handle the exception.\n\nExample:\n\n for item in items:\n runstate.raiseif()\n ... process item ...\n\n*`RunState.run_time`*:\nA property returning most recent run time (`stop_time-start_time`).\nIf still running, use now as the stop time.\nIf not started, return `0.0`.\n\n*`RunState.running`*:\nWhether the state is `'RUNNING'` or `'STOPPING'`.\n\n*`RunState.sleep(self, delay, step=1.0)`*:\nSleep for `delay` seconds in increments of `step` (default `1.0`).\n`self.raiseif()` is polled between steps.\n\n*`RunState.start(self, running_ok=False)`*:\nStart: adjust state, set `start_time` to now.\nSets `.cancelled` to `False` and sets `.running` to `True`.\n\n*`RunState.state`*:\nOBSOLETE state\n\nThe `RunState`'s state as a string.\n Deprecated, new uses should consult `self.fsm_state`.\n\n*`RunState.stop(self)`*:\nFire the `'stop'` event.\n\n*`RunState.stopping`*:\nIs the process stopping?\n\n## <a name=\"RunStateMixin\"></a>Class `RunStateMixin`\n\nMixin to provide convenient access to a `RunState`.\n\nProvides: `.runstate`, `.cancelled`, `.running`, `.stopping`, `.stopped`.\n\n*`RunStateMixin.__init__(self, *, runstate: Union[cs.resources.RunState, str, NoneType] = <function uses_runstate.<locals>.<lambda> at 0x102270c20>)`*:\nInitialise the `RunStateMixin`; sets the `.runstate` attribute.\n\nParameters:\n* `runstate`: optional `RunState` instance or name.\n If a `str`, a new `RunState` with that name is allocated.\n If omitted, the default `RunState` is used.\n\n*`RunStateMixin.cancel(self)`*:\nCall .runstate.cancel().\n\n*`RunStateMixin.cancelled`*:\nTest .runstate.cancelled.\n\n*`RunStateMixin.running`*:\nTest .runstate.running.\n\n*`RunStateMixin.stopped`*:\nTest .runstate.stopped.\n\n*`RunStateMixin.stopping`*:\nTest .runstate.stopping.\n\n## <a name=\"uses_runstate\"></a>`uses_runstate(*da, **dkw)`\n\nA wrapper for `@default_params` which makes a new thread wide\n`RunState` parameter `runstate` if missing.\nThe optional decorator parameter `name` may be used to specify\na name for the new `RunState` if one is made. The default name\ncomes from the wrapped function's name.\n\nExample:\n\n @uses_runstate\n def do_something(blah, *, runstate:RunState):\n ... do something, polling the runstate as approriate ...\n\n# Release Log\n\n\n\n*Release 20241005*:\n* New RunState.sleep(delay[,step]) function to do an interruptable sleep.\n* RunState.bg: override HasThreadState.bg to catch CancellationError and just issue a warning.\n\n*Release 20240723*:\n_MultiOpenMixinOpenCloseState: use an RLock, still to investigate the deadlock with NRLock.\n\n*Release 20240721*:\nThe MutliOpen wrapper class is now obsolete (NB: not the MultiOpenMixin class).\n\n*Release 20240630*:\n* @uses_runstate: now accepts an optional name= parameter which defaults to the name of the function being decorated, supplied to the RunState factory.\n* RunState.FSM_TRANSITIONS: allow IDLE->cancel->IDLE.\n* @not_closed: wrap in @decorator to set the wrapper name etc.\n* RunState: allow STOPPED->cancel->STOPPED transition.\n\n*Release 20240522*:\n* @uses_runstate: if we make a new RunState, get the default name from the wrapped function.\n* RunState: bug fixes from the recent subclassing of cs.fsm.FSM.\n\n*Release 20240519*:\nRunState now subclasses cs.fsm.FSM.\n\n*Release 20240423*:\nRunStateMixin: make the optional runstate parameter keyword only.\n\n*Release 20240422*:\ndataclass backport for Python < 3.10.\n\n*Release 20240412*:\n* 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.\n* RunState: new .iter(iterable) method which iterates while not RunState.cancelled.\n* MultiOpenMixin: replace __mo_getstate() method with MultiOpenMixin_state property.\n* RunState.__init__: make most parameters keyword only.\n\n*Release 20240316*:\nFixed release upload artifacts.\n\n*Release 20240201*:\nMultiOpenMixin: new .is_open() method to test for opens > 0.\n\n*Release 20231221*:\nRunState: new raiseif() method to raise CancellationError if the RunState is cancelled.\n\n*Release 20231129*:\n* RunStateMixin: runstate parameter may be None, str, RunState.\n* MultiOpenMixin.__enter_exit__: do not pass caller frame to self.close(), uninformative.\n\n*Release 20230503*:\nRunState: new optional poll_cancel Callable parameter, make .cancelled a property.\n\n*Release 20230331*:\n* @uses_runstate: use the prevailing RunState or create one.\n* MultiOpenMixin: move all the open/close counting logic to the _mom_state class, make several attributes public, drop separate finalise() method and associated Condition.\n* bugfix: _mom_state.open: only set self._teardown when opens==1.\n\n*Release 20230217*:\nMultiOpenMixin: __repr__ for the state object.\n\n*Release 20230212*:\nRunState: if already running, do not adjust state or catch signals; if not in the main thread do not adjust signals.\n\n*Release 20230125*:\nRunState: subclass HasThreadState, adjust @uses_runstate.\n\n*Release 20221228*:\n* Get error,warning from cs.gimmicks.\n* RunState: get store verbose as self.verbose, drop from catch_signals.\n\n*Release 20221118*:\n* New RunState.current thread local stackable class attribute.\n* New @uses_runstate decorator for functions using a RunState, defaulting to RunState.current.runstate.\n\n*Release 20220918*:\n* MultiOpenMixin.close: report caller of underflow close.\n* RunState: new optional handle_signal parameter to override the default method.\n* New openif() context manager to open/close an object if it has a .open method.\n* MultiOpenMixin.startup_shutdown: be silent for missing (obsolete) .startup, require .shutdown if .startup.\n\n*Release 20220429*:\nRunState: new catch_signal(sig,verbose=False) context manager method to cancel the RunState on receipt of a signal.\n\n*Release 20211208*:\n* MultiOpenMixin.startup_shutdown: since this is the fallback for obsolete uses of MultiOpenMixin, warn if there is no .startup/.shutdown method.\n* MultiOpenMixin.startup_shutdown: fix up shutdown logic, was not using a finally clause.\n* MultiOpenMixin: use ContextManagerMixin __enter_exit__ generator method instead of __enter__ and __exit__.\n\n*Release 20210906*:\nMultiOpenMixin: make startup and shutdown optional.\n\n*Release 20210731*:\nRunState: tune the sanity checks around whether the state is \"running\".\n\n*Release 20210420*:\nMultiOpenMixin: 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.\n\n*Release 20201025*:\nMultiOpenMixin.__mo_getstate: dereference self.__dict__ because using AttributeError was pulling a state object from another instance, utterly weird.\n\n*Release 20200718*:\nMultiOpenMixin: as a hack to avoid having an __init__, move state into an on demand object accesses by a private method.\n\n*Release 20200521*:\nSweeping removal of cs.obj.O, universally supplanted by types.SimpleNamespace.\n\n*Release 20190812*:\n* MultiOpenMixin: no longer subclass cs.obj.O.\n* MultiOpenMixin: remove `lock` param support, the mixin has its own lock.\n* MultiOpen: drop `lock` param support, no longer used by MultiOpenMixin.\n* MultiOpenMixin: do finalise inside the lock for the same reason as shutdown (competition with open/startup).\n* MultiOpenMixin.close: new `unopened_ok=False` parameter intended for callback closes which might fire even if the initial open does not occur.\n\n*Release 20190617*:\nRunState.__exit__: if an exception was raised call .canel() before calling .stop().\n\n*Release 20190103*:\n* Bugfixes for context managers.\n* MultiOpenMixin fixes and changes.\n* RunState improvements.\n\n*Release 20171024*:\n* bugfix MultiOpenMixin finalise logic and other small logic fixes and checs\n* new class RunState for tracking or controlling a running task\n\n*Release 20160828*:\nUse \"install_requires\" instead of \"requires\" in DISTINFO.\n\n*Release 20160827*:\n* BREAKING CHANGE: rename NestingOpenCloseMixin to MultiOpenMixin.\n* New Pool class for generic object reuse.\n* Assorted minor improvements.\n\n*Release 20150115*:\nFirst PyPI release.\n",
"bugtrack_url": null,
"license": "GNU General Public License v3 or later (GPLv3+)",
"summary": "Resource management classes and functions.",
"version": "20241005",
"project_urls": {
"MonoRepo Commits": "https://bitbucket.org/cameron_simpson/css/commits/branch/main",
"Monorepo Git Mirror": "https://github.com/cameron-simpson/css",
"Monorepo Hg/Mercurial Mirror": "https://hg.sr.ht/~cameron-simpson/css",
"Source": "https://github.com/cameron-simpson/css/blob/main/lib/python/cs/resources.py"
},
"split_keywords": [
"python2",
" python3"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "779700aa5807846921de793a5aa370f42c90a311995762f7b15a9ee2ed5d37e6",
"md5": "0d683aa6d9d4e548d2b050c9b1d3fbfe",
"sha256": "0bcb50bd12d38ead2a3005b8cf992e0fbeeee79cea05706eb007e55363f41ff2"
},
"downloads": -1,
"filename": "cs.resources-20241005-py3-none-any.whl",
"has_sig": false,
"md5_digest": "0d683aa6d9d4e548d2b050c9b1d3fbfe",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 19668,
"upload_time": "2024-10-05T08:56:05",
"upload_time_iso_8601": "2024-10-05T08:56:05.207281Z",
"url": "https://files.pythonhosted.org/packages/77/97/00aa5807846921de793a5aa370f42c90a311995762f7b15a9ee2ed5d37e6/cs.resources-20241005-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "4e24f3110487e4df9c12a0f26b87c3c6ce81a84a069dfea5ec09516382075ce3",
"md5": "b5b33f44a6f442301caf1d9905e09a66",
"sha256": "eff6b9f43f1848b06c147652beb93c7da49a69b199196e40e4c0d567c788661a"
},
"downloads": -1,
"filename": "cs_resources-20241005.tar.gz",
"has_sig": false,
"md5_digest": "b5b33f44a6f442301caf1d9905e09a66",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 35120,
"upload_time": "2024-10-05T08:56:06",
"upload_time_iso_8601": "2024-10-05T08:56:06.774974Z",
"url": "https://files.pythonhosted.org/packages/4e/24/f3110487e4df9c12a0f26b87c3c6ce81a84a069dfea5ec09516382075ce3/cs_resources-20241005.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-10-05 08:56:06",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "cameron-simpson",
"github_project": "css",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "cs.resources"
}