Skip to content

Commit

Permalink
fix: support colander 2
Browse files Browse the repository at this point in the history
Colander 2 introduced a extra argument to `colander.Mapping._impl`
function (from Pylons/colander#264), which break the `OpenMapping` and `SortedOpenMapping`
subclasses from colander-tools.

This add the missing argument, as well
as a couple of tests to know about it if it ever happen again.
  • Loading branch information
Flavien Binet committed Aug 4, 2023
1 parent 58e44f1 commit 3dd1762
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 4 deletions.
18 changes: 15 additions & 3 deletions colander_tools/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,22 @@ def callback(subnode, subcstruct):
return self.__impl(node, cstruct, callback, serializing=False, unknown=self.unknown)


unset = object()


class OpenMapping(colander.Mapping):
"""
A mapping where the keys are free-form and the values a specific type.
"""

def _impl(self, node, value, callback):
def _impl(self, node, value, callback, default_or_missing=unset):
# default_or_missing is used to specify whether this call is happening
# during serialization or deserialization, so that the validation can
# correctly choose between the default or missing attributes. Before
# colander 2.0, the default attribute was incorrectly used for both
# directions. Because OpenMapping doesn't validate the presence of
# specific keys, default and missing are both ignored, and the
# default_or_missing argument doesn't need to be used.
value = self._validate(node, value)

error = None
Expand All @@ -109,6 +119,8 @@ def _impl(self, node, value, callback):


class SortedOpenMapping(OpenMapping):
def _impl(self, node, value, callback):
result = OpenMapping._impl(self, node, value, callback)
def _impl(self, node, value, callback, default_or_missing=unset):
result = OpenMapping._impl(
self, node, value, callback, default_or_missing
)
return collections.OrderedDict(sorted(result.items()))
43 changes: 43 additions & 0 deletions colander_tools/test_mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import colander
from colander_tools import mapping, serializable, strict
from collections import OrderedDict

def test_open_mapping():
@serializable.serializable
class Definition(object):
class Schema(colander.MappingSchema):
schema_type = strict.Mapping

foo = colander.SchemaNode(
mapping.OpenMapping(),
colander.SchemaNode(colander.String(), name="key"),
colander.SchemaNode(strict.Integer(), name="value"),
)

def __init__(self, foo):
self.foo = foo

assert Definition.Schema().deserialize({"foo": {"a": 42}}).foo == {"a": 42}
assert Definition.Schema().serialize(Definition(foo={"mykey": 1234})) == {"foo": {"mykey": 1234}}



def test_sorted_open_mapping():
@serializable.serializable
class Definition(object):
class Schema(colander.MappingSchema):
schema_type = strict.Mapping

foo = colander.SchemaNode(
mapping.SortedOpenMapping(),
colander.SchemaNode(colander.String(), name="key"),
colander.SchemaNode(strict.Integer(), name="value"),
)

def __init__(self, foo):
self.foo = foo

deserialized = Definition.Schema().deserialize({"foo": {"a": 42}}).foo
assert deserialized == {"a": 42}
assert isinstance(deserialized, OrderedDict)
assert Definition.Schema().serialize(Definition(foo={"mykey": 1234})) == {"foo": {"mykey": 1234}}
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
url="https://github.com/platformsh/colander-tools",
packages=find_packages(),
install_requires=[
"colander < 2",
"colander >= 2, < 3",
"pytz",
"netaddr",
"six",
Expand Down

0 comments on commit 3dd1762

Please sign in to comment.