class FakeThing(JsonRecord): att_a = JsonProperty() att_b = JsonProperty() att_c = JsonListProperty(of=FakeItem)
class FakeItem(JsonRecord): service_id = JsonProperty(json_name="id") primary_key = [service_id] name = JsonProperty()
class JsonRecord(Record): """Version of a Record which deals primarily in JSON form. This means: 1. The first argument to the constructor is assumed to be a JSON data dictionary, and passed through the class' ``json_to_initkwargs`` method before being used to set actual properties 2. Unknown keys are permitted, and saved in the "unknown_json_keys" property, which is merged back on output (ie, calling ``.json_data()`` or ``to_json()``) """ unknown_json_keys = JsonProperty(json_name=None, extraneous=True) def __init__(self, json_data=None, **kwargs): """Build a new JsonRecord sub-class. args: ``json_data=``\ *DICT|other* JSON data (string or already ``json.loads``'d). If not a JSON dictionary with keys corresponding to the ``json_name`` or the properties within, then ``json_to_initkwargs`` should be overridden to handle the unpacking differently ``**kwargs`` ``JsonRecord`` instances may also be constructed by passing in attribute initializers in keyword form. The keys here should be the names of the attributes and the python values, not the JSON names or form. """ if isinstance(json_data, OhPickle): return if isinstance(json_data, basestring): json_data = json.loads(json_data) if json_data is not None: kwargs = type(self).json_to_initkwargs(json_data, kwargs) super(JsonRecord, self).__init__(**kwargs) @classmethod def json_to_initkwargs(self, json_data, kwargs): """Subclassing hook to specialize how JSON data is converted to keyword arguments""" if isinstance(json_data, basestring): json_data = json.loads(json_data) return json_to_initkwargs(self, json_data, kwargs) @classmethod def from_json(self, json_data): """This method can be overridden to specialize how the class is loaded when marshalling in; however beware that it is not invoked when the caller uses the ``from_json()`` function directly.""" return self(json_data) def json_data(self, extraneous=False): """Returns the JSON data form of this ``JsonRecord``. The 'unknown' JSON keys will be merged back in, if: 1. the ``extraneous=True`` argument is passed. 2. the ``unknown_json_keys`` property on this class is replaced by one not marked as ``extraneous`` """ jd = to_json(self, extraneous) if hasattr(self, "unknown_json_keys"): prop = type(self).properties['unknown_json_keys'] if extraneous or not prop.extraneous: for k, v in self.unknown_json_keys.iteritems(): if k not in jd: jd[k] = v return jd def diff_iter(self, other, **kwargs): """Generator method which returns the differences from the invocant to the argument. This specializes :py:meth:`Record.diff_iter` by returning :py:class:`JsonDiffInfo` objects. """ for diff in super(JsonRecord, self).diff_iter(other, **kwargs): # TODO: object copy/upgrade constructor newargs = diff.__getstate__() yield JsonDiffInfo(**(newargs)) def diff(self, other, **kwargs): """Compare an object with another. This specializes :py:meth:`Record.diff` by returning a :py:class:`JsonDiff` object. """ return JsonDiff( base_type_name=type(self).__name__, other_type_name=type(other).__name__, values=self.diff_iter(other, **kwargs), )
class AutoJsonRecord(JsonRecord): unknown_json_keys = JsonProperty( json_name=None, extraneous=False, default=lambda: {}, ) @classmethod def auto_upgrade_dict(cls, thing): return AutoJsonRecord(thing) @classmethod def auto_upgrade_list(cls, thing): if len(thing) and isinstance(thing[0], dict): return list_of(AutoJsonRecord)(thing) else: return thing @classmethod def auto_upgrade_any(cls, thing): if isinstance(thing, dict): return cls.auto_upgrade_dict(thing) elif isinstance(thing, list): return cls.auto_upgrade_list(thing) else: return thing @classmethod def convert_json_key_in(cls, key): return re.sub( r'([a-z\d])([A-Z])', lambda m: "%s_%s" % (m.group(1), m.group(2).lower()), key, ) @classmethod def convert_json_key_out(cls, key): return re.sub( r'([a-z\d])_([a-z])', lambda m: "%s%s" % (m.group(1), m.group(2).upper()), key, ) @classmethod def json_to_initkwargs(cls, json_data, kwargs): kwargs = super(AutoJsonRecord, cls).json_to_initkwargs( json_data, kwargs, ) # upgrade any dictionaries to AutoJsonRecord, and # any lists of dictionaries to list_of(AutoJsonRecord) if 'unknown_json_keys' in kwargs: kwargs['unknown_json_keys'] = { cls.convert_json_key_in(k): cls.auto_upgrade_any(v) for k, v in list(kwargs['unknown_json_keys'].items()) } return kwargs def json_data(self, extraneous=False): jd = to_json(self, extraneous) if hasattr(self, "unknown_json_keys"): prop = type(self).properties['unknown_json_keys'] if extraneous or not prop.extraneous: for k, v in self.unknown_json_keys.items(): k = type(self).convert_json_key_out(k) if k not in jd: jd[k] = to_json(v, extraneous) return jd def __getattr__(self, attr): if attr in type(self).properties: return type(self).properties[attr].__get__(self) else: return self.unknown_json_keys[attr] def __setattr__(self, attr, value): if attr in type(self).properties: return type(self).properties[attr].__set__(self) else: self.unknown_json_keys[attr] def __delattr__(self, attr, value): if attr in type(self).properties: return type(self).properties[attr].__del__(self) else: del self.unknown_json_keys[attr]
class EnumsGalore(JsonRecord): my_enum = JsonProperty(isa=MyEnum.EnumValue) enum_list = JsonListProperty(of=MyEnum.EnumValue) enum_map = JsonDictProperty(of=MyEnum.EnumValue)
class Banana(JsonRecord): color = Property(isa=str) length = Property(isa=int) contents = JsonProperty(isa=Fruit) vitamins = JsonProperty(isa=str)