Source code for yggdrasil.metaschema.datatypes.JSONArrayMetaschemaType
import numpy as np
import pandas as pd
from yggdrasil.metaschema.datatypes import generate_data
from yggdrasil.metaschema.datatypes.ContainerMetaschemaType import (
ContainerMetaschemaType)
[docs]class JSONArrayMetaschemaType(ContainerMetaschemaType):
r"""Type associated with a set of subtypes.
Developer Notes:
Support for dynamic arrays in C/C++ is still under development.
"""
name = 'array'
description = 'A container of ordered values.'
properties = ['items']
metadata_properties = ['items']
extract_properties = ['items']
python_types = (list, tuple, np.ndarray, pd.DataFrame)
_replaces_existing = True
_container_type = list
_json_type = 'array'
_json_property = 'items'
_empty_msg = []
[docs] @classmethod
def validate(cls, obj, raise_errors=False):
r"""Validate an object to check if it could be of this type.
Args:
obj (object): Object to validate.
raise_errors (bool, optional): If True, errors will be raised when
the object fails to be validated. Defaults to False.
Returns:
bool: True if the object could be of this type, False otherwise.
"""
out = super(JSONArrayMetaschemaType, cls).validate(
obj, raise_errors=raise_errors)
if out and isinstance(obj, np.ndarray):
out = (len(obj.dtype) > 0)
if (not out) and raise_errors:
raise ValueError("Array dosn't have a structured data type.")
return out
[docs] @classmethod
def normalize(cls, obj):
r"""Normalize an object, if possible, to conform to this type.
Args:
obj (object): Object to normalize.
Returns:
object: Normalized object.
"""
if isinstance(obj, str):
obj = [v.strip() for v in obj.split(',')]
elif isinstance(obj, tuple):
obj = list(obj)
return obj
[docs] @classmethod
def encode_type(cls, obj, **kwargs):
r"""Encode an object's type definition.
Args:
obj (object): Object to encode.
**kwargs: Additional keyword arguments are passed to the
parent class's method.
Returns:
dict: Encoded type definition.
"""
names = None
if isinstance(obj, pd.DataFrame):
names = obj.columns
if all([isinstance(n, int) for n in names]):
names = None
elif isinstance(obj, np.ndarray) and (len(obj.dtype) > 0):
names = obj.dtype.names
out = super(JSONArrayMetaschemaType, cls).encode_type(obj, **kwargs)
if names is not None:
assert 'items' in out
assert len(out['items']) == len(names)
for n, x in zip(names, out['items']):
x.setdefault('title', n)
return out
[docs] @classmethod
def coerce_type(cls, obj, typedef=None, key_order=None,
dont_wrap_single=False, **kwargs):
r"""Coerce objects of specific types to match the data type.
Args:
obj (object): Object to be coerced.
typedef (dict, optional): Type defintion that object should be
coerced to. Defaults to None.
key_order (list, optional): Order that keys from a dictionary should
be used to compose an array. Defaults to None.
dont_wrap_single (bool, optional): If True, single element
data types will not attempt to wrap input object in
additional list. Defaults to False.
**kwargs: Additional keyword arguments are metadata entries that may
aid in coercing the type.
Returns:
object: Coerced object.
Raises:
RuntimeError: If obj is a dictionary, but key_order is not provided.
"""
from yggdrasil.serialize import pandas2list, numpy2list, dict2list
if isinstance(obj, pd.DataFrame):
obj = pandas2list(obj)
elif isinstance(obj, np.ndarray) and (len(obj.dtype) == 0):
obj = [obj]
elif isinstance(obj, np.ndarray) and (len(obj.dtype) > 0):
obj = numpy2list(obj)
elif isinstance(obj, dict):
if (key_order is not None) or (len(obj) == 1):
obj = dict2list(obj, order=key_order)
elif (isinstance(typedef, dict)
and isinstance(typedef.get('items', None), list)
and all([('title' in x) for x in typedef['items']])):
key_order = [x['title'] for x in typedef['items']]
obj = dict2list(obj, order=key_order)
else:
obj = [obj]
elif ((isinstance(typedef, dict) and (not dont_wrap_single)
and (len(typedef.get('items', [])) == 1))):
typedef_validated = kwargs.get('typedef_validated', False)
try_obj = cls.coerce_type([obj], typedef=typedef,
key_order=key_order,
dont_wrap_single=True, **kwargs)
if cls.check_decoded(try_obj, typedef,
typedef_validated=typedef_validated):
obj = try_obj
return super(JSONArrayMetaschemaType, cls).coerce_type(
obj, typedef=typedef, **kwargs)
@classmethod
def _iterate(cls, container):
r"""Iterate over the contents of the container. Each element returned
should be a tuple including an index and a value.
Args:
container (obj): Object to be iterated over.
Returns:
iterator: Iterator over elements in the container.
"""
for k, v in enumerate(container):
yield (k, v)
@classmethod
def _assign(cls, container, index, value):
r"""Assign an element in the container to the specified value.
Args:
container (obj): Object that element will be assigned to.
index (obj): Index in the container object where element will be
assigned.
value (obj): Value that will be assigned to the element in the
container object.
"""
if len(container) > index:
container[index] = value
elif len(container) == index:
container.append(value)
else:
raise RuntimeError("The container has %s elements and the index is %s"
% (len(container), index))
@classmethod
def _has_element(cls, container, index):
r"""Check to see if an index is in the container.
Args:
container (obj): Object that should be checked for index.
index (obj): Index that should be checked for.
Returns:
bool: True if the index is in the container.
"""
return (len(container) > index)
@classmethod
def _get_element(cls, container, index, default):
r"""Get an element from the container if it exists, otherwise return
the default.
Args:
container (obj): Object that should be returned from.
index (obj): Index of element that should be returned.
default (obj): Default that should be returned if the index is not
in the container.
Returns:
object: Container contents at specified element.
"""
if isinstance(container, dict):
assert 'type' in container
return container
return super(JSONArrayMetaschemaType, cls)._get_element(
container, index, default)
@classmethod
def _generate_data(cls, typedef, **kwargs):
r"""Generate mock data for the specified type.
Args:
typedef (dict): Type definition.
Returns:
object: Python object of the specified type.
"""
if isinstance(typedef[cls._json_property], dict):
nitems = typedef.get('minItems', 1)
out = cls._container_type()
for i in range(nitems):
cls._assign(out, i, generate_data(typedef[cls._json_property],
**kwargs))
else:
out = super(JSONArrayMetaschemaType, cls)._generate_data(typedef,
**kwargs)
return out
[docs] @classmethod
def get_test_data(cls, typedef=None):
r"""object: Test data."""
if typedef is None:
typedef = {
'type': 'array',
'items': [
{'type': 'float',
'precision': 32,
'units': ''},
{'type': 'bytes',
'precision': 40,
'units': ''},
{'type': 'unicode',
'precision': 40,
'units': ''},
{'type': 'object',
'properties': {'nested': {'type': 'int',
'precision': 64,
'units': ''}}},
{'type': 'array',
'items': [{'type': 'complex',
'precision': 128,
'units': ''},
{'type': 'uint',
'precision': 8,
'units': ''}]}]}
return super(JSONArrayMetaschemaType, cls).get_test_data(typedef)