Source code for sf_toolkit.json_data

"""JSON-compatible object views; schemas are hints, not decoding prerequisites."""

from collections.abc import MutableMapping

from .data.fields import FieldConfigurableObject, ReferenceField, object_fields


def _as_model(model, value):
    if model is None:
        raise TypeError("This JSON object has no associated typed model")
    if isinstance(model, type) and issubclass(model, FieldConfigurableObject):
        return model._from_mapping(value)
    return model(**value)


def _attribute(data, model, name):
    if name.startswith("_"):
        raise AttributeError(name)
    fields = object_fields(model) if model is not None else {}
    if name not in data:
        if name in fields:
            return None
        raise AttributeError(name)
    value = data[name]
    if isinstance(value, dict):
        field = fields.get(name)
        child_model = field.meta_py_type if isinstance(field, ReferenceField) else None
        if not isinstance(child_model, type) or not issubclass(
            child_model, FieldConfigurableObject
        ):
            child_model = None
        return JsonView(value, model=child_model)
    return value


[docs] class JsonView(MutableMapping): """Optional attribute view over a dictionary, with no copying or coercion. Use dictionary access on the root response for native JSON values, or ``dict(view)`` when explicitly encoding an attribute view with json.dumps. """ __slots__ = ("_data", "_model")
[docs] def __init__(self, value, *, model=None): self._data = value self._model = model
def __getitem__(self, key): return self._data[key] def __setitem__(self, key, value): self._data[key] = value def __delitem__(self, key): del self._data[key] def __iter__(self): return iter(self._data) def __len__(self): return len(self._data) def __getattr__(self, name): return _attribute(self._data, self._model, name)
[docs] def as_model(self): return _as_model(self._model, dict(self._data))
[docs] class JsonObject(dict): """A normal JSON dictionary with optional non-coercing attribute access. Keys, values and missing/null distinctions stay in the dictionary. Payload keys never become implementation attributes. Use [] for keys colliding with methods or private names. Nested arrays remain ordinary JSON arrays/dicts. """ __slots__ = ("_model",)
[docs] def __init__(self, value, *, model=None): super().__init__(value) self._model = model
def __getattr__(self, name): return _attribute(self, self._model, name)
[docs] def as_model(self): """Explicitly opt into schema revival/validation; raw data stays available.""" return _as_model(self._model, dict(self))