Source code for flexeval.classes.jsonview
import json
from collections import UserDict
[docs]
class JsonViewDict(UserDict):
"""Dictionary that syncs changes back to the model field."""
def __init__(
self,
model_instance,
text_field_attr_name,
json_dumps_fn=json.dumps,
json_loads_fn=json.loads,
):
self.model_instance = model_instance
self.text_field_attr_name = text_field_attr_name
self.json_dumps_fn = json_dumps_fn
self.json_loads_fn = json_loads_fn
text_value = getattr(model_instance, text_field_attr_name)
initial_data = self.json_loads_fn(text_value)
super().__init__(initial_data)
def _sync_to_model(self):
"""Sync the current data back to the model field."""
json_str = self.json_dumps_fn(self.data)
setattr(self.model_instance, self.text_field_attr_name, json_str)
# Override mutating methods to trigger sync
def __setitem__(self, key, value):
super().__setitem__(key, value)
self._sync_to_model()
def __delitem__(self, key):
super().__delitem__(key)
self._sync_to_model()
def clear(self):
super().clear()
self._sync_to_model()
def pop(self, key, *args):
result = super().pop(key, *args)
self._sync_to_model()
return result
def popitem(self):
result = super().popitem()
self._sync_to_model()
return result
def setdefault(self, key, default=None):
result = super().setdefault(key, default)
self._sync_to_model()
return result
def update(self, *args, **kwargs):
super().update(*args, **kwargs)
self._sync_to_model()
def refresh_from_model(self):
"""If the text attribute has been mutated in the model, this method brings the view back in sync.
If you're going to use the JsonView, avoid mutating the text attribute directly.
"""
text_value = getattr(self.model_instance, self.text_field_attr_name)
self.update(self.json_loads_fn(text_value))
[docs]
class JsonView:
"""Descriptor that provides dict-like access to a JSON text field.
Example:
class SomeModel(pw.Model):
some_field = pw.TextField(default="{}")
some_field_dict = JsonView(text_field_attr_name="some_field")
"""
def __init__(self, text_field_attr_name):
self.text_field_attr_name = text_field_attr_name
self.attr_name = None
def __set_name__(self, owner, name):
"""Called when the descriptor is assigned to a class attribute."""
self.attr_name = f"_{name}_dict"
def __get__(self, instance, owner) -> JsonViewDict:
if instance is None:
return self
# Check if we already have a cached JsonViewDict
if not hasattr(instance, self.attr_name):
if not hasattr(instance, self.text_field_attr_name):
raise ValueError(
f"Failed to link this JsonView to field '{self.text_field_attr_name}' because it doesn't exist on this model instance."
)
# Cache a new JsonViewDict
json_dict = JsonViewDict(instance, self.text_field_attr_name)
setattr(instance, self.attr_name, json_dict)
return getattr(instance, self.attr_name)
def __set__(self, instance, value):
"""Allow setting the entire dict."""
if isinstance(value, dict):
json_dict = JsonViewDict(instance, self.text_field_attr_name)
json_dict.update(value)
setattr(instance, self.attr_name, json_dict)
else:
raise ValueError(
f"This JsonView must be a dictionary to set linked field '{self.text_field_attr_name}' correctly."
)