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_loads_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()
[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")
m = SomeModel()
m.some_field_dict["chosen_mistake"] = "whatever"
"""
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):
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."
)