nexosim.types
Type-related utilities.
This module provides facilities to conveniently build Python counterparts to
Rust's struct and enum types that can be (de)serialized as events, requests
or replies within a Simulation.
Struct-like classes
Rust distinguishes between regular struct types (with named fields), unit
struct types and tuple struct types. Because these types are serialized
differently, it is important to preserve this distinction on the Python side.
Regular Rust struct types can be directly represented in Python with
dataclasses, using e.g. the standard @dataclasses.dataclass decorator or the
@define decorator from the attrs package.
Unit struct types are in turn represented by empty classes deriving from the
UnitType class. Likewise, tuple struct types are
represented by deriving from one of the TupleType*Arg classes.
Here are example Rust struct types and their Python counterparts:
In the case of tuple struct types, if you don't use a type checker or if your
type checker supports dynamically-generated base classes (as of the day of this
writing, this is the case for Pyright but not Mypy), it is possible to save
a bit of typing by using the convenience
tuple_type type constructor instead of the
TupleType*Arg classes. For instant, the above MyTupleStruct could be instead
defined with:
Note that tuple struct types with arity greater than 16 must be defined
manually with the tupleclass decorator. This is
for instance how TupleType2Arg is defined:
from nexosim.types import variantclass
@tupleclass
class TupleType2Arg[Arg0, Arg1]:
_0: Arg0
_1: Arg1
Enum-like classes
Rust enum types are represented by classes decorated with
@enumclass, defining the variants as nested
classes.
Unit-like, tuple-like and struct-like variants are defined in exactly the
same way as struct-like classes.
Warning
Classes decorated with @enumclass implicitly
define an enum type but are not the enum type itself.
Rather, the enum type is represented by a Python Union of the nested
variant types, which for convenience is automatically generated by the
@enumclass decorator and exposed as the type class variable.
Here's an example Rust enum and its Python counterpart:
from dataclasses import dataclass
from nexosim.types import enumclass, UnitType, TupleType2Arg
@enumclass
class MyEnum:
class MyUnitVariant(UnitType): ...
class MyTupleVariant(TupleType2Arg[int, str]): ...
@dataclass
class MyStructVariant:
foo: float
bar: int
# The following class variable is automatically generated:
#
# type = MyUnitVariant | MyTupleVariant | MyStructVariant
If the above enum were in turn used within a Rust struct, it would be represented like this:
Type checking enums
Because the type field is dynamically added to the enum class definition by
the @enumclass decorator, it is invisible to type
checkers. This would in particular cause type checkers to complain in the
earlier declaration of MyComposedStruct, even though its definition is valid
and usable for serialization and deserialization purposes.
To prevent such false positives, you may elect to explicitly set the type
field, for instance:
@enumclass
class MyEnum:
class MyUnitVariant(UnitType): ...
class MyTupleVariant(TupleType2Arg[int, str]): ...
@dataclass
class MyStructVariant:
foo: float
bar: int
type = MyUnitVariant | MyTupleVariant | MyStructVariant
In order to prevent mistakes, the @enumclass
decorator raises an exception if an explicitly-set type field is not
equivalent to the auto-generated one.
Pattern matching
Tuple- and struct-like enum variants are dataclasses, either because they are
explicitly marked as such (struct-like variants) or because they derive from
dataclasses (tuple-like variants). This implies that enums can be
structurally matched.
This is an example of pattern matching for the previously-defined MyEnum:
def print_my_enum(my_enum):
match my_enum:
case MyEnum.MyUnitVariant():
print("MyUnitVariant")
case MyEnum.MyTupleVariant(number, string):
print(f"MyTupleVariant({number}, {string})")
case MyEnum.MyStructVariant(foo=x, bar=y):
print(f"MyStructVariant(foo={x}, bar={y})")
case _:
raise RuntimeError("unexpected enum variant")
Explicitly setting the type field
additionally enables exhaustiveness checking. For instance, if the type field
is explicitly set in MyEnum's definition, the missing case in the following
code can be caught by the type checker:
def my_enum_discriminant(my_enum: MyEnum.type) -> int:
match my_enum: # ERROR: function does not return `int` on all code paths
case MyEnum.MyUnitVariant():
return 0
case MyEnum.MyTupleVariant(_):
return 1
UnitType
Base class for unit-like types.
TupleType0Arg
Base class for nullary tuple-like types.
TupleType1Arg
Bases: Generic[_Arg0]
Base class for unary tuple-like types.
TupleType2Arg
Bases: Generic[_Arg0, _Arg1]
Base class for binary tuple-like types.
TupleType3Arg
Bases: Generic[_Arg0, _Arg1, _Arg2]
Base class for ternary tuple-like types.
TupleType4Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3]
Base class for 4-ary tuple-like types.
TupleType5Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4]
Base class for 5-ary tuple-like types.
TupleType6Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5]
Base class for 6-ary tuple-like types.
TupleType7Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6]
Base class for 7-ary tuple-like types.
TupleType8Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7]
Base class for 8-ary tuple-like types.
TupleType9Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8]
Base class for 9-ary tuple-like types.
TupleType10Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9]
Base class for 10-ary tuple-like types.
TupleType11Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10]
Base class for 11-ary tuple-like types.
TupleType12Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11]
Base class for 12-ary tuple-like types.
TupleType13Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12]
Base class for 13-ary tuple-like types.
TupleType14Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13]
Base class for 14-ary tuple-like types.
TupleType15Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13, _Arg14]
Base class for 15-ary tuple-like types.
TupleType16Arg
Bases: Generic[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13, _Arg14, _Arg15]
Base class for 16-ary tuple-like types.
enumclass(cls)
Class decorator that marks a class as the definition of a Rust-like
enum.
Note that the decorated class is not the enum type itself: the Python
Union of all variants that actually represents the enum is generated by
this decorator and exposed as the type class variable.
tupleclass(cls)
Decorator that marks a class as a tuple-like type.
This automatically makes the class a dataclass.
tuple_type(*args)
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3]) -> TupleType4Arg[_Arg0, _Arg1, _Arg2, _Arg3]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4]) -> TupleType5Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5]) -> TupleType6Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6]) -> TupleType7Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7]) -> TupleType8Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8]) -> TupleType9Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9]) -> TupleType10Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10]) -> TupleType11Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11]) -> TupleType12Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11], _12: type[_Arg12]) -> TupleType13Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11], _12: type[_Arg12], _13: type[_Arg13]) -> TupleType14Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11], _12: type[_Arg12], _13: type[_Arg13], _14: type[_Arg14]) -> TupleType15Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13, _Arg14]
tuple_type(_0: type[_Arg0], _1: type[_Arg1], _2: type[_Arg2], _3: type[_Arg3], _4: type[_Arg4], _5: type[_Arg5], _6: type[_Arg6], _7: type[_Arg7], _8: type[_Arg8], _9: type[_Arg9], _10: type[_Arg10], _11: type[_Arg11], _12: type[_Arg12], _13: type[_Arg13], _14: type[_Arg14], _15: type[_Arg15]) -> TupleType16Arg[_Arg0, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5, _Arg6, _Arg7, _Arg8, _Arg9, _Arg10, _Arg11, _Arg12, _Arg13, _Arg14, _Arg15]
A convenient type constructor for tuple-like types.
This function can saves some typing and reduce import boilerplate compared
to manually specifying the TupleType*Arg base classes. It takes the types
of the components as arguments and is overloaded by arity to return the
appropriate TupleType*Arg class.
Warning
Note that not all type checkers support dynamically generated base
classes and it may be necessary to manally specify the appropriate
TupleType*Arg base class in order to satisfy the type checker.