Coerce a raw value — typically text read off a document — to a field's declared type.
The inverse of :func:~graflo.architecture.onto_sample.infer_field_type: that
one reads a type off values, this one makes a value conform to a declared type.
Every refusal is a :class:CoercionError whose kind says what went wrong,
so a caller can group failures and ask for a targeted correction.
Attributes
CoercionKind = Literal['type', 'unit_mismatch', 'list_item', 'empty']
module-attribute
Classes
CoercionError
Bases: ValueError
A value that does not conform to its field's declared type.
Attributes:
| Name |
Type |
Description |
field |
|
Name of the field being coerced.
|
value |
|
The raw value as received.
|
kind |
|
type (wrong shape for the type), unit_mismatch (a unit
other than the declared one), list_item (an item of a list
failed; see index) or empty (nothing to coerce).
|
index |
|
Position of the failing item when kind is list_item.
|
Source code in graflo/architecture/schema/coerce.py
| class CoercionError(ValueError):
"""A value that does not conform to its field's declared type.
Attributes:
field: Name of the field being coerced.
value: The raw value as received.
kind: ``type`` (wrong shape for the type), ``unit_mismatch`` (a unit
other than the declared one), ``list_item`` (an item of a list
failed; see ``index``) or ``empty`` (nothing to coerce).
index: Position of the failing item when ``kind`` is ``list_item``.
"""
def __init__(
self,
message: str,
*,
field: str,
value: Any,
kind: CoercionKind,
index: int | None = None,
) -> None:
super().__init__(message)
self.field = field
self.value = value
self.kind = kind
self.index = index
|
Attributes
field = field
instance-attribute
index = index
instance-attribute
kind = kind
instance-attribute
value = value
instance-attribute
Methods:
__init__(message, *, field, value, kind, index=None)
Source code in graflo/architecture/schema/coerce.py
| def __init__(
self,
message: str,
*,
field: str,
value: Any,
kind: CoercionKind,
index: int | None = None,
) -> None:
super().__init__(message)
self.field = field
self.value = value
self.kind = kind
self.index = index
|
Functions:
coerce_value(value, field)
Return value as the Python value field declares.
INT / UINT: an int; integral floats and digit strings are
accepted, bool and grouped digits (1,000) are not. UINT
refuses negatives.
FLOAT / DOUBLE: a finite float.
BOOL: a bool; true/false/yes/no/1/0 in any case.
STRING: a stripped str; numbers are stringified.
DATETIME: an ISO 8601 string, not a datetime. A space
separator becomes T and z becomes Z. The partial dates
YYYY and YYYY-MM are kept as written, never padded.
UUID: a lower-cased UUID string.
LIST: a list of item_type values; a bare scalar becomes a
one-item list.
- Untyped: the value unchanged.
For a numeric field (or a list of numerics) whose semantics.unit is
set, a string carrying exactly that unit ("200 kV" for kV) has it
stripped. Unit matching is case-sensitive; no conversion is attempted.
Raises:
| Type |
Description |
CoercionError
|
when value is empty or does not conform.
|
Source code in graflo/architecture/schema/coerce.py
| def coerce_value(value: Any, field: Field) -> Any:
"""Return *value* as the Python value *field* declares.
- ``INT`` / ``UINT``: an ``int``; integral floats and digit strings are
accepted, ``bool`` and grouped digits (``1,000``) are not. ``UINT``
refuses negatives.
- ``FLOAT`` / ``DOUBLE``: a finite ``float``.
- ``BOOL``: a ``bool``; ``true/false/yes/no/1/0`` in any case.
- ``STRING``: a stripped ``str``; numbers are stringified.
- ``DATETIME``: an ISO 8601 **string**, not a ``datetime``. A space
separator becomes ``T`` and ``z`` becomes ``Z``. The partial dates
``YYYY`` and ``YYYY-MM`` are kept as written, never padded.
- ``UUID``: a lower-cased UUID string.
- ``LIST``: a list of ``item_type`` values; a bare scalar becomes a
one-item list.
- Untyped: the value unchanged.
For a numeric field (or a list of numerics) whose ``semantics.unit`` is
set, a string carrying exactly that unit (``"200 kV"`` for ``kV``) has it
stripped. Unit matching is case-sensitive; no conversion is attempted.
Raises:
CoercionError: when *value* is empty or does not conform.
"""
if _is_empty(value):
raise CoercionError(
f"{field.name}: no value", field=field.name, value=value, kind="empty"
)
if field.type is None:
return value
unit = field.semantics.unit if field.semantics is not None else None
ftype = FieldType(field.type)
if ftype == FieldType.LIST:
item_type = FieldType(field.item_type or FieldType.STRING)
items = list(value) if isinstance(value, (list, tuple)) else [value]
out = []
for index, item in enumerate(items):
try:
out.append(_coerce_scalar(item, item_type, unit, field.name))
except CoercionError as error:
raise CoercionError(
f"{field.name}[{index}]: {error}",
field=field.name,
value=value,
kind="list_item",
index=index,
) from error
return out
return _coerce_scalar(value, ftype, unit, field.name)
|