def writable( model # type: Union[type, serial.model.Model] ): # type: (...) -> Union[Object, Mapping, str] if isinstance(model, serial.model.Model): if model._meta is None: model._meta = deepcopy(writable(type(model))) elif issubclass(model, serial.model.Model): if model._meta is None: model._meta = ( Object() if issubclass(model, serial.model.Object) else Array() if issubclass(model, serial.model.Array) else Dictionary() if issubclass(model, serial.model.Dictionary) else None ) else: for b in model.__bases__: if hasattr(b, '_meta') and (model._meta is b._meta): model._meta = deepcopy(model._meta) break else: repr_model = repr(model) raise TypeError( '%s requires a parameter which is an instance or sub-class of `%s`, not%s' % ( serial.utilities.calling_function_qualified_name(), qualified_name(serial.model.Model), ( ':\n' + repr_model if '\n' in repr_model else ' `%s`' % repr_model ) ) ) return model._meta
def format_(model, serialization_format=_UNIDENTIFIED): # type: (serial.model.Model, Optional[str]) -> Optional[str] if not isinstance(model, serial.model.Model): raise TypeError('`model` must be an instance of `%s`, not %s.' % (qualified_name(serial.model.Model), repr(model))) if serialization_format is not _UNIDENTIFIED: if not isinstance(serialization_format, str): raise TypeError('`serialization_format` must be a `str`, not %s.' % repr(serialization_format)) model._format = serialization_format if isinstance(model, serial.model.Dictionary): for v in model.values(): if isinstance(v, serial.model.Model): format_(v, serialization_format) elif isinstance(model, serial.model.Object): for pn in read(model).properties.keys(): v = getattr(model, pn) if isinstance(v, serial.model.Model): format_(v, serialization_format) elif isinstance(model, serial.model.Array): for v in model: if isinstance(v, serial.model.Model): format_(v, serialization_format) return model._format
def read( model # type: Union[type, serial.model.Model] ): # type: (...) -> Union[Object, Mapping, str] if isinstance( model, serial.model.Model ): return model._meta or read(type(model)) elif issubclass(model, serial.model.Model): return model._meta else: repr_model = repr(model) raise TypeError( '%s requires a parameter which is an instance or sub-class of `%s`, not%s' % ( serial.utilities.calling_function_qualified_name(), qualified_name(serial.model.Model), ( ':\n' + repr_model if '\n' in repr_model else ' `%s`' % repr_model ) ) )
def json_object( o, # type: Union[model.Object, typing.Sequence] raise_validation_errors=True, # type: bool ): # type: (...) -> None if isinstance(o, model.Object): errors = model.validate(o, raise_errors=raise_validation_errors) if errors: warn('\n' + '\n'.join(errors)) t = type(o) string = str(o) assert string != '' #print(t) #print('string: ' + string) reloaded = t(string) try: assert o == reloaded except AssertionError as e: message = [ 'Discrepancies were found between the instance of `%s` provided and ' % qualified_name(type(o)) + 'a serialized/deserialized clone:' ] for k, a_b in _object_discrepancies(o, reloaded).items(): a, b = a_b sa = model.serialize(a) sb = model.serialize(b) message.append( '\n %s().%s:\n\n %s\n %s\n %s' % (qualified_name( type(o)), k, sa, '==' if sa == sb else '!=', sb)) ra = ''.join(l.strip() for l in repr(a).split('\n')) rb = ''.join(l.strip() for l in repr(b).split('\n')) message.append('\n %s\n %s\n %s' % (ra, '==' if ra == rb else '!=', rb)) e.args = tuple( chain((e.args[0] + '\n' + '\n'.join(message) if e.args else '\n'.join(message), ), e.args[1:] if e.args else tuple())) raise e reloaded_string = str(reloaded) try: assert string == reloaded_string except AssertionError as e: m = '\n%s\n!=\n%s' % (string, reloaded_string) if e.args: e.args = tuple(chain((e.args[0] + '\n' + m, ), e.args[1:])) else: e.args = (m, ) raise e reloaded_json = json.loads(string, object_hook=collections.OrderedDict, object_pairs_hook=collections.OrderedDict) keys = set() for n, p in meta.writable(o).properties.items(): keys.add(p.name or n) json_object(getattr(o, n), raise_validation_errors=raise_validation_errors) for k in reloaded_json.keys(): if k not in keys: raise KeyError( '"%s" not found in serialized/re-deserialized data: %s' % (k, string)) elif ( # ``isinstance(o, collections.Iterable)`` produces a recursion error in Python 2x, so we test for the # existence of an '__iter__' method directly hasattr(o, '__iter__') and not isinstance(o, (str, native_str, bytes))): if isinstance(o, (dict, collections.OrderedDict, model.Dictionary)): for k, v in o.items(): json_object(v, raise_validation_errors=raise_validation_errors) else: for oo in o: json_object(oo, raise_validation_errors=raise_validation_errors)
def __init__( self, property, # type: Property items=( None ) # type: Optional[Union[Sequence[Union[type, Property], Types], type, Property] ): # (...) -> None if not isinstance(property, Property): raise TypeError( 'The parameter `property` must be a `type`, or an instance of `%s`.' % qualified_name(Property) ) self.property = property if isinstance(items, (type, Property)): items = (items,) if items is None: super().__init__() else: super().__init__(items)
def version(data, specification, version_number): # type: (Any, str, Union[str, int, typing.Sequence[int]]) -> Any """ Recursively alters instances of ``serial.model.Object`` according to version_number metadata associated with that object's serial.properties. Arguments: - data - specification (str): The specification to which the ``version_number`` argument applies. - version_number (str|int|[int]): A version number represented as text (in the form of integers separated by periods), an integer, or a sequence of integers. """ def version_match(p): if p.versions is not None: vm = False sm = False for v in p.versions: if v.specification == specification: sm = True if v == version_number: vm = True break if sm and (not vm): return False return True def version_properties(ps): # type: (typing.Sequence[serial.properties.Property]) -> Optional[typing.Sequence[serial.meta.Meta]] changed = False nps = [] for p in ps: if isinstance(p, serial.properties.Property): if version_match(p): np = version_property(p) if np is not p: changed = True nps.append(np) else: changed = True else: nps.append(p) if changed: return tuple(nps) else: return None def version_property(p): # type: (serial.properties.Property) -> serial.meta.Meta changed = False if isinstance(p, serial.properties.Array) and (p.item_types is not None): item_types = version_properties(p.item_types) if item_types is not None: if not changed: p = deepcopy(p) p.item_types = item_types changed = True elif isinstance(p, serial.properties.Dictionary) and (p.value_types is not None): value_types = version_properties(p.value_types) if value_types is not None: if not changed: p = deepcopy(p) p.value_types = value_types changed = True if p.types is not None: types = version_properties(p.types) if types is not None: if not changed: p = deepcopy(p) p.types = types return p if isinstance(data, Model): im = serial.meta.read(data) cm = serial.meta.read(type(data)) if isinstance(data, Object): for n in tuple(im.properties.keys()): p = im.properties[n] if version_match(p): np = version_property(p) if np is not p: if im is cm: im = serial.meta.writable(data) im.properties[n] = np else: if im is cm: im = serial.meta.writable(data) del im.properties[n] v = getattr(data, n) if v is not None: raise serial.errors.VersionError( '%s - the property `%s` is not applicable in %s version %s:\n%s' % (qualified_name(type(data)), n, specification, version_number, str(data))) version(getattr(data, n), specification, version_number) elif isinstance(data, Dictionary): if im.value_types: new_value_types = version_properties(im.value_types) if new_value_types: if im is cm: im = serial.meta.writable(data) im.value_types = new_value_types for v in data.values(): version(v, specification, version_number) elif isinstance(data, Array): if im.item_types: new_item_types = version_properties(im.item_types) if new_item_types: if im is cm: im = serial.meta.writable(data) im.item_types = new_item_types for v in data: version(v, specification, version_number) elif isinstance( data, (collections.Set, collections.Sequence)) and not isinstance(data, (str, bytes)): # for d in data: # version(d, specification, version_number) raise ValueError() elif isinstance(data, (dict, OrderedDict)): # for k, v in data.items(): # version(v, specification, version_number) raise ValueError()
def version(data, specification, version_number): # type: (serial.abc.model.Model, str, Union[str, int, Sequence[int]]) -> None """ Recursively alters model class or instance metadata based on version number metadata associated with an object's properties. This allows one data model to represent multiple versions of a specification and dynamically change based on the version of a specification represented. Arguments: - data (serial.abc.model.Model) - specification (str): The specification to which the `version_number` argument applies. - version_number (str|int|[int]): A version number represented as text (in the form of integers separated by periods), an integer, or a sequence of integers. """ if not isinstance(data, serial.abc.model.Model): raise TypeError( 'The data provided is not an instance of serial.abc.model.Model: ' + repr(data) ) def version_match(property_): # type: (serial.properties.Property) -> bool if property_.versions is not None: version_matched = False specification_matched = False for applicable_version in property_.versions: if applicable_version.specification == specification: specification_matched = True if applicable_version == version_number: version_matched = True break if specification_matched and (not version_matched): return False return True def version_properties(properties_): # type: (Sequence[serial.properties.Property]) -> Optional[Sequence[Meta]] changed = False nps = [] for property in properties_: if isinstance(property, serial.properties.Property): if version_match(property): np = version_property(property) if np is not property: changed = True nps.append(np) else: changed = True else: nps.append(property) if changed: return tuple(nps) else: return None def version_property(property): # type: (serial.properties.Property) -> Meta changed = False if isinstance(property, serial.properties.Array) and (property.item_types is not None): item_types = version_properties(property.item_types) if item_types is not None: if not changed: property = deepcopy(property) property.item_types = item_types changed = True elif isinstance(property, serial.properties.Dictionary) and (property.value_types is not None): value_types = version_properties(property.value_types) if value_types is not None: if not changed: property = deepcopy(property) property.value_types = value_types changed = True if property.types is not None: types = version_properties(property.types) if types is not None: if not changed: property = deepcopy(property) property.types = types return property instance_meta = read(data) class_meta = read(type(data)) if isinstance(data, serial.abc.model.Object): for property_name in tuple(instance_meta.properties.keys()): property = instance_meta.properties[property_name] if version_match(property): np = version_property(property) if np is not property: if instance_meta is class_meta: instance_meta = writable(data) instance_meta.properties[property_name] = np else: if instance_meta is class_meta: instance_meta = writable(data) del instance_meta.properties[property_name] version_ = getattr(data, property_name) if version_ is not None: raise serial.errors.VersionError( '%s - the property `%s` is not applicable in %s version %s:\n%s' % ( qualified_name(type(data)), property_name, specification, version_number, str(data) ) ) value = getattr(data, property_name) if isinstance(value, serial.abc.model.Model): version(value, specification, version_number) elif isinstance(data, serial.abc.model.Dictionary): if instance_meta and instance_meta.value_types: new_value_types = version_properties(instance_meta.value_types) if new_value_types: if instance_meta is class_meta: instance_meta = writable(data) instance_meta.value_types = new_value_types for value in data.values(): if isinstance(value, serial.abc.model.Model): version(value, specification, version_number) elif isinstance(data, serial.abc.model.Array): if instance_meta and instance_meta.item_types: new_item_types = version_properties(instance_meta.item_types) if new_item_types: if instance_meta is class_meta: instance_meta = writable(data) instance_meta.item_types = new_item_types for item in data: if isinstance(item, serial.abc.model.Model): version(item, specification, version_number)
def __repr__(self): return ('\n'.join( ['%s(' % qualified_name(type(self))] + [' %s=%s,' % (p, repr(v)) for p, v in properties_values(self)] + [')']))
def model( model_instance, # type: serial.model.Model format_, # type: str raise_validation_errors=True, # type: bool ): # type: (...) -> None """ Tests an instance of a `serial.model.Model` sub-class. Parameters: - model_instance (serial.model.Model): An instance of a `serial.model.Model` sub-class. - format_ (str): The serialization format being tested: 'json', 'yaml' or 'xml'. - raise_validation_errors (bool): The function `serial.model.validate` verifies that all required attributes are present, as well as any additional validations implemented using the model's validation hooks `after_validate` and/or `before_validate`. - If `True`, errors resulting from `serial.model.validate` are raised. - If `False`, errors resulting from `serial.model.validate` are expressed only as warnings. """ if not isinstance(model_instance, Model): value_representation = repr(model_instance) raise TypeError( '`%s` requires an instance of `%s` for the parameter `model_instance`, not%s' % (calling_function_qualified_name(), qualified_name(Model), ((':\n%s' if '\n' in value_representation else ' `%s`') % value_representation))) serial.meta.format_(model_instance, format_) if isinstance(model_instance, serial.model.Object): errors = serial.marshal.validate(model_instance, raise_errors=raise_validation_errors) if errors: warn('\n' + '\n'.join(errors)) model_type = type(model_instance) string = str(model_instance) assert string != '' reloaded_model_instance = model_type(string) qualified_model_name = qualified_name(type(model_instance)) try: assert model_instance == reloaded_model_instance except AssertionError as e: sa = serial.marshal.serialize(model_instance) sb = serial.marshal.serialize(reloaded_model_instance) ra = ''.join(l.strip() for l in repr(model_instance).split('\n')) rb = ''.join(l.strip() for l in repr(reloaded_model_instance).split('\n')) message = [ 'Discrepancies were found between the instance of `%s` provided and ' % qualified_model_name + 'a serialized/deserialized clone:' ] if sa != sb: message.append( '\n :\n\n %s\n !=\n %s' % (sa, sb)) if ra != rb: message.append('\n %s\n !=\n %s\n' % (ra, rb)) for k, a_b in _object_discrepancies( model_instance, reloaded_model_instance).items(): a, b = a_b assert a != b sa = serial.marshal.serialize(a) sb = serial.marshal.serialize(b) if sa != sb: message.append( '\n %s().%s:\n\n %s\n %s\n %s' % (qualified_model_name, k, sa, '==' if sa == sb else '!=', sb)) ra = ''.join(l.strip() for l in repr(a).split('\n')) rb = ''.join(l.strip() for l in repr(b).split('\n')) if ra != rb: message.append('\n %s\n %s\n %s' % (ra, '==' if ra == rb else '!=', rb)) e.args = tuple( chain((e.args[0] + '\n' + '\n'.join(message) if e.args else '\n'.join(message), ), e.args[1:] if e.args else tuple())) raise e reloaded_string = str(reloaded_model_instance) try: assert string == reloaded_string except AssertionError as e: m = '\n%s\n!=\n%s' % (string, reloaded_string) if e.args: e.args = tuple(chain((e.args[0] + '\n' + m, ), e.args[1:])) else: e.args = (m, ) raise e if format_ == 'json': reloaded_marshalled_data = _json.loads( string, object_hook=collections.OrderedDict, object_pairs_hook=collections.OrderedDict) elif format_ == 'yaml': reloaded_marshalled_data = _yaml.load(string) elif format_ == 'xml': raise NotImplementedError() else: format_representation = repr(format_) raise ValueError( 'Valid serialization types for parameter `format_` are "json", "yaml", or "xml' ", not" + ((':\n%s' if '\n' in format_representation else ' %s.') % format_representation)) keys = set() for property_name, property in serial.meta.read( model_instance).properties.items(): keys.add(property.name or property_name) property_value = getattr(model_instance, property_name) if isinstance( property_value, Model) or (hasattr(property_value, '__iter__') and (not isinstance(property_value, (str, native_str, bytes)))): model(property_value, format_=format_, raise_validation_errors=raise_validation_errors) for k in reloaded_marshalled_data.keys(): if k not in keys: raise KeyError( '"%s" not found in serialized/re-deserialized data: %s' % (k, string)) elif isinstance(model_instance, serial.model.Array): serial.marshal.validate(model_instance) for item in model_instance: if isinstance( item, Model) or (hasattr(item, '__iter__') and (not isinstance(item, (str, native_str, bytes)))): model(item, format_=format_, raise_validation_errors=raise_validation_errors) elif isinstance(model_instance, serial.model.Dictionary): serial.marshal.validate(model_instance) for key, value in model_instance.items(): if isinstance( value, Model) or (hasattr(value, '__iter__') and (not isinstance(value, (str, native_str, bytes)))): model(value, format_=format_, raise_validation_errors=raise_validation_errors)
def __repr__(self): representation = [ qualified_name(type(self)) + '(' ] if len(self) > 0: representation.append(' [') for i in self: ri = ( qualified_name(i) if isinstance(i, type) else repr(i) ) rils = ri.split('\n') if len(rils) > 1: ris = [rils[0]] ris += [ ' ' + rvl for rvl in rils[1:] ] ri = '\n'.join(ris) representation.append( ' %s,' % ri ) im = serial.meta.read(self) cm = serial.meta.read(type(self)) m = None if (im is cm) else im representation.append( ' ]' + ('' if m is None or m.item_types is None else ',') ) im = serial.meta.read(self) cm = serial.meta.read(type(self)) if im is not cm: if im.item_types: representation.append( ' item_types=(', ) for it in im.item_types: ri = ( qualified_name(it) if isinstance(it, type) else repr(it) ) rils = ri.split('\n') if len(rils) > 2: ris = [rils[0]] ris += [ ' ' + rvl for rvl in rils[1:-1] ] ris.append(' ' + rils[-1]) ri = '\n'.join(ris) representation.append(' %s,' % ri) m = serial.meta.read(self) if len(m.item_types) > 1: representation[-1] = representation[-1][:-1] representation.append(' )') representation.append(')') if len(representation) > 2: return '\n'.join(representation) else: return ''.join(representation)
def __repr__(self): representation = [ qualified_name(type(self)) + '(' ] items = tuple(self.items()) if len(items) > 0: representation.append(' [') for k, v in items: rv = ( qualified_name(v) if isinstance(v, type) else repr(v) ) rvls = rv.split('\n') if len(rvls) > 1: rvs = [rvls[0]] for rvl in rvls[1:]: rvs.append(' ' + rvl) # rvs.append(' ' + rvs[-1]) rv = '\n'.join(rvs) representation += [ ' (', ' %s,' % repr(k), ' %s' % rv, ' ),' ] else: representation.append( ' (%s, %s),' % (repr(k), rv) ) representation[-1] = representation[-1][:-1] representation.append( ' ]' if self._meta is None or self._meta.value_types is None else ' ],' ) cm = serial.meta.read(type(self)) im = serial.meta.read(self) if cm is not im: if self._meta.value_types: representation.append( ' value_types=(', ) for vt in im.value_types: rv = ( qualified_name(vt) if isinstance(vt, type) else repr(vt) ) rvls = rv.split('\n') if len(rvls) > 1: rvs = [rvls[0]] rvs += [ ' ' + rvl for rvl in rvls[1:] ] rv = '\n'.join(rvs) representation.append(' %s,' % rv) if len(self._meta.value_types) > 1: representation[-1] = representation[-1][:-1] representation.append(' )') representation.append(')') if len(representation) > 2: return '\n'.join(representation) else: return ''.join(representation)