from typing import Callable
from typedpy.structures import (
Field,
Structure,
TypedField,
ImmutableField,
ClassReference,
)
from typedpy.commons import python_ver_atleast_39
from .collections_impl import (
_ListStruct,
SizedCollection,
ContainNestedFieldMixin,
_CollectionMeta,
)
from .fields import _map_to_field, verify_type_and_uniqueness
from .numbers import Number
from .strings import String
def extract_field_value(*, self, value, cls):
setattr(self.items, "_name", self._name)
res = cls()
temp_st = Structure()
for i, val in enumerate(value):
setattr(self.items, "_name", self._name + f"_{str(i)}")
self.items.__set__(temp_st, val) # pylint: disable=unnecessary-dunder-call
res.append(getattr(temp_st, getattr(self.items, "_name")))
return res
def init_array_like(
self, *args, items=None, uniqueItems=None, additionalItems=None, **kwargs
):
self.uniqueItems = uniqueItems
self.additionalItems = additionalItems
if isinstance(items, list):
self.items = []
for item in items:
self.items.append(_map_to_field(item))
else:
self.items = _map_to_field(items)
def _get_items(items):
if isinstance(items, list):
res = []
for item in items:
res.append(_map_to_field(item))
else:
res = _map_to_field(items)
return res
def has_multiple_items(items):
return not isinstance(items, (list, tuple)) and items and python_ver_atleast_39
[docs]class Array(
SizedCollection,
ContainNestedFieldMixin,
TypedField,
metaclass=_CollectionMeta,
):
"""
An Array field, similar to a list. Supports the properties in JSON schema draft 4.
Expected input is of type `list`.
Arguments:
minItems(int): optional
minimal size
maxItems(int): optional
maximal size
unqieItems(bool): optional
are elements required to be unique?
additionalItems(bool): optional
Relevant in case items parameter is a list of Fields. Is it allowed to have additional
elements beyond the ones defined in "items"?
items(a :class:`Field` or :class:`Structure`, or a list/tuple of :class:`Field` or :class:`Structure`): optional
Describes the fields of the elements.
If a items if a :class:`Field`, then it applies to all items.
If a items is a list, then every element in the content is expected to be
of the corresponding field in items.
Examples:
.. code-block:: python
names = Array[String]
names = Array[String(minLengh=3)]
names = Array(minItems=5, items=String)
my_record = Array(items=[String, Integer(minimum=5), String])
my_lists = Array[Array[Integer]]
my_structs = Array[StructureReference(a=Integer, b=Float)]
# Let's say we defined a Structure "Person"
people = Array[Person]
# Assume Foo is an arbitrary (non-Typedpy) class
foos = Array[Foo]
"""
_ty = list
# pylint: disable=duplicate-code
def __init__( # pylint: disable=duplicate-code
self, *args, items=None, uniqueItems=None, additionalItems=None, **kwargs
):
"""
Constructor
:param args: pass-through
:param items: either a single field, which will be enforced for all elements, or a list
of fields which enforce the elements with the correspondent index
:param uniqueItems: are elements required to be unique?
:param additionalItems: Relevant if "items" is a list. Is it allowed to have additional
elements beyond the ones defined in "items"?
:param kwargs: pass-through
"""
self.uniqueItems = uniqueItems
self.additionalItems = additionalItems
self.items = _get_items(items)
self._serialize = None
super().__init__(*args, **kwargs)
self._set_immutable(getattr(self, "_immutable", False))
@property
def get_type(self):
if has_multiple_items(self.items):
return list[self.items.get_type]
return list
def __set__(self, instance, value):
if getattr(instance, "_trust_supplied_values", False):
super().__set__(instance, value)
return
verify_type_and_uniqueness(list, value, self._name, self.uniqueItems)
self.validate_size(value, self._name)
if self.items is not None:
if isinstance(self.items, Field):
value = extract_field_value(self=self, value=value, cls=list)
elif isinstance(self.items, list):
additional_properties_forbidden = self.additionalItems is False
if not getattr(instance, "_skip_validation", False):
if len(self.items) > len(value) or (
additional_properties_forbidden and len(self.items) > len(value)
):
raise ValueError(
f"{self._name}: Got {value}; Expected an array of length {len(self.items)}"
)
temp_st = Structure()
temp_st._skip_validation = getattr(instance, "_skip_validation", False)
res = []
for ind, item in enumerate(self.items):
if ind >= len(value):
continue
setattr(item, "_name", self._name + f"_{str(ind)}")
item.__set__(temp_st, value[ind])
res.append(getattr(temp_st, getattr(item, "_name")))
res += value[len(self.items) :]
value = res
super().__set__(instance, _ListStruct(self, instance, value, self._name))
def _from_trusted_value(self, value, instance):
if isinstance(value, _ListStruct):
return _ListStruct(self, instance, value, self._name)
return value
def serialize(self, value):
cached: Callable = getattr(self, "_serialize", None)
if cached is not None:
return cached(value) # pylint: disable=E1102
items = self.items
if items is not None:
if isinstance(items, Field):
if isinstance(items, Number) or items.__class__ is String:
self._serialize = lambda value: value
return value
if isinstance(items, ClassReference):
serializer = items._ty.serialize
self._serialize = lambda value: [serializer(x) for x in value]
return self._serialize(value)
serialize = items.serialize
self._serialize = lambda value: [serialize(x) for x in value]
return self._serialize(value)
elif isinstance(items, list):
self._serialize = lambda value: [
items[i].serialize(x) for (i, x) in enumerate(value)
]
return self._serialize(value)
return value
[docs]class ImmutableArray(ImmutableField, Array):
"""
An immutable version of :class:`Array`
"""