Slabs design #49
Replies: 1 comment
-
Slabs setup and teardownRecently, I extended with self: # enter all the contexts that need to be entered
while True: # loop until you encounter a handled exception
try:
yield next(self)
except ... But there was no hook to be able to specify the finally block of a try/except clause. I thought I needed one, so I implemented one. Removing
|
Beta Was this translation helpful? Give feedback.
-
There's a bit more history before the point I'm showing now, which I might describe another day. Now, I'll start at a point the allows us to see the basic ideas of the design.
handling exceptions
First, I'd like to note that "exception" doesn't mean "error", though that's is the typical kind of exception we see.
In the simple code above, we see that we can specify a collection of exception types that will lead to breaking out of the loop without a fuss (silently). We can use this to specify, in which conditions should stop the otherwise infinite iteration of the slabs without raising an error or otherwise bugging the user about it: For example, when there's a
KeyBoardInterrupt
or some other custom exception. This can be used by any of the components to "signal" to the iteration that we should quit.For example, a component reads the state of a switch, and if the switch is off, it raises a
SwitchIsOff
exception, which will then have the effect of exiting the loop.There's the main idea: We want to be able to use exceptions to control the (normal) flow described by the mesh of our components.
There's more to an iteration's life than quitting though.
That's why we need more. Consider the following evolution of the exception block.
Note that here,
self.handle_exceptions
is no longer a tuple, but adict
whose keys are exception types and the values areexception_handler
callback functions that should be called if an exception of that type happens. For details on exception type matching (it's more complicated than you think) and what the callback function has visibility on, see the_handle_exception
function.Here's an example of use. Your slabs are there to compute some really smart and energy-consuming stuff, but only if the light switch is on. One way to achieve this is to have all components take as an input the light switch state and condition their execution on that. Compare that too simply having an early component look at the state of the switch, and if off, raise a
SwitchIsOff
exception with an associated handler that waits a bit (to not get right back in the loop) and returns thedo_not_break
sentinel to get back in the loop.Still, we can do more. Note that in the code above:
do_not_break
sentinel to specify otherwise. Is this the right default? Should we add an init argument so that a user can control this ** <-- DISCUSS**execution
The
_call_on_scope
is akin tomeshed.DAG.call_on_scope
(which itself usesFuncNode.call_on_scope
): The sequential execution of functions, reading inputs from and writing outputs to the scope (atyping.MutableMapping
). Not that there are other choices (which we'll hopefully explore at some point soon) of execution. We can use concurrent execution instead of sequential, or even use the DAG's structure as a declarative, as opposed to an imperative, specification. We could interpret the DAG's structure as the specification of reactive components, for instance.scope
Note that a lot can be done by specifying a custom
scope
(which acts as thelocals()
dictionary of "normal" python execution).For example, one can have
MutableMapping
, such as a message-bus or http serviceIn
DAG
, the default scope is a dict, constructed afresh on every call, but you can specify the scope at call time, or specify a scope factory that it will use to make a new scope on every call.Initially (as seen above) we didn't have that in
Slabs
, so we changed__next__
to:where
scope_factory
is given at initiation.Beta Was this translation helpful? Give feedback.
All reactions