UniteLabs
Tutorial

Complex Data Structures

Keep your parameters and return values typed and constrained.

Data types are specified according to PEP 484. When using the UniteLabs CDK type hints are automatically converted to the respective SiLA data types by the SiLA decorators. The data types, as specified by the SiLA 2 Standard Part A, and their respective counterparts are defined in the data_types module of the CDK.

Up to this point in the tutorials all of our function signatures have utilized native python data types. It is, however, important that we be able to define or derive our own types to keep our data well structured and human-readable.

CustomDataType

Custom Data Types can be created using the python stdlib dataclasses module and are generally best defined in the same file as the feature that will utilize that data type. Custom data types are a part of a SiLA Feature, rather than the Connector Protocol, and primarily serve to organize and clearly annotate inputs and outputs for the end-user.

CustomDataTypes may be composed of any mixture of valid SiLA data types and other CustomDataTypes.

Let's take as an example the LabwareTransferFeature in the feature library.

In the LabwareTransferManipulatorControllerBase we see the following method:

@sila.UnobservableCommand()
async def prepare_for_input(
    self,
    handover_position: HandoverPosition,
    internal_position: PositionIndex,
    labware_type: str,
    labware_unique_ID: str,
) -> str:

This method utilizes CustomDataTypes to specify how structured data required by the method can be supplied by the user with HandoverPosition defined as follows:

@dataclasses.dataclass
class HandoverPosition(sila.CustomDataType):
    """
    Specifies one of the possible positions of a device where labware items can be handed over.
    Can contain a sub-position, e.g. for specifying a position in a rack.
    """

    position: str
    sub_position: PositionIndex

Here we see the mixed usage of basic data types and CustomDataTypes. Continue reading to learn how you can further improve your CustomDataType annotations with Constraints.

Constraints

We can also enforce limitations on standard data types through the addition of Constraint annotations to our type-hints.

Let's say that we have a function that takes a string input:

def my_func(input: string) -> None:
    if input == "a":
        do_a()
    elif input == "b":
        do_b()

Here we only have two valid input values, so we might want to limit the inputs that a user can provide to this function. With constraints this is simply a matter of wrapping the current parameter type-hint in typing.Annotated and adding as many constraints as desired.

def my_func(
  input: typing.Annotated[str, sila.constraints.Set(["a", "b"])]
) -> None:
  ...

Now any value supplied which is not "a" or "b" will automatically raise a ValidationError.

Constraints can be used in SiLA endpoints directly as part of the parameter type hinting or can be built into CustomDataTypes, like the PositionIndex from the LabwareTransferManipulatorControllerBase:

@dataclasses.dataclass
class PositionIndex(sila.CustomDataType):
    """Specifies a position via an index number, starting at 1."""

    position_index: typing.Annotated[int, sila.constraints.MinimalInclusive(value=1)]

Constraints come with pre-existing validations that are applied automatically before submission when data is provided by end-users.

Read up on available constraints in the installed Python SiLA library.


Copyright © 2025