Typed identity keys and lossless values, shared by the bulk fold and the writers.
An identity value keys as [kind, value] (:func:key_member), so 1 and
1.0 are one key and 1 and "1" two. Values JSON cannot hold (dates,
times, UUIDs, decimals) encode as {"$graflo": <type>, "v": <text>} and
decode back to the same Python value.
Attributes
TAG = '$graflo'
module-attribute
Classes
IdentityKeyError
Bases: ValueError
An identity value that cannot key a row: null, empty, NaN or not a scalar.
Source code in graflo/util/typed_values.py
| class IdentityKeyError(ValueError):
"""An identity value that cannot key a row: null, empty, NaN or not a scalar."""
|
Functions:
decode(value)
The inverse of :func:encode.
Source code in graflo/util/typed_values.py
| def decode(value: Any) -> Any:
"""The inverse of :func:`encode`."""
if isinstance(value, dict):
if len(value) == 2 and TAG in value and "v" in value:
return _DECODERS[value[TAG]](value["v"])
return {k: decode(v) for k, v in value.items()}
if isinstance(value, list):
return [decode(v) for v in value]
return value
|
encode(value)
value as JSON-ready data, non-JSON scalars tagged.
Raises:
| Type |
Description |
TypeError
|
A value graflo does not write.
|
Source code in graflo/util/typed_values.py
| def encode(value: Any) -> Any:
"""*value* as JSON-ready data, non-JSON scalars tagged.
Raises:
TypeError: A value graflo does not write.
"""
if value is None or isinstance(value, bool | int | str):
return value
if isinstance(value, float):
return value if math.isfinite(value) else {TAG: "float", "v": repr(value)}
if isinstance(value, dict):
return {str(k): encode(v) for k, v in value.items()}
if isinstance(value, list | tuple):
return [encode(v) for v in value]
if isinstance(value, dt.datetime):
return {TAG: "datetime", "v": value.isoformat()}
if isinstance(value, dt.date):
return {TAG: "date", "v": value.isoformat()}
if isinstance(value, dt.time):
return {TAG: "time", "v": value.isoformat()}
if isinstance(value, UUID):
return {TAG: "uuid", "v": str(value)}
if isinstance(value, Decimal):
return {TAG: "decimal", "v": str(value)}
raise TypeError(f"cannot stage a value of type {type(value).__name__}")
|
identity_member(value)
An edge identity field's value as part of the edge key; absent is null.
A value that cannot key a vertex keys an edge by its encoded form.
Source code in graflo/util/typed_values.py
| def identity_member(value: Any) -> Any:
"""An edge identity field's value as part of the edge key; absent is null.
A value that cannot key a vertex keys an edge by its encoded form.
"""
if value is None:
return None
try:
return key_member(value)
except IdentityKeyError:
return encode(value)
|
key_member(value)
One identity value as [kind, value], compared as the targets compare it.
Numbers compare by value (1 and 1.0 are one key), booleans apart
from numbers, strings apart from numbers (1 and "1" are two keys).
A UUID keys as its text, the way every backend stores it.
Raises:
Source code in graflo/util/typed_values.py
| def key_member(value: Any) -> list[Any]:
"""One identity value as ``[kind, value]``, compared as the targets compare it.
Numbers compare by value (``1`` and ``1.0`` are one key), booleans apart
from numbers, strings apart from numbers (``1`` and ``"1"`` are two keys).
A UUID keys as its text, the way every backend stores it.
Raises:
IdentityKeyError: The value cannot key a row.
"""
if value is None or value == "":
raise IdentityKeyError("empty identity value")
if isinstance(value, bool):
return ["b", value]
if isinstance(value, int):
return ["n", value]
if isinstance(value, float | Decimal):
if isinstance(value, float) and not math.isfinite(value):
raise IdentityKeyError(f"identity value {value!r} is not finite")
if value == int(value):
return ["n", int(value)]
return ["n", float(value)]
if isinstance(value, str):
return ["s", value]
if isinstance(value, UUID):
return ["s", str(value)]
if isinstance(value, dt.datetime):
return ["t", value.isoformat()]
if isinstance(value, dt.date):
return ["d", value.isoformat()]
if isinstance(value, dt.time):
return ["h", value.isoformat()]
raise IdentityKeyError(
f"identity value of type {type(value).__name__} is not a scalar"
)
|
typed_key(values)
The canonical text of a tuple of identity values; equal keys, equal text.
A composite identity may lack members, as an upsert matches them: an
absent one keys as null, an empty string as itself.
Raises:
| Type |
Description |
IdentityKeyError
|
No member has a value, or one cannot key a row.
|
Source code in graflo/util/typed_values.py
| def typed_key(values: Iterable[Any]) -> str:
"""The canonical text of a tuple of identity values; equal keys, equal text.
A composite identity may lack members, as an upsert matches them: an
absent one keys as null, an empty string as itself.
Raises:
IdentityKeyError: No member has a value, or one cannot key a row.
"""
values = list(values)
if all(v is None or v == "" for v in values):
raise IdentityKeyError("no identity value")
members = [
None if v is None else ["s", ""] if v == "" else key_member(v) for v in values
]
return json.dumps(members, separators=(",", ":"), ensure_ascii=False)
|