305 lines
9.1 KiB
Python
305 lines
9.1 KiB
Python
from __future__ import nested_scopes, generators, division, absolute_import, with_statement, \
|
|
print_function, unicode_literals
|
|
from .utilities.compatibility import backport
|
|
|
|
backport() # noqa
|
|
|
|
from future.utils import native_str
|
|
|
|
from serial.utilities import qualified_name, calling_function_qualified_name
|
|
from serial.abc.model import Model
|
|
|
|
from warnings import warn
|
|
|
|
import collections
|
|
import json as _json
|
|
from itertools import chain
|
|
|
|
import yaml as _yaml
|
|
|
|
import serial
|
|
|
|
try:
|
|
import typing
|
|
except ImportError as e:
|
|
typing = None
|
|
|
|
|
|
def _object_discrepancies(a, b):
|
|
# type: (serial.model.Object, serial.model.Object) -> dict
|
|
discrepancies = {}
|
|
a_properties = set(serial.meta.read(a).properties.keys())
|
|
b_properties = set(serial.meta.read(b).properties.keys())
|
|
for property_ in a_properties | b_properties:
|
|
try:
|
|
a_value = getattr(a, property_)
|
|
except AttributeError:
|
|
a_value = None
|
|
try:
|
|
b_value = getattr(b, property_)
|
|
except AttributeError:
|
|
b_value = None
|
|
if a_value != b_value:
|
|
discrepancies[property_] = (a_value, b_value)
|
|
return discrepancies
|
|
|
|
|
|
def json(
|
|
model_instance, # type: serial.model.Model
|
|
raise_validation_errors=True, # type: bool
|
|
):
|
|
# type: (...) -> None
|
|
model(
|
|
model_instance=model_instance,
|
|
format_='json',
|
|
raise_validation_errors=raise_validation_errors
|
|
)
|
|
|
|
|
|
def yaml(
|
|
model_instance, # type: serial.model.Model
|
|
raise_validation_errors=True, # type: bool
|
|
):
|
|
# type: (...) -> None
|
|
model(
|
|
model_instance=model_instance,
|
|
format_='yaml',
|
|
raise_validation_errors=raise_validation_errors
|
|
)
|
|
|
|
|
|
def xml(
|
|
model_instance, # type: serial.model.Model
|
|
raise_validation_errors=True, # type: bool
|
|
):
|
|
# type: (...) -> None
|
|
raise NotImplementedError()
|
|
# model(
|
|
# model_instance=model_instance,
|
|
# format_='xml',
|
|
# raise_validation_errors=raise_validation_errors
|
|
# )
|
|
|
|
|
|
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)
|