Null vs Omission
Nullability is explicit, and CleanChausie differentiates between:
- the field is required
- value is non-nullable
- value is nullable (if
None
is explicitly passed)
- the field is omittable
- the value is non-nullable
- the value is nullable
- the field has a default value (or value factory)
These variants can either be expressed explicitly, or CleanChausie will define them automatically to match a Schema's type annotations.
from typing import Optional, Union
from cleanchausie.consts import OMITTED
from cleanchausie.fields import field, StrField, Omittable, Required
from cleanchausie.schema import Schema
# auto define fields based on annotations
class NullabilityExample(Schema):
# field is required
nonnull_required: str
nullable_required: Optional[str]
# field is omittable
nonnull_omittable: Union[str, OMITTED]
nonnull_omittable_with_default: str = "default"
nullable_omittable: Optional[Union[str, OMITTED]]
nullable_omittable_with_default: Optional[str] = None
# or define the same fields explicitly
class NullabilityExplicitExample(Schema):
# field is required
nonnull_required = field(StrField())
nullable_required = field(StrField(), nullability=Required(allow_none=True))
# field is omittable
nonnull_omittable = field(StrField(), nullability=Omittable(allow_none=False))
nonnull_omittable_with_default = field(
StrField(),
nullability=Omittable(allow_none=False, omitted_value="default")
)
nullable_omittable = field(StrField(), nullability=Omittable(allow_none=True))
nullable_omittable_with_default = field(
StrField(), nullability=Omittable(allow_none=True, omitted_value=None)
)
For lists and other mutable types, an explicit field definition can specify
an omitted_value_factory
instead:
from cleanchausie import field, ListField, Omittable, Schema, StrField
class ListExample(Schema):
omittable_list_with_default = field(
ListField(StrField()),
nullability=Omittable(allow_none=True, omitted_value_factory=list)
)
OMITTED vs omitted?
CleanChausie uses a special omitted
singleton to represent the absence of a
value. Singletons are not well-supported by static type checkers, so
omitted
is implemented as a single-value enum (which is valid within a
Literal
).
OMITTED
is just an alias for Literal[omitted]
, and is used in type
annotations.
The drawback of this approach is that it's not possible to control implicit
behavior like __bool__
or __repr__
.
Fallback Utility
Want to use omitted
within your schema, but not within downstream code?
CleanChausie provides a fallback
utility to help:
from cleanchausie import OMITTED, Schema, clean, fallback
from .service import downstream_func, empty
class MySchema(Schema):
my_field: str | OMITTED
def process(data: dict):
result = clean(MySchema, data)
assert isinstance(result, MySchema)
return downstream_func(
val=fallback(result.my_field, empty)
)