Skip to content
This repository has been archived by the owner on Apr 10, 2023. It is now read-only.

Commit

Permalink
Add base _Container field
Browse files Browse the repository at this point in the history
  • Loading branch information
rossmacarthur committed Dec 1, 2019
1 parent ad6d24b commit e418303
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 181 deletions.
264 changes: 85 additions & 179 deletions src/serde/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,80 +715,101 @@ def deserialize(self, d):
return self.type.from_dict(d)


class Dict(Instance):
class _Container(Instance):
"""
This field represents the built-in `dict` type.
Each key and value will be serialized, deserialized, normalized, and
validated with the specified key and value types. The key and value types
can be specified using `Field` classes, `Field` instances, `~serde.Model`
classes, or built-in types that have a corresponding field type in this
library.
Args:
key: the `Field` class or instance for keys in this `Dict`.
value: the `Field` class or instance for values in this `Dict`.
**kwargs: keyword arguments for the `Field` constructor.
A base class for `Dict`, `List`, `Tuple`, and other container fields.
"""

type = dict
def _iter(self, value):
"""
Iterate over the container.
"""
return value

def __init__(self, key=None, value=None, **kwargs):
def _apply(self, stage, element):
"""
Create a new `Dict`.
Apply a stage to a particular element in the container.
"""
super(Dict, self).__init__(self.__class__.type, **kwargs)
self.key = _resolve_to_field_instance(key)
self.value = _resolve_to_field_instance(value)
return getattr(self.element, stage)(element)

def serialize(self, value):
"""
Serialize the given dictionary.
Serialize the given container.
Each key and value in the dictionary will be serialized with the
specified key and value field instances.
Each element in the container will be serialized with the specified
field instances.
"""
return self.type(
(self.key._serialize(k), self.value._serialize(v))
for k, v in value.items()
)
value = self.type(self._apply('_serialize', element) for element in self._iter(value))
return super(_Container, self).serialize(value)

def deserialize(self, value):
"""
Deserialize the given dictionary.
Deserialize the given container.
Each key and value in the dictionary will be deserialized with the
specified key and value field instances.
Each element in the container will be deserialized with the specified
field instances.
"""
return self.type(
(self.key._deserialize(k), self.value._deserialize(v))
for k, v in value.items()
)
value = super(_Container, self).deserialize(value)
return self.type(self._apply('_deserialize', element) for element in self._iter(value))

def normalize(self, value):
"""
Normalize the given dictionary.
Deserialize the given container.
Each key and value in the dictionary will be normalized with the
specified key and value field instances.
Each element in the container will be normalized with the specified
field instances.
"""
return self.type(
(self.key._normalize(k), self.value._normalize(v))
for k, v in value.items()
)
value = super(_Container, self).normalize(value)
return self.type(self._apply('_normalize', element) for element in self._iter(value))

def validate(self, value):
"""
Validate the given dictionary.
Validate the given container.
Each element in the container will be validated with the specified field
instances.
"""
super(_Container, self).validate(value)
for element in self._iter(value):
self._apply('_validate', element)


class Dict(_Container):
"""
This field represents the built-in `dict` type.
Each key and value will be serialized, deserialized, normalized, and
validated with the specified key and value types. The key and value types
can be specified using `Field` classes, `Field` instances, `~serde.Model`
classes, or built-in types that have a corresponding field type in this
library.
Args:
key: the `Field` class or instance for keys in this `Dict`.
value: the `Field` class or instance for values in this `Dict`.
**kwargs: keyword arguments for the `Field` constructor.
"""

def __init__(self, key=None, value=None, **kwargs):
"""
Create a new `Dict`.
"""
super(Dict, self).__init__(dict, **kwargs)
self.key = _resolve_to_field_instance(key)
self.value = _resolve_to_field_instance(value)

Each key and value in the dictionary will be validated with the
specified key and value field instances.
def _iter(self, value):
"""
Iterate over the dictionary items.
"""
super(Dict, self).validate(value)
return value.items()

for k, v in value.items():
self.key._validate(k)
self.value._validate(v)
def _apply(self, stage, element):
"""
Apply the key stage to each key, and the value stage to each value.
"""
k, v = element
return (getattr(self.key, stage)(k), getattr(self.value, stage)(v))


class OrderedDict(Dict):
Expand All @@ -807,10 +828,16 @@ class OrderedDict(Dict):
**kwargs: keyword arguments for the `Field` constructor.
"""

type = collections.OrderedDict
def __init__(self, key=None, value=None, **kwargs):
"""
Create a new `OrderedDict`.
"""
super(Dict, self).__init__(collections.OrderedDict, **kwargs)
self.key = _resolve_to_field_instance(key)
self.value = _resolve_to_field_instance(value)


class List(Instance):
class List(_Container):
"""
This field represents the built-in `list` type.
Expand All @@ -831,50 +858,8 @@ def __init__(self, element=None, **kwargs):
super(List, self).__init__(list, **kwargs)
self.element = _resolve_to_field_instance(element)

def serialize(self, value):
"""
Serialize the given list.

Each element in the list will be serialized with the specified element
field instance.
"""
value = [self.element._serialize(v) for v in value]
return super(List, self).serialize(value)

def deserialize(self, value):
"""
Deserialize the given list.
Each element in the list will be deserialized with the specified element
field instance.
"""
value = super(List, self).deserialize(value)
return [self.element._deserialize(v) for v in value]

def normalize(self, value):
"""
Normalize the given list.
Each element in the list will be normalized with the specified element
field instance.
"""
value = super(List, self).normalize(value)
return [self.element._normalize(v) for v in value]

def validate(self, value):
"""
Validate the given list.
Each element in the list will be validated with the specified element
field instance.
"""
super(List, self).validate(value)

for v in value:
self.element._validate(v)


class Set(Instance):
class Set(_Container):
"""
This field represents the built-in `set` type.
Expand All @@ -895,50 +880,8 @@ def __init__(self, element=None, **kwargs):
super(Set, self).__init__(set, **kwargs)
self.element = _resolve_to_field_instance(element)

def serialize(self, value):
"""
Serialize the given set.
Each element in the list will be serialized with the specified
element field instance.
"""
value = {self.element._serialize(v) for v in value}
return super(Set, self).serialize(value)

def deserialize(self, value):
"""
Deserialize the given set.

Each element in the set will be deserialized with the specified
element field instance.
"""
value = super(Set, self).deserialize(value)
return {self.element._deserialize(v) for v in value}

def normalize(self, value):
"""
Normalize the given set.
Each element in the set will be normalized with the specified
element field instance.
"""
value = super(Set, self).normalize(value)
return {self.element._normalize(v) for v in value}

def validate(self, value):
"""
Validate the given set.
Each element in the set will be validated with the specified
element field instance.
"""
super(Set, self).validate(value)

for v in value:
self.element._validate(v)


class Tuple(Instance):
class Tuple(_Container):
"""
This field represents the built-in `tuple` type.
Expand All @@ -962,55 +905,18 @@ def __init__(self, *elements, **kwargs):
for e in elements
)

def serialize(self, value):
"""
Serialize the given tuple.
Each element in the tuple will be serialized with the specified element
Field instance.
"""
return tuple(
e._serialize(v)
for e, v in zip_equal(self.elements, value)
)

def deserialize(self, value):
"""
Deserialize the given tuple.
Each element in the tuple will be deserialized with the specified
element field instance.
"""
value = super(Tuple, self).deserialize(value)
return tuple(
e._deserialize(v)
for e, v in zip_equal(self.elements, value)
)

def normalize(self, value):
def _iter(self, value):
"""
Normalize the given tuple.
Each element in the tuple will be normalized with the specified element
field instance.
Iterate over the fields and each element in the tuple.
"""
value = super(Tuple, self).normalize(value)
return tuple(
e._normalize(v)
for e, v in zip_equal(self.elements, value)
)
return zip_equal(self.elements, value)

def validate(self, value):
def _apply(self, stage, element):
"""
Validate the given tuple.
Each element in the tuple will be validated with the specified element
field instance.
Apply the element field stage to the corresponding element value.
"""
super(Tuple, self).validate(value)

for e, v in zip_equal(self.elements, value):
e._validate(v)
f, v = element
return getattr(f, stage)(v)


def create_primitive(name, type):
Expand Down
23 changes: 21 additions & 2 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import collections
import datetime
import re
import uuid
from collections import OrderedDict

from pytest import raises

Expand Down Expand Up @@ -32,6 +32,7 @@
Literal,
Nested,
Optional,
OrderedDict,
Regex,
Set,
Str,
Expand Down Expand Up @@ -669,7 +670,7 @@ class Example(Model):
a = Field()

field = Nested(Example)
assert field.serialize(Example(a=0)) == OrderedDict([('a', 0)])
assert field.serialize(Example(a=0)) == collections.OrderedDict([('a', 0)])

def test_deserialize(self):
# A Nested should deserialize as a dictionary representation of the
Expand Down Expand Up @@ -781,6 +782,24 @@ def test_validate_extra(self):
field.validate({'test': 11})


class TestOrderedDict:

def test___init___basic(self):
# Construct a basic OrderedDict and check values are set correctly.
field = OrderedDict()
assert field.key == Field()
assert field.value == Field()
assert field.validators == []

def test___init___options(self):
# Construct a OrderedDict with extra options and make sure values are
# passed to Field.
field = OrderedDict(key=Str, value=Int, validators=[None])
assert field.key == Str()
assert field.value == Int()
assert field.validators == [None]


class TestList:

def test___init___basic(self):
Expand Down

0 comments on commit e418303

Please sign in to comment.