1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2024-10-06 10:51:13 +01:00
workload-automation/wa/utils/serializer.py

288 lines
8.3 KiB
Python
Raw Normal View History

2017-02-21 13:37:11 +00:00
"""
This module contains wrappers for Python serialization modules for
common formats that make it easier to serialize/deserialize WA
Plain Old Data structures (serilizable WA classes implement
``to_pod()``/``from_pod()`` methods for converting between POD
2017-02-21 13:37:11 +00:00
structures and Python class instances).
The modifications to standard serilization procedures are:
- mappings are deserialized as ``OrderedDict``\ 's rather than standard
2017-02-21 13:37:11 +00:00
Python ``dict``\ 's. This allows for cleaner syntax in certain parts
of WA configuration (e.g. values to be written to files can be specified
as a dict, and they will be written in the order specified in the config).
- regular expressions are automatically encoded/decoded. This allows for
configuration values to be transparently specified as strings or regexes
in the POD config.
This module exports the "wrapped" versions of serialization libraries,
and this should be imported and used instead of importing the libraries
2017-02-21 13:37:11 +00:00
directly. i.e. ::
from wa.utils.serializer import yaml
pod = yaml.load(fh)
instead of ::
import yaml
pod = yaml.load(fh)
It's also possible to use the serializer directly::
2017-02-21 13:37:11 +00:00
from wa.utils import serializer
pod = serializer.load(fh)
This can also be used to ``dump()`` POD structures. By default,
``dump()`` will produce JSON, but ``fmt`` parameter may be used to
specify an alternative format (``yaml`` or ``python``). ``load()`` will
use the file plugin to guess the format, but ``fmt`` may also be used
2017-02-21 13:37:11 +00:00
to specify it explicitly.
"""
# pylint: disable=unused-argument
2017-02-21 13:37:11 +00:00
import os
import re
import json as _json
from collections import OrderedDict
from datetime import datetime
import yaml as _yaml
import dateutil.parser
from wa.framework.exception import SerializerSyntaxError
from wa.utils.misc import isiterable
from wa.utils.types import regex_type, none_type
2017-02-21 13:37:11 +00:00
__all__ = [
'json',
'yaml',
'read_pod',
'dump',
'load',
'is_pod',
'POD_TYPES',
2017-02-21 13:37:11 +00:00
]
POD_TYPES = [
list,
tuple,
dict,
set,
str,
unicode,
int,
float,
bool,
datetime,
regex_type,
none_type,
]
2017-02-21 13:37:11 +00:00
class WAJSONEncoder(_json.JSONEncoder):
def default(self, obj): # pylint: disable=method-hidden
if isinstance(obj, regex_type):
2017-02-21 13:37:11 +00:00
return 'REGEX:{}:{}'.format(obj.flags, obj.pattern)
elif isinstance(obj, datetime):
return 'DATET:{}'.format(obj.isoformat())
else:
return _json.JSONEncoder.default(self, obj)
class WAJSONDecoder(_json.JSONDecoder):
def decode(self, s, **kwargs):
d = _json.JSONDecoder.decode(self, s, **kwargs)
2017-02-21 13:37:11 +00:00
def try_parse_object(v):
if isinstance(v, basestring) and v.startswith('REGEX:'):
_, flags, pattern = v.split(':', 2)
return re.compile(pattern, int(flags or 0))
elif isinstance(v, basestring) and v.startswith('DATET:'):
_, pattern = v.split(':', 1)
return dateutil.parser.parse(pattern)
else:
return v
def load_objects(d):
pairs = []
for k, v in d.iteritems():
if hasattr(v, 'iteritems'):
pairs.append((k, load_objects(v)))
elif isiterable(v):
pairs.append((k, [try_parse_object(i) for i in v]))
else:
pairs.append((k, try_parse_object(v)))
return OrderedDict(pairs)
return load_objects(d)
class json(object):
@staticmethod
def dump(o, wfh, indent=4, *args, **kwargs):
return _json.dump(o, wfh, cls=WAJSONEncoder, indent=indent, *args, **kwargs)
@staticmethod
def dumps(o, indent=4, *args, **kwargs):
return _json.dumps(o, cls=WAJSONEncoder, indent=indent, *args, **kwargs)
2017-02-21 13:37:11 +00:00
@staticmethod
def load(fh, *args, **kwargs):
try:
return _json.load(fh, cls=WAJSONDecoder, object_pairs_hook=OrderedDict, *args, **kwargs)
except ValueError as e:
raise SerializerSyntaxError(e.message)
@staticmethod
def loads(s, *args, **kwargs):
try:
return _json.loads(s, cls=WAJSONDecoder, object_pairs_hook=OrderedDict, *args, **kwargs)
except ValueError as e:
raise SerializerSyntaxError(e.message)
_mapping_tag = _yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
_regex_tag = u'tag:wa:regex'
def _wa_dict_representer(dumper, data):
return dumper.represent_mapping(_mapping_tag, data.iteritems())
def _wa_regex_representer(dumper, data):
text = '{}:{}'.format(data.flags, data.pattern)
return dumper.represent_scalar(_regex_tag, text)
def _wa_dict_constructor(loader, node):
pairs = loader.construct_pairs(node)
seen_keys = set()
for k, _ in pairs:
if k in seen_keys:
raise ValueError('Duplicate entry: {}'.format(k))
seen_keys.add(k)
return OrderedDict(pairs)
def _wa_regex_constructor(loader, node):
value = loader.construct_scalar(node)
flags, pattern = value.split(':', 1)
return re.compile(pattern, int(flags or 0))
_yaml.add_representer(OrderedDict, _wa_dict_representer)
_yaml.add_representer(regex_type, _wa_regex_representer)
_yaml.add_constructor(_mapping_tag, _wa_dict_constructor)
_yaml.add_constructor(_regex_tag, _wa_regex_constructor)
class yaml(object):
@staticmethod
def dump(o, wfh, *args, **kwargs):
return _yaml.dump(o, wfh, *args, **kwargs)
@staticmethod
def load(fh, *args, **kwargs):
try:
return _yaml.load(fh, *args, **kwargs)
except _yaml.YAMLError as e:
lineno = None
if hasattr(e, 'problem_mark'):
lineno = e.problem_mark.line # pylint: disable=no-member
2017-02-21 13:37:11 +00:00
raise SerializerSyntaxError(e.message, lineno)
loads = load
class python(object):
@staticmethod
def dump(o, wfh, *args, **kwargs):
raise NotImplementedError()
@classmethod
def load(cls, fh, *args, **kwargs):
return cls.loads(fh.read())
@staticmethod
def loads(s, *args, **kwargs):
pod = {}
try:
exec s in pod # pylint: disable=exec-used
2017-02-21 13:37:11 +00:00
except SyntaxError as e:
raise SerializerSyntaxError(e.message, e.lineno)
for k in pod.keys():
if k.startswith('__'):
del pod[k]
return pod
def read_pod(source, fmt=None):
if isinstance(source, basestring):
with open(source) as fh:
return _read_pod(fh, fmt)
elif hasattr(source, 'read') and (hasattr(source, 'name') or fmt):
2017-02-21 13:37:11 +00:00
return _read_pod(source, fmt)
else:
message = 'source must be a path or an open file handle; got {}'
raise ValueError(message.format(type(source)))
def write_pod(pod, dest, fmt=None):
if isinstance(dest, basestring):
with open(dest, 'w') as wfh:
return _write_pod(pod, wfh, fmt)
elif hasattr(dest, 'write') and (hasattr(dest, 'name') or fmt):
return _write_pod(pod, dest, fmt)
else:
message = 'dest must be a path or an open file handle; got {}'
raise ValueError(message.format(type(dest)))
2017-02-21 13:37:11 +00:00
def dump(o, wfh, fmt='json', *args, **kwargs):
serializer = {'yaml': yaml,
'json': json,
'python': python,
'py': python,
}.get(fmt)
2017-02-21 13:37:11 +00:00
if serializer is None:
raise ValueError('Unknown serialization format: "{}"'.format(fmt))
serializer.dump(o, wfh, *args, **kwargs)
def load(s, fmt='json', *args, **kwargs):
return read_pod(s, fmt=fmt)
def _read_pod(fh, fmt=None):
if fmt is None:
fmt = os.path.splitext(fh.name)[1].lower().strip('.')
if fmt == 'yaml':
return yaml.load(fh)
elif fmt == 'json':
return json.load(fh)
elif fmt == 'py':
return python.load(fh)
else:
raise ValueError('Unknown format "{}": {}'.format(fmt, getattr(fh, 'name', '<none>')))
def _write_pod(pod, wfh, fmt=None):
if fmt is None:
fmt = os.path.splitext(wfh.name)[1].lower().strip('.')
if fmt == 'yaml':
return yaml.dump(pod, wfh)
elif fmt == 'json':
return json.dump(pod, wfh)
elif fmt == 'py':
raise ValueError('Serializing to Python is not supported')
else:
raise ValueError('Unknown format "{}": {}'.format(fmt, getattr(wfh, 'name', '<none>')))
def is_pod(obj):
return type(obj) in POD_TYPES