labthings_fastapi.thing_slots
Facilitate connections between Things.
It is often desirable for two Things in the same server to be able to communicate.
In order to do this in a nicely typed way that is easy to test and inspect,
LabThings-FastAPI provides the thing_slot. This allows a Thing
to declare that it depends on another Thing being present, and provides a way for
the server to automatically connect the two when the server is set up.
Thing connections are set up after all the Thing instances are initialised.
This means you should not rely on them during initialisation: if you attempt to
access a connection before it is provided, it will raise an exception. The
advantage of making connections after initialisation is that circular connections
are not a problem: Thing a may depend on Thing b and vice versa.
As with properties, thing connections will usually be declared using the function
thing_slot rather than the descriptor directly. This allows them to be
typed and documented on the class, i.e.
import labthings_fastapi as lt
class ThingA(lt.Thing):
"A class that doesn't do much."
@lt.action
def say_hello(self) -> str:
"A canonical example function."
return "Hello world."
class ThingB(lt.Thing):
"A class that relies on ThingA."
thing_a: ThingA = lt.thing_slot()
@lt.action
def say_hello(self) -> str:
"I'm too lazy to say hello, ThingA does it for me."
return self.thing_a.say_hello()
Attributes
Classes
Descriptor that instructs the server to supply other Things. |
Module Contents
- labthings_fastapi.thing_slots.ThingSubclass
- labthings_fastapi.thing_slots.ConnectedThings
- class labthings_fastapi.thing_slots.ThingSlot(*, default: str | None | collections.abc.Iterable[str] | types.EllipsisType = ...)
Bases:
Generic[ConnectedThings],labthings_fastapi.base_descriptor.FieldTypedBaseDescriptor[labthings_fastapi.thing.Thing,ConnectedThings]Descriptor that instructs the server to supply other Things.
A
ThingSlotprovides either one or severalThinginstances as a property of aThing. This allowsThings to communicate with each other within the server, including accessing attributes that are not exposed over HTTP.While it is possible to dynamically retrieve a
Thingfrom theThingServerthis is not recommended: using Thing Connections ensures all theThinginstances are available before the server starts, reducing the likelihood of run-time crashes.The usual way of creating these connections is the function
thing_slot. This class and its subclasses are not usually instantiated directly.The type of the
ThingSlotattribute is key to its operation. It should be assigned to an attribute typed either as aThingsubclass, a mapping of strings toThingor subclass instances, or an optionalThinginstance:class OtherExample(lt.Thing): pass class Example(lt.Thing): # This will always evaluate to an `OtherExample` other_thing: OtherExample = lt.thing_slot("other_thing") # This may evaluate to an `OtherExample` or `None` optional: OtherExample | None = lt.thing_slot("other_thing") # This evaluates to a mapping of `str` to `.Thing` instances things: Mapping[str, OtherExample] = lt.thing_slot(["thing_a"])
Declare a ThingSlot.
- Parameters:
default –
The name of the Thing(s) that will be connected by default.
If the type is optional (e.g.
ThingSubclass | None) a default value ofNonewill result in the connection evaluating toNoneunless it has been configured by the server.If the type is not optional, a default value of
Nonewill result in an error, unless the server has set another value in its configuration.If the type is a mapping of
strtoThingthe default should be of typeIterable[str](and could be an empty list).
- _default = Ellipsis
- _things: weakref.WeakKeyDictionary[labthings_fastapi.thing.Thing, weakref.ReferenceType[labthings_fastapi.thing.Thing] | weakref.WeakValueDictionary[str, labthings_fastapi.thing.Thing] | None]
- property thing_type: tuple[type, Ellipsis]
The
Thingsubclass(es) returned by this connection.A tuple is returned to allow for optional thing connections that are typed as the union of two Thing types. It will work with
isinstance.
- property default: str | collections.abc.Iterable[str] | None | types.EllipsisType
The name of the Thing that will be connected by default, if any.
- _pick_things(things: Mapping[str, Thing], target: str | collections.abc.Iterable[str] | None | types.EllipsisType) Sequence[Thing]
Pick the Things we should connect to from a list.
This function is used internally by
ThingSlot.connectto choose the Things we return when theThingSlotis accessed.- Parameters:
- Raises:
ThingSlotError – if the supplied
Thingis of the wrong type, if a sequence is supplied when a singleThingis required, or ifNoneis supplied and the connection is not optional.TypeError – if
targetis not one of the allowed types.
KeyErrorwill also be raised if names specified intargetdo not exist inthings.- Returns:
a list of
Thinginstances to supply in response to__get__.
- connect(host: labthings_fastapi.thing.Thing, things: Mapping[str, Thing], target: str | collections.abc.Iterable[str] | None | types.EllipsisType = ...) None
Find the
Thing(s) we should supply when accessed.This method sets up a ThingSlot on
host_thingby finding theThinginstance(s) it should supply when its__get__method is called. The logic for determining this is:If
targetis specified, we look for the specifiedThing(s).Nonemeans we should returnNone- that’s only allowed if the type hint permits it.If
targetis not specified or is...we use the default value set when the connection was defined.If the default value was
...and no target was specified, we will attempt to find theThingby type. Most of the time, this is the desired behaviour.
If the type of this connection is a
Mapping,targetshould be a sequence of names. This sequence may be empty.Noneis treated as equivalent to the empty list, and a list with one name in it is treated as equivalent to a single name.If the type hint of this connection does not permit
None, and eitherNoneis specified, or notargetis given and the default is set asNone, then an error will be raised.Nonewill only be returned at runtime if it is permitted by the type hint.- Parameters:
- Raises:
ThingSlotError – if the supplied
Thingis of the wrong type, if a sequence is supplied when a singleThingis required, or ifNoneis supplied and the connection is not optional.
- instance_get(obj: labthings_fastapi.thing.Thing) ConnectedThings
Supply the connected
Thing(s).- Parameters:
obj – The
Thingon which the connection is defined.- Returns:
the
Thinginstance(s) connected.- Raises:
ThingNotConnectedError – if the ThingSlot has not yet been set up.
ReferenceError – if a connected Thing no longer exists (should not ever happen in normal usage).
Typing notes:
This must be annotated as
ConnectedThingswhich is the type variable corresponding to the type of this connection. The type determined at runtime will be within the upper bound ofConnectedThingsbut it would be possible forConnectedThingsto be more specific.In general, types determined at runtime may conflict with generic types, and at least for this class the important thing is that types determined at runtime match the attribute annotations, which is tested in unit tests.
The return statements here consequently have their types ignored.