Namespaces¶
Namespaces enable covariance in the type returned by unmarshaling. This means that a superclass can be specified as the type and one of its subclasses is constructed.
A namespace is a set of types, each associated with its own unique name. There are no other limitations to the inheritance hierarchy beyond that all types must be subtypes of the base class of the namespace.
Note
Namespaces are applied through the entire framework. In the examples below
marsh.unmarshal(...)()
is used but the namespace functionality is applied
recursively and thus affects nested types (for example, the types of the
attributes of a dataclass) as well.
A new namespace is created by supplying a name and a base class.
import marsh
class Model:
def predict(
self,
input: List[float]
) -> float:
raise NotImplementedError
namespace = marsh.namespaces.new('model', Model)
Types can be added to the namespace through the registration function.
@namespace.register(name='a')
class ModelA(Model):
def __init__(
self,
layers: int
) -> None:
...
@namespace.register(name='b')
class ModelB(Model):
def __init__(
self,
clusters: int
) -> None:
...
Once added, the types can be selected during unmarshaling by specifying
a 'name'
field in the input. The name field is stripped before the rest
of the inputs are passed to the constructor of the type corresponding to the name.
model = marsh.unmarshal(Model, dict(name='a', layers=3))
assert isinstance(model, ModelA)
If no name is supplied with the input when unmarshaling the return type is invariant relative to the input type.
model = marsh.unmarshal(Model, {})
assert isinstance(model, Model)
It is not required that the type given to marsh.unmarshal()
is the base
class of the namespace. Other classes in the inheritance hierarchy may be
provided (even if they themselves are not registered to the namespace).
class StatefulModel(Model):
"""Part of inheritance hierarchy but not registered
to the namespace."""
def get_state(
self
) -> bytes:
raise NotImplementedError
@namespace.register(name='c')
class ModelC(StatefulModel):
...
@namespace.register(name='d')
class ModelD(StatefulModel):
...
# here we can use name='c' or name='d'. The other models
# do not inherit `StatefulModel` and specifying their names
# would result in an error.
model = marsh.unmarshal(StatefulModel, dict(name='c'))
assert isinstance(model, ModelC)