Type Validators#
When the context
loads available plans, it iterates through the type signature and replaces any reference to a bluesky protocol with a new class that extends the original type. Defining a class validator on this new type allows it to check that the string being deserialised is the ID of a device of the correct type.
These new intermediate types are used only in the deserialisation process. The object returned from validator method is not checked by pydantic so it can be the actual instance and the plan never sees the runtime generated reference type, only the type it was expecting.
Note
This uses the fact that the new types generated at runtime have access to the context that required them via their closure. This circumvents the usual problem of pydantic validation not being able to access external state when validating or deserializing.
def my_plan(a: int, b: Readable) -> Plan:
...
# Becomes
class MyPlanModel(BaseModel):
a: int
b: Reference[Readable]
This also allows Readable
to be placed at various type levels. For example:
def my_weird_plan(
a: Readable,
b: List[Readable],
c: Dict[str, Readable],
d: List[List[Readable]],
e: List[Dict[str, Set[Readable]]]) -> Plan:
...