"""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))