1582 lines
50 KiB
Python
1582 lines
50 KiB
Python
from collections import OrderedDict
|
|
from datetime import date, datetime
|
|
from decimal import Decimal
|
|
from functools import partial
|
|
|
|
import pytz
|
|
import pytest
|
|
|
|
from flask import Blueprint
|
|
from flask_restx import fields, Api
|
|
|
|
|
|
class FieldTestCase(object):
|
|
field_class = None
|
|
|
|
@pytest.fixture
|
|
def api(self, app):
|
|
blueprint = Blueprint("api", __name__)
|
|
api = Api(blueprint)
|
|
app.register_blueprint(blueprint)
|
|
yield api
|
|
|
|
def assert_field(self, field, value, expected):
|
|
assert field.output("foo", {"foo": value}) == expected
|
|
|
|
def assert_field_raises(self, field, value):
|
|
with pytest.raises(fields.MarshallingError):
|
|
field.output("foo", {"foo": value})
|
|
|
|
|
|
class BaseFieldTestMixin(object):
|
|
def test_description(self):
|
|
field = self.field_class(description="A description")
|
|
assert "description" in field.__schema__
|
|
assert field.__schema__["description"] == "A description"
|
|
|
|
def test_title(self):
|
|
field = self.field_class(title="A title")
|
|
assert "title" in field.__schema__
|
|
assert field.__schema__["title"] == "A title"
|
|
|
|
def test_required(self):
|
|
field = self.field_class(required=True)
|
|
assert field.required
|
|
|
|
def test_readonly(self):
|
|
field = self.field_class(readonly=True)
|
|
assert "readOnly" in field.__schema__
|
|
assert field.__schema__["readOnly"]
|
|
|
|
|
|
class NumberTestMixin(object):
|
|
def test_min(self):
|
|
field = self.field_class(min=0)
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == 0
|
|
assert "exclusiveMinimum" not in field.__schema__
|
|
|
|
def test_min_as_callable(self):
|
|
field = self.field_class(min=lambda: 0)
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == 0
|
|
|
|
def test_min_exlusive(self):
|
|
field = self.field_class(min=0, exclusiveMin=True)
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == 0
|
|
assert "exclusiveMinimum" in field.__schema__
|
|
assert field.__schema__["exclusiveMinimum"] is True
|
|
|
|
def test_max(self):
|
|
field = self.field_class(max=42)
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == 42
|
|
assert "exclusiveMaximum" not in field.__schema__
|
|
|
|
def test_max_as_callable(self):
|
|
field = self.field_class(max=lambda: 42)
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == 42
|
|
|
|
def test_max_exclusive(self):
|
|
field = self.field_class(max=42, exclusiveMax=True)
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == 42
|
|
assert "exclusiveMaximum" in field.__schema__
|
|
assert field.__schema__["exclusiveMaximum"] is True
|
|
|
|
def test_mulitple_of(self):
|
|
field = self.field_class(multiple=5)
|
|
assert "multipleOf" in field.__schema__
|
|
assert field.__schema__["multipleOf"] == 5
|
|
|
|
|
|
class StringTestMixin(object):
|
|
def test_min_length(self):
|
|
field = self.field_class(min_length=1)
|
|
assert "minLength" in field.__schema__
|
|
assert field.__schema__["minLength"] == 1
|
|
|
|
def test_min_length_as_callable(self):
|
|
field = self.field_class(min_length=lambda: 1)
|
|
assert "minLength" in field.__schema__
|
|
assert field.__schema__["minLength"] == 1
|
|
|
|
def test_max_length(self):
|
|
field = self.field_class(max_length=42)
|
|
assert "maxLength" in field.__schema__
|
|
assert field.__schema__["maxLength"] == 42
|
|
|
|
def test_max_length_as_callable(self):
|
|
field = self.field_class(max_length=lambda: 42)
|
|
assert "maxLength" in field.__schema__
|
|
assert field.__schema__["maxLength"] == 42
|
|
|
|
def test_pattern(self):
|
|
field = self.field_class(pattern="[a-z]")
|
|
assert "pattern" in field.__schema__
|
|
assert field.__schema__["pattern"] == "[a-z]"
|
|
|
|
|
|
class RawFieldTest(BaseFieldTestMixin, FieldTestCase):
|
|
"""Test Raw field AND some common behaviors"""
|
|
|
|
field_class = fields.Raw
|
|
|
|
def test_type(self):
|
|
field = fields.Raw()
|
|
assert field.__schema__["type"] == "object"
|
|
|
|
def test_default(self):
|
|
field = fields.Raw(default="aaa")
|
|
assert field.__schema__["default"] == "aaa"
|
|
self.assert_field(field, None, "aaa")
|
|
|
|
def test_default_as_callable(self):
|
|
field = fields.Raw(default=lambda: "aaa")
|
|
assert field.__schema__["default"] == "aaa"
|
|
self.assert_field(field, None, "aaa")
|
|
|
|
def test_with_attribute(self):
|
|
field = fields.Raw(attribute="bar")
|
|
assert field.output("foo", {"bar": 42}) == 42
|
|
|
|
def test_with_lambda_attribute(self, mocker):
|
|
obj = mocker.Mock()
|
|
obj.value = 42
|
|
field = fields.Raw(attribute=lambda x: x.value)
|
|
assert field.output("foo", obj) == 42
|
|
|
|
def test_with_partial_attribute(self, mocker):
|
|
def f(x, suffix):
|
|
return "{0}-{1}".format(x.value, suffix)
|
|
|
|
obj = mocker.Mock()
|
|
obj.value = 42
|
|
|
|
p = partial(f, suffix="whatever")
|
|
field = fields.Raw(attribute=p)
|
|
assert field.output("foo", obj) == "42-whatever"
|
|
|
|
def test_attribute_not_found(self):
|
|
field = fields.Raw()
|
|
assert field.output("foo", {"bar": 42}) is None
|
|
|
|
def test_object(self, mocker):
|
|
obj = mocker.Mock()
|
|
obj.foo = 42
|
|
field = fields.Raw()
|
|
assert field.output("foo", obj) == 42
|
|
|
|
def test_nested_object(self, mocker):
|
|
foo = mocker.Mock()
|
|
bar = mocker.Mock()
|
|
bar.value = 42
|
|
foo.bar = bar
|
|
field = fields.Raw()
|
|
assert field.output("bar.value", foo) == 42
|
|
|
|
|
|
class StringFieldTest(StringTestMixin, BaseFieldTestMixin, FieldTestCase):
|
|
field_class = fields.String
|
|
|
|
def test_defaults(self):
|
|
field = fields.String()
|
|
assert not field.required
|
|
assert not field.discriminator
|
|
assert field.__schema__ == {"type": "string"}
|
|
|
|
def test_with_enum(self):
|
|
enum = ["A", "B", "C"]
|
|
field = fields.String(enum=enum)
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "string", "enum": enum, "example": enum[0]}
|
|
|
|
def test_with_empty_enum(self):
|
|
field = fields.String(enum=[])
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "string"}
|
|
|
|
def test_with_callable_enum(self):
|
|
enum = lambda: ["A", "B", "C"] # noqa
|
|
field = fields.String(enum=enum)
|
|
assert not field.required
|
|
assert field.__schema__ == {
|
|
"type": "string",
|
|
"enum": ["A", "B", "C"],
|
|
"example": "A",
|
|
}
|
|
|
|
def test_with_empty_callable_enum(self):
|
|
enum = lambda: [] # noqa
|
|
field = fields.String(enum=enum)
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "string"}
|
|
|
|
def test_with_default(self):
|
|
field = fields.String(default="aaa")
|
|
assert field.__schema__ == {"type": "string", "default": "aaa"}
|
|
|
|
def test_string_field_with_discriminator(self):
|
|
field = fields.String(discriminator=True)
|
|
assert field.discriminator
|
|
assert field.required
|
|
assert field.__schema__ == {"type": "string"}
|
|
|
|
def test_string_field_with_discriminator_override_require(self):
|
|
field = fields.String(discriminator=True, required=False)
|
|
assert field.discriminator
|
|
assert field.required
|
|
assert field.__schema__ == {"type": "string"}
|
|
|
|
def test_discriminator_output(self, api):
|
|
model = api.model(
|
|
"Test",
|
|
{
|
|
"name": fields.String(discriminator=True),
|
|
},
|
|
)
|
|
|
|
data = api.marshal({}, model)
|
|
assert data == {"name": "Test"}
|
|
|
|
def test_multiple_discriminator_field(self, api):
|
|
model = api.model(
|
|
"Test",
|
|
{
|
|
"name": fields.String(discriminator=True),
|
|
"name2": fields.String(discriminator=True),
|
|
},
|
|
)
|
|
|
|
with pytest.raises(ValueError):
|
|
api.marshal(object(), model)
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,expected",
|
|
[
|
|
("string", "string"),
|
|
(42, "42"),
|
|
],
|
|
)
|
|
def test_values(self, value, expected):
|
|
self.assert_field(fields.String(), value, expected)
|
|
|
|
|
|
class IntegerFieldTest(BaseFieldTestMixin, NumberTestMixin, FieldTestCase):
|
|
field_class = fields.Integer
|
|
|
|
def test_defaults(self):
|
|
field = fields.Integer()
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "integer"}
|
|
|
|
def test_with_default(self):
|
|
field = fields.Integer(default=42)
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "integer", "default": 42}
|
|
self.assert_field(field, None, 42)
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,expected",
|
|
[
|
|
(0, 0),
|
|
(42, 42),
|
|
("42", 42),
|
|
(None, None),
|
|
(66.6, 66),
|
|
],
|
|
)
|
|
def test_value(self, value, expected):
|
|
self.assert_field(fields.Integer(), value, expected)
|
|
|
|
def test_decode_error_on_invalid_value(self):
|
|
field = fields.Integer()
|
|
self.assert_field_raises(field, "an int")
|
|
|
|
def test_decode_error_on_invalid_type(self):
|
|
field = fields.Integer()
|
|
self.assert_field_raises(field, {"a": "dict"})
|
|
|
|
|
|
class BooleanFieldTest(BaseFieldTestMixin, FieldTestCase):
|
|
field_class = fields.Boolean
|
|
|
|
def test_defaults(self):
|
|
field = fields.Boolean()
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "boolean"}
|
|
|
|
def test_with_default(self):
|
|
field = fields.Boolean(default=True)
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "boolean", "default": True}
|
|
|
|
def test_with_example(self):
|
|
field = fields.Boolean(default=True, example=False)
|
|
assert field.__schema__ == {
|
|
"type": "boolean",
|
|
"default": True,
|
|
"example": False,
|
|
}
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,expected",
|
|
[
|
|
(True, True),
|
|
(False, False),
|
|
({}, False),
|
|
("false", False), # These consistent with inputs.boolean
|
|
("0", False),
|
|
],
|
|
)
|
|
def test_value(self, value, expected):
|
|
self.assert_field(fields.Boolean(), value, expected)
|
|
|
|
|
|
class FloatFieldTest(BaseFieldTestMixin, NumberTestMixin, FieldTestCase):
|
|
field_class = fields.Float
|
|
|
|
def test_defaults(self):
|
|
field = fields.Float()
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "number"}
|
|
|
|
def test_with_default(self):
|
|
field = fields.Float(default=0.5)
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "number", "default": 0.5}
|
|
|
|
def test_none_uses_default(self):
|
|
field = fields.Float(default=0.5)
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "number", "default": 0.5}
|
|
assert field.format(None) == 0.5
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,expected",
|
|
[
|
|
("-3.13", -3.13),
|
|
(str(-3.13), -3.13),
|
|
(3, 3.0),
|
|
],
|
|
)
|
|
def test_value(self, value, expected):
|
|
self.assert_field(fields.Float(), value, expected)
|
|
|
|
def test_raises(self):
|
|
self.assert_field_raises(fields.Float(), "bar")
|
|
|
|
def test_decode_error_on_invalid_value(self):
|
|
field = fields.Float()
|
|
self.assert_field_raises(field, "not a float")
|
|
|
|
def test_decode_error_on_invalid_type(self):
|
|
field = fields.Float()
|
|
self.assert_field_raises(field, {"a": "dict"})
|
|
|
|
|
|
PI_STR = (
|
|
"3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117"
|
|
"06798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493"
|
|
"038196442881097566593344612847564823378678316527120190914564856692346034861"
|
|
)
|
|
|
|
PI = Decimal(PI_STR)
|
|
|
|
|
|
class FixedFieldTest(BaseFieldTestMixin, NumberTestMixin, FieldTestCase):
|
|
field_class = fields.Fixed
|
|
|
|
def test_defaults(self):
|
|
field = fields.Fixed()
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "number"}
|
|
|
|
def test_with_default(self):
|
|
field = fields.Fixed(default=0.5)
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "number", "default": 0.5}
|
|
|
|
def test_fixed(self):
|
|
field5 = fields.Fixed(5)
|
|
field4 = fields.Fixed(4)
|
|
|
|
self.assert_field(field5, PI, "3.14159")
|
|
self.assert_field(field4, PI, "3.1416")
|
|
self.assert_field(field4, 3, "3.0000")
|
|
self.assert_field(field4, "03", "3.0000")
|
|
self.assert_field(field4, "03.0", "3.0000")
|
|
|
|
def test_zero(self):
|
|
self.assert_field(fields.Fixed(), "0", "0.00000")
|
|
|
|
def test_infinite(self):
|
|
field = fields.Fixed()
|
|
self.assert_field_raises(field, "+inf")
|
|
self.assert_field_raises(field, "-inf")
|
|
|
|
def test_nan(self):
|
|
field = fields.Fixed()
|
|
self.assert_field_raises(field, "NaN")
|
|
|
|
|
|
class ArbitraryFieldTest(BaseFieldTestMixin, NumberTestMixin, FieldTestCase):
|
|
field_class = fields.Arbitrary
|
|
|
|
def test_defaults(self):
|
|
field = fields.Arbitrary()
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "number"}
|
|
|
|
def test_with_default(self):
|
|
field = fields.Arbitrary(default=0.5)
|
|
assert field.__schema__ == {"type": "number", "default": 0.5}
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,expected",
|
|
[
|
|
(PI_STR, PI_STR),
|
|
(PI, PI_STR),
|
|
],
|
|
)
|
|
def test_value(self, value, expected):
|
|
self.assert_field(fields.Arbitrary(), value, expected)
|
|
|
|
|
|
class DatetimeFieldTest(BaseFieldTestMixin, FieldTestCase):
|
|
field_class = fields.DateTime
|
|
|
|
def test_defaults(self):
|
|
field = fields.DateTime()
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "string", "format": "date-time"}
|
|
self.assert_field(field, None, None)
|
|
|
|
def test_with_default(self):
|
|
field = fields.DateTime(default="2014-08-25")
|
|
assert field.__schema__ == {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"default": "2014-08-25T00:00:00",
|
|
}
|
|
self.assert_field(field, None, "2014-08-25T00:00:00")
|
|
|
|
def test_with_default_as_datetime(self):
|
|
field = fields.DateTime(default=datetime(2014, 8, 25))
|
|
assert field.__schema__ == {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"default": "2014-08-25T00:00:00",
|
|
}
|
|
self.assert_field(field, None, "2014-08-25T00:00:00")
|
|
|
|
def test_with_default_as_date(self):
|
|
field = fields.DateTime(default=date(2014, 8, 25))
|
|
assert field.__schema__ == {
|
|
"type": "string",
|
|
"format": "date-time",
|
|
"default": "2014-08-25T00:00:00",
|
|
}
|
|
self.assert_field(field, None, "2014-08-25T00:00:00")
|
|
|
|
def test_min(self):
|
|
field = fields.DateTime(min="1984-06-07T00:00:00")
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == "1984-06-07T00:00:00"
|
|
assert "exclusiveMinimum" not in field.__schema__
|
|
|
|
def test_min_as_date(self):
|
|
field = fields.DateTime(min=date(1984, 6, 7))
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == "1984-06-07T00:00:00"
|
|
assert "exclusiveMinimum" not in field.__schema__
|
|
|
|
def test_min_as_datetime(self):
|
|
field = fields.DateTime(min=datetime(1984, 6, 7, 1, 2, 0))
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == "1984-06-07T01:02:00"
|
|
assert "exclusiveMinimum" not in field.__schema__
|
|
|
|
def test_min_exlusive(self):
|
|
field = fields.DateTime(min="1984-06-07T00:00:00", exclusiveMin=True)
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == "1984-06-07T00:00:00"
|
|
assert "exclusiveMinimum" in field.__schema__
|
|
assert field.__schema__["exclusiveMinimum"] is True
|
|
|
|
def test_max(self):
|
|
field = fields.DateTime(max="1984-06-07T00:00:00")
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == "1984-06-07T00:00:00"
|
|
assert "exclusiveMaximum" not in field.__schema__
|
|
|
|
def test_max_as_date(self):
|
|
field = fields.DateTime(max=date(1984, 6, 7))
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == "1984-06-07T00:00:00"
|
|
assert "exclusiveMaximum" not in field.__schema__
|
|
|
|
def test_max_as_datetime(self):
|
|
field = fields.DateTime(max=datetime(1984, 6, 7, 1, 2, 0))
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == "1984-06-07T01:02:00"
|
|
assert "exclusiveMaximum" not in field.__schema__
|
|
|
|
def test_max_exclusive(self):
|
|
field = fields.DateTime(max="1984-06-07T00:00:00", exclusiveMax=True)
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == "1984-06-07T00:00:00"
|
|
assert "exclusiveMaximum" in field.__schema__
|
|
assert field.__schema__["exclusiveMaximum"] is True
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,expected",
|
|
[
|
|
(date(2011, 1, 1), "Sat, 01 Jan 2011 00:00:00 -0000"),
|
|
(datetime(2011, 1, 1), "Sat, 01 Jan 2011 00:00:00 -0000"),
|
|
(datetime(2011, 1, 1, 23, 59, 59), "Sat, 01 Jan 2011 23:59:59 -0000"),
|
|
(
|
|
datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc),
|
|
"Sat, 01 Jan 2011 23:59:59 -0000",
|
|
),
|
|
(
|
|
datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.timezone("CET")),
|
|
"Sat, 01 Jan 2011 22:59:59 -0000",
|
|
),
|
|
],
|
|
)
|
|
def test_rfc822_value(self, value, expected):
|
|
self.assert_field(fields.DateTime(dt_format="rfc822"), value, expected)
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,expected",
|
|
[
|
|
(date(2011, 1, 1), "2011-01-01T00:00:00"),
|
|
(datetime(2011, 1, 1), "2011-01-01T00:00:00"),
|
|
(datetime(2011, 1, 1, 23, 59, 59), "2011-01-01T23:59:59"),
|
|
(datetime(2011, 1, 1, 23, 59, 59, 1000), "2011-01-01T23:59:59.001000"),
|
|
(
|
|
datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc),
|
|
"2011-01-01T23:59:59+00:00",
|
|
),
|
|
(
|
|
datetime(2011, 1, 1, 23, 59, 59, 1000, tzinfo=pytz.utc),
|
|
"2011-01-01T23:59:59.001000+00:00",
|
|
),
|
|
(
|
|
datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.timezone("CET")),
|
|
"2011-01-01T23:59:59+01:00",
|
|
),
|
|
],
|
|
)
|
|
def test_iso8601_value(self, value, expected):
|
|
self.assert_field(fields.DateTime(dt_format="iso8601"), value, expected)
|
|
|
|
def test_unsupported_format(self):
|
|
field = fields.DateTime(dt_format="raw")
|
|
self.assert_field_raises(field, datetime.now())
|
|
|
|
def test_unsupported_value_format(self):
|
|
field = fields.DateTime(dt_format="raw")
|
|
self.assert_field_raises(field, "xxx")
|
|
|
|
|
|
class DateFieldTest(BaseFieldTestMixin, FieldTestCase):
|
|
field_class = fields.Date
|
|
|
|
def test_defaults(self):
|
|
field = fields.Date()
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "string", "format": "date"}
|
|
|
|
def test_with_default(self):
|
|
field = fields.Date(default="2014-08-25")
|
|
assert field.__schema__ == {
|
|
"type": "string",
|
|
"format": "date",
|
|
"default": "2014-08-25",
|
|
}
|
|
self.assert_field(field, None, "2014-08-25")
|
|
|
|
def test_with_default_as_date(self):
|
|
field = fields.Date(default=date(2014, 8, 25))
|
|
assert field.__schema__ == {
|
|
"type": "string",
|
|
"format": "date",
|
|
"default": "2014-08-25",
|
|
}
|
|
|
|
def test_with_default_as_datetime(self):
|
|
field = fields.Date(default=datetime(2014, 8, 25))
|
|
assert field.__schema__ == {
|
|
"type": "string",
|
|
"format": "date",
|
|
"default": "2014-08-25",
|
|
}
|
|
|
|
def test_min(self):
|
|
field = fields.Date(min="1984-06-07")
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == "1984-06-07"
|
|
assert "exclusiveMinimum" not in field.__schema__
|
|
|
|
def test_min_as_date(self):
|
|
field = fields.Date(min=date(1984, 6, 7))
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == "1984-06-07"
|
|
assert "exclusiveMinimum" not in field.__schema__
|
|
|
|
def test_min_as_datetime(self):
|
|
field = fields.Date(min=datetime(1984, 6, 7, 1, 2, 0))
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == "1984-06-07"
|
|
assert "exclusiveMinimum" not in field.__schema__
|
|
|
|
def test_min_exlusive(self):
|
|
field = fields.Date(min="1984-06-07", exclusiveMin=True)
|
|
assert "minimum" in field.__schema__
|
|
assert field.__schema__["minimum"] == "1984-06-07"
|
|
assert "exclusiveMinimum" in field.__schema__
|
|
assert field.__schema__["exclusiveMinimum"] is True
|
|
|
|
def test_max(self):
|
|
field = fields.Date(max="1984-06-07")
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == "1984-06-07"
|
|
assert "exclusiveMaximum" not in field.__schema__
|
|
|
|
def test_max_as_date(self):
|
|
field = fields.Date(max=date(1984, 6, 7))
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == "1984-06-07"
|
|
assert "exclusiveMaximum" not in field.__schema__
|
|
|
|
def test_max_as_datetime(self):
|
|
field = fields.Date(max=datetime(1984, 6, 7, 1, 2, 0))
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == "1984-06-07"
|
|
assert "exclusiveMaximum" not in field.__schema__
|
|
|
|
def test_max_exclusive(self):
|
|
field = fields.Date(max="1984-06-07", exclusiveMax=True)
|
|
assert "maximum" in field.__schema__
|
|
assert field.__schema__["maximum"] == "1984-06-07"
|
|
assert "exclusiveMaximum" in field.__schema__
|
|
assert field.__schema__["exclusiveMaximum"] is True
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,expected",
|
|
[
|
|
(date(2011, 1, 1), "2011-01-01"),
|
|
(datetime(2011, 1, 1), "2011-01-01"),
|
|
(datetime(2011, 1, 1, 23, 59, 59), "2011-01-01"),
|
|
(datetime(2011, 1, 1, 23, 59, 59, 1000), "2011-01-01"),
|
|
(datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.utc), "2011-01-01"),
|
|
(datetime(2011, 1, 1, 23, 59, 59, 1000, tzinfo=pytz.utc), "2011-01-01"),
|
|
(
|
|
datetime(2011, 1, 1, 23, 59, 59, tzinfo=pytz.timezone("CET")),
|
|
"2011-01-01",
|
|
),
|
|
],
|
|
)
|
|
def test_value(self, value, expected):
|
|
self.assert_field(fields.Date(), value, expected)
|
|
|
|
def test_unsupported_value_format(self):
|
|
self.assert_field_raises(fields.Date(), "xxx")
|
|
|
|
|
|
class FormatedStringFieldTest(StringTestMixin, BaseFieldTestMixin, FieldTestCase):
|
|
field_class = partial(fields.FormattedString, "Hello {name}")
|
|
|
|
def test_defaults(self):
|
|
field = fields.FormattedString("Hello {name}")
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "string"}
|
|
|
|
def test_dict(self):
|
|
data = {
|
|
"sid": 3,
|
|
"account_sid": 4,
|
|
}
|
|
field = fields.FormattedString("/foo/{account_sid}/{sid}/")
|
|
assert field.output("foo", data) == "/foo/4/3/"
|
|
|
|
def test_object(self, mocker):
|
|
obj = mocker.Mock()
|
|
obj.sid = 3
|
|
obj.account_sid = 4
|
|
field = fields.FormattedString("/foo/{account_sid}/{sid}/")
|
|
assert field.output("foo", obj) == "/foo/4/3/"
|
|
|
|
def test_none(self):
|
|
field = fields.FormattedString("{foo}")
|
|
# self.assert_field_raises(field, None)
|
|
with pytest.raises(fields.MarshallingError):
|
|
field.output("foo", None)
|
|
|
|
def test_invalid_object(self):
|
|
field = fields.FormattedString("/foo/{0[account_sid]}/{0[sid]}/")
|
|
self.assert_field_raises(field, {})
|
|
|
|
def test_tuple(self):
|
|
field = fields.FormattedString("/foo/{0[account_sid]}/{0[sid]}/")
|
|
self.assert_field_raises(field, (3, 4))
|
|
|
|
|
|
class UrlFieldTest(StringTestMixin, BaseFieldTestMixin, FieldTestCase):
|
|
field_class = partial(fields.Url, "endpoint")
|
|
|
|
def test_defaults(self):
|
|
field = fields.Url("endpoint")
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "string"}
|
|
|
|
def test_invalid_object(self, app):
|
|
app.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
field = fields.Url("foobar")
|
|
|
|
with app.test_request_context("/"):
|
|
with pytest.raises(fields.MarshallingError):
|
|
field.output("foo", None)
|
|
|
|
def test_simple(self, app, mocker):
|
|
app.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
field = fields.Url("foobar")
|
|
obj = mocker.Mock(foo=42)
|
|
|
|
with app.test_request_context("/"):
|
|
assert "/42" == field.output("foo", obj)
|
|
|
|
def test_absolute(self, app, mocker):
|
|
app.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
field = fields.Url("foobar", absolute=True)
|
|
obj = mocker.Mock(foo=42)
|
|
|
|
with app.test_request_context("/"):
|
|
assert "http://localhost/42" == field.output("foo", obj)
|
|
|
|
def test_absolute_scheme(self, app, mocker):
|
|
"""Url.scheme should override current_request.scheme"""
|
|
app.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
field = fields.Url("foobar", absolute=True, scheme="https")
|
|
obj = mocker.Mock(foo=42)
|
|
|
|
with app.test_request_context("/", base_url="http://localhost"):
|
|
assert "https://localhost/42" == field.output("foo", obj)
|
|
|
|
def test_without_endpoint_invalid_object(self, app):
|
|
app.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
field = fields.Url()
|
|
|
|
with app.test_request_context("/foo"):
|
|
with pytest.raises(fields.MarshallingError):
|
|
field.output("foo", None)
|
|
|
|
def test_without_endpoint(self, app, mocker):
|
|
app.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
field = fields.Url()
|
|
obj = mocker.Mock(foo=42)
|
|
|
|
with app.test_request_context("/foo"):
|
|
assert "/42" == field.output("foo", obj)
|
|
|
|
def test_without_endpoint_absolute(self, app, mocker):
|
|
app.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
field = fields.Url(absolute=True)
|
|
obj = mocker.Mock(foo=42)
|
|
|
|
with app.test_request_context("/foo"):
|
|
assert "http://localhost/42" == field.output("foo", obj)
|
|
|
|
def test_without_endpoint_absolute_scheme(self, app, mocker):
|
|
app.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
field = fields.Url(absolute=True, scheme="https")
|
|
obj = mocker.Mock(foo=42)
|
|
|
|
with app.test_request_context("/foo", base_url="http://localhost"):
|
|
assert "https://localhost/42" == field.output("foo", obj)
|
|
|
|
def test_with_blueprint_invalid_object(self, app):
|
|
bp = Blueprint("foo", __name__, url_prefix="/foo")
|
|
bp.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
app.register_blueprint(bp)
|
|
field = fields.Url()
|
|
|
|
with app.test_request_context("/foo/foo"):
|
|
with pytest.raises(fields.MarshallingError):
|
|
field.output("foo", None)
|
|
|
|
def test_with_blueprint(self, app, mocker):
|
|
bp = Blueprint("foo", __name__, url_prefix="/foo")
|
|
bp.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
app.register_blueprint(bp)
|
|
field = fields.Url()
|
|
obj = mocker.Mock(foo=42)
|
|
|
|
with app.test_request_context("/foo/foo"):
|
|
assert "/foo/42" == field.output("foo", obj)
|
|
|
|
def test_with_blueprint_absolute(self, app, mocker):
|
|
bp = Blueprint("foo", __name__, url_prefix="/foo")
|
|
bp.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
app.register_blueprint(bp)
|
|
field = fields.Url(absolute=True)
|
|
obj = mocker.Mock(foo=42)
|
|
|
|
with app.test_request_context("/foo/foo"):
|
|
assert "http://localhost/foo/42" == field.output("foo", obj)
|
|
|
|
def test_with_blueprint_absolute_scheme(self, app, mocker):
|
|
bp = Blueprint("foo", __name__, url_prefix="/foo")
|
|
bp.add_url_rule("/<foo>", "foobar", view_func=lambda x: x)
|
|
app.register_blueprint(bp)
|
|
field = fields.Url(absolute=True, scheme="https")
|
|
obj = mocker.Mock(foo=42)
|
|
|
|
with app.test_request_context("/foo/foo", base_url="http://localhost"):
|
|
assert "https://localhost/foo/42" == field.output("foo", obj)
|
|
|
|
|
|
class NestedFieldTest(FieldTestCase):
|
|
def test_defaults(self, api):
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
field = fields.Nested(nested_fields)
|
|
assert not field.required
|
|
assert field.__schema__ == {"$ref": "#/definitions/NestedModel"}
|
|
|
|
def test_with_required(self, api):
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
field = fields.Nested(nested_fields, required=True)
|
|
assert field.required
|
|
assert not field.allow_null
|
|
assert field.__schema__ == {"$ref": "#/definitions/NestedModel"}
|
|
|
|
def test_with_description(self, api):
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
field = fields.Nested(nested_fields, description="A description")
|
|
assert field.__schema__ == {
|
|
"description": "A description",
|
|
"allOf": [{"$ref": "#/definitions/NestedModel"}],
|
|
}
|
|
|
|
def test_with_title(self, api):
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
field = fields.Nested(nested_fields, title="A title")
|
|
assert field.__schema__ == {
|
|
"title": "A title",
|
|
"allOf": [{"$ref": "#/definitions/NestedModel"}],
|
|
}
|
|
|
|
def test_with_allow_null(self, api):
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
field = fields.Nested(nested_fields, allow_null=True)
|
|
assert not field.required
|
|
assert field.allow_null
|
|
assert field.__schema__ == {"$ref": "#/definitions/NestedModel"}
|
|
|
|
def test_with_skip_none(self, api):
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
field = fields.Nested(nested_fields, skip_none=True)
|
|
assert not field.required
|
|
assert field.skip_none
|
|
assert field.__schema__ == {"$ref": "#/definitions/NestedModel"}
|
|
|
|
def test_with_readonly(self, app):
|
|
api = Api(app)
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
field = fields.Nested(nested_fields, readonly=True)
|
|
assert field.__schema__ == {
|
|
"readOnly": True,
|
|
"allOf": [{"$ref": "#/definitions/NestedModel"}],
|
|
}
|
|
|
|
def test_as_list(self, api):
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
field = fields.Nested(nested_fields, as_list=True)
|
|
assert field.as_list
|
|
assert field.__schema__ == {
|
|
"type": "array",
|
|
"items": {"$ref": "#/definitions/NestedModel"},
|
|
}
|
|
|
|
def test_as_list_is_reusable(self, api):
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
|
|
field = fields.Nested(nested_fields, as_list=True)
|
|
assert field.__schema__ == {
|
|
"type": "array",
|
|
"items": {"$ref": "#/definitions/NestedModel"},
|
|
}
|
|
|
|
field = fields.Nested(nested_fields)
|
|
assert field.__schema__ == {"$ref": "#/definitions/NestedModel"}
|
|
|
|
|
|
class ListFieldTest(BaseFieldTestMixin, FieldTestCase):
|
|
field_class = partial(fields.List, fields.String)
|
|
|
|
def test_defaults(self):
|
|
field = fields.List(fields.String)
|
|
assert not field.required
|
|
assert field.__schema__ == {"type": "array", "items": {"type": "string"}}
|
|
|
|
def test_with_nested_field(self, api):
|
|
nested_fields = api.model("NestedModel", {"name": fields.String})
|
|
field = fields.List(fields.Nested(nested_fields))
|
|
assert field.__schema__ == {
|
|
"type": "array",
|
|
"items": {"$ref": "#/definitions/NestedModel"},
|
|
}
|
|
|
|
data = [{"name": "John Doe", "age": 42}, {"name": "Jane Doe", "age": 66}]
|
|
expected = [
|
|
OrderedDict([("name", "John Doe")]),
|
|
OrderedDict([("name", "Jane Doe")]),
|
|
]
|
|
self.assert_field(field, data, expected)
|
|
|
|
def test_min_items(self):
|
|
field = fields.List(fields.String, min_items=5)
|
|
assert "minItems" in field.__schema__
|
|
assert field.__schema__["minItems"] == 5
|
|
|
|
def test_max_items(self):
|
|
field = fields.List(fields.String, max_items=42)
|
|
assert "maxItems" in field.__schema__
|
|
assert field.__schema__["maxItems"] == 42
|
|
|
|
def test_unique(self):
|
|
field = fields.List(fields.String, unique=True)
|
|
assert "uniqueItems" in field.__schema__
|
|
assert field.__schema__["uniqueItems"] is True
|
|
|
|
@pytest.mark.parametrize(
|
|
"value,expected",
|
|
[
|
|
(["a", "b", "c"], ["a", "b", "c"]),
|
|
(["c", "b", "a"], ["c", "b", "a"]),
|
|
(("a", "b", "c"), ["a", "b", "c"]),
|
|
(["a"], ["a"]),
|
|
(None, None),
|
|
],
|
|
)
|
|
def test_value(self, value, expected):
|
|
self.assert_field(fields.List(fields.String()), value, expected)
|
|
|
|
def test_with_set(self):
|
|
field = fields.List(fields.String)
|
|
value = set(["a", "b", "c"])
|
|
output = field.output("foo", {"foo": value})
|
|
assert set(output) == value
|
|
|
|
def test_with_scoped_attribute_on_dict_or_obj(self):
|
|
class Test(object):
|
|
def __init__(self, data):
|
|
self.data = data
|
|
|
|
class Nested(object):
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
nesteds = [Nested(i) for i in ["a", "b", "c"]]
|
|
test_obj = Test(nesteds)
|
|
test_dict = {"data": [{"value": "a"}, {"value": "b"}, {"value": "c"}]}
|
|
|
|
field = fields.List(fields.String(attribute="value"), attribute="data")
|
|
assert ["a" == "b", "c"], field.output("whatever", test_obj)
|
|
assert ["a" == "b", "c"], field.output("whatever", test_dict)
|
|
|
|
def test_with_attribute(self):
|
|
data = [{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 3, "b": 1}]
|
|
field = fields.List(fields.Integer(attribute="a"))
|
|
self.assert_field(field, data, [1, 2, 3])
|
|
|
|
def test_list_of_raw(self):
|
|
field = fields.List(fields.Raw)
|
|
|
|
data = [{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 3, "b": 1}]
|
|
expected = [
|
|
OrderedDict([("a", 1), ("b", 1)]),
|
|
OrderedDict([("a", 2), ("b", 1)]),
|
|
OrderedDict([("a", 3), ("b", 1)]),
|
|
]
|
|
self.assert_field(field, data, expected)
|
|
|
|
data = [1, 2, "a"]
|
|
self.assert_field(field, data, data)
|
|
|
|
|
|
class WildcardFieldTest(BaseFieldTestMixin, FieldTestCase):
|
|
field_class = partial(fields.Wildcard, fields.String)
|
|
|
|
def test_types(self):
|
|
with pytest.raises(fields.MarshallingError):
|
|
|
|
class WrongType:
|
|
pass
|
|
|
|
x = WrongType()
|
|
field1 = fields.Wildcard(WrongType) # noqa
|
|
field2 = fields.Wildcard(x) # noqa
|
|
|
|
def test_defaults(self):
|
|
field = fields.Wildcard(fields.String)
|
|
assert not field.required
|
|
assert field.__schema__ == {
|
|
"type": "object",
|
|
"additionalProperties": {"type": "string"},
|
|
}
|
|
|
|
def test_with_scoped_attribute_on_dict_or_obj(self):
|
|
class Test(object):
|
|
def __init__(self, data):
|
|
self.data = data
|
|
|
|
class Nested(object):
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
nesteds = [Nested(i) for i in ["a", "b", "c"]]
|
|
test_obj = Test(nesteds)
|
|
test_dict = {"data": [{"value": "a"}, {"value": "b"}, {"value": "c"}]}
|
|
|
|
field = fields.Wildcard(fields.String(attribute="value"), attribute="data")
|
|
assert ["a" == "b", "c"], field.output("whatever", test_obj)
|
|
assert ["a" == "b", "c"], field.output("whatever", test_dict)
|
|
|
|
def test_list_of_raw(self):
|
|
field = fields.Wildcard(fields.Raw)
|
|
|
|
data = [{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 3, "b": 1}]
|
|
expected = [
|
|
OrderedDict([("a", 1), ("b", 1)]),
|
|
OrderedDict([("a", 2), ("b", 1)]),
|
|
OrderedDict([("a", 3), ("b", 1)]),
|
|
]
|
|
self.assert_field(field, data, expected)
|
|
|
|
data = [1, 2, "a"]
|
|
self.assert_field(field, data, data)
|
|
|
|
def test_wildcard(self, api):
|
|
wild1 = fields.Wildcard(fields.String)
|
|
wild2 = fields.Wildcard(fields.Integer)
|
|
wild3 = fields.Wildcard(fields.String)
|
|
wild4 = fields.Wildcard(fields.String, default="x")
|
|
wild5 = fields.Wildcard(fields.String)
|
|
wild6 = fields.Wildcard(fields.Integer)
|
|
wild7 = fields.Wildcard(fields.String)
|
|
wild8 = fields.Wildcard(fields.String)
|
|
|
|
mod5 = OrderedDict()
|
|
mod5["toto"] = fields.Integer
|
|
mod5["bob"] = fields.Integer
|
|
mod5["*"] = wild5
|
|
|
|
wild_fields1 = api.model("WildcardModel1", {"*": wild1})
|
|
wild_fields2 = api.model("WildcardModel2", {"j*": wild2})
|
|
wild_fields3 = api.model("WildcardModel3", {"*": wild3})
|
|
wild_fields4 = api.model("WildcardModel4", {"*": wild4})
|
|
wild_fields5 = api.model("WildcardModel5", mod5)
|
|
wild_fields6 = api.model(
|
|
"WildcardModel6",
|
|
{
|
|
"nested": {
|
|
"f1": fields.String(default="12"),
|
|
"f2": fields.Integer(default=13),
|
|
},
|
|
"a*": wild6,
|
|
},
|
|
)
|
|
wild_fields7 = api.model("WildcardModel7", {"*": wild7})
|
|
wild_fields8 = api.model("WildcardModel8", {"*": wild8})
|
|
|
|
class Dummy(object):
|
|
john = 12
|
|
bob = "42"
|
|
alice = None
|
|
|
|
class Dummy2(object):
|
|
pass
|
|
|
|
class Dummy3(object):
|
|
a = None
|
|
b = None
|
|
|
|
data = {"John": 12, "bob": 42, "Jane": "68"}
|
|
data3 = Dummy()
|
|
data4 = Dummy2()
|
|
data5 = {"John": 12, "bob": 42, "Jane": "68", "toto": "72"}
|
|
data6 = {"nested": {"f1": 12, "f2": 13}, "alice": "14"}
|
|
data7 = Dummy3()
|
|
data8 = None
|
|
expected1 = {"John": "12", "bob": "42", "Jane": "68"}
|
|
expected2 = {"John": 12, "Jane": 68}
|
|
expected3 = {"john": "12", "bob": "42"}
|
|
expected4 = {"*": "x"}
|
|
expected5 = {"John": "12", "bob": 42, "Jane": "68", "toto": 72}
|
|
expected6 = {"nested": {"f1": "12", "f2": 13}, "alice": 14}
|
|
expected7 = {}
|
|
expected8 = {}
|
|
|
|
result1 = api.marshal(data, wild_fields1)
|
|
result2 = api.marshal(data, wild_fields2)
|
|
result3 = api.marshal(data3, wild_fields3, skip_none=True)
|
|
result4 = api.marshal(data4, wild_fields4)
|
|
result5 = api.marshal(data5, wild_fields5)
|
|
result6 = api.marshal(data6, wild_fields6)
|
|
result7 = api.marshal(data7, wild_fields7, skip_none=True)
|
|
result8 = api.marshal(data8, wild_fields8, skip_none=True)
|
|
|
|
assert expected1 == result1
|
|
assert expected2 == result2
|
|
assert expected3 == result3
|
|
assert expected4 == result4
|
|
assert expected5 == result5
|
|
assert expected6 == result6
|
|
assert expected7 == result7
|
|
assert expected8 == result8
|
|
|
|
def test_clone(self, api):
|
|
wild1 = fields.Wildcard(fields.String)
|
|
wild2 = wild1.clone()
|
|
|
|
wild_fields1 = api.model("cloneWildcard1", {"*": wild1})
|
|
wild_fields2 = api.model("cloneWildcard2", {"*": wild2})
|
|
|
|
data = {"John": 12, "bob": 42, "Jane": "68"}
|
|
expected1 = {"John": "12", "bob": "42", "Jane": "68"}
|
|
|
|
result1 = api.marshal(data, wild_fields1)
|
|
result2 = api.marshal(data, wild_fields2)
|
|
|
|
assert expected1 == result1
|
|
assert result2 == result1
|
|
|
|
|
|
class ClassNameFieldTest(StringTestMixin, BaseFieldTestMixin, FieldTestCase):
|
|
field_class = fields.ClassName
|
|
|
|
def test_simple_string_field(self):
|
|
field = fields.ClassName()
|
|
assert not field.required
|
|
assert not field.discriminator
|
|
assert field.__schema__ == {"type": "string"}
|
|
|
|
def test_default_output_classname(self, api):
|
|
model = api.model(
|
|
"Test",
|
|
{
|
|
"name": fields.ClassName(),
|
|
},
|
|
)
|
|
|
|
class FakeClass(object):
|
|
pass
|
|
|
|
data = api.marshal(FakeClass(), model)
|
|
assert data == {"name": "FakeClass"}
|
|
|
|
def test_output_dash(self, api):
|
|
model = api.model(
|
|
"Test",
|
|
{
|
|
"name": fields.ClassName(dash=True),
|
|
},
|
|
)
|
|
|
|
class FakeClass(object):
|
|
pass
|
|
|
|
data = api.marshal(FakeClass(), model)
|
|
assert data == {"name": "fake_class"}
|
|
|
|
def test_with_dict(self, api):
|
|
model = api.model(
|
|
"Test",
|
|
{
|
|
"name": fields.ClassName(),
|
|
},
|
|
)
|
|
|
|
data = api.marshal({}, model)
|
|
assert data == {"name": "object"}
|
|
|
|
|
|
class PolymorphTest(FieldTestCase):
|
|
def test_polymorph_field(self, api):
|
|
parent = api.model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
},
|
|
)
|
|
|
|
child1 = api.inherit(
|
|
"Child1",
|
|
parent,
|
|
{
|
|
"extra1": fields.String,
|
|
},
|
|
)
|
|
|
|
child2 = api.inherit(
|
|
"Child2",
|
|
parent,
|
|
{
|
|
"extra2": fields.String,
|
|
},
|
|
)
|
|
|
|
class Child1(object):
|
|
name = "child1"
|
|
extra1 = "extra1"
|
|
|
|
class Child2(object):
|
|
name = "child2"
|
|
extra2 = "extra2"
|
|
|
|
mapping = {Child1: child1, Child2: child2}
|
|
|
|
thing = api.model(
|
|
"Thing",
|
|
{
|
|
"owner": fields.Polymorph(mapping),
|
|
},
|
|
)
|
|
|
|
def data(cls):
|
|
return api.marshal({"owner": cls()}, thing)
|
|
|
|
assert data(Child1) == {"owner": {"name": "child1", "extra1": "extra1"}}
|
|
|
|
assert data(Child2) == {"owner": {"name": "child2", "extra2": "extra2"}}
|
|
|
|
def test_polymorph_field_no_common_ancestor(self, api):
|
|
child1 = api.model(
|
|
"Child1",
|
|
{
|
|
"extra1": fields.String,
|
|
},
|
|
)
|
|
|
|
child2 = api.model(
|
|
"Child2",
|
|
{
|
|
"extra2": fields.String,
|
|
},
|
|
)
|
|
|
|
class Child1(object):
|
|
pass
|
|
|
|
class Child2(object):
|
|
pass
|
|
|
|
mapping = {Child1: child1, Child2: child2}
|
|
|
|
with pytest.raises(ValueError):
|
|
fields.Polymorph(mapping)
|
|
|
|
def test_polymorph_field_unknown_class(self, api):
|
|
parent = api.model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
},
|
|
)
|
|
|
|
child1 = api.inherit(
|
|
"Child1",
|
|
parent,
|
|
{
|
|
"extra1": fields.String,
|
|
},
|
|
)
|
|
|
|
child2 = api.inherit(
|
|
"Child2",
|
|
parent,
|
|
{
|
|
"extra2": fields.String,
|
|
},
|
|
)
|
|
|
|
class Child1(object):
|
|
name = "child1"
|
|
extra1 = "extra1"
|
|
|
|
class Child2(object):
|
|
name = "child2"
|
|
extra2 = "extra2"
|
|
|
|
mapping = {Child1: child1, Child2: child2}
|
|
|
|
thing = api.model(
|
|
"Thing",
|
|
{
|
|
"owner": fields.Polymorph(mapping),
|
|
},
|
|
)
|
|
|
|
with pytest.raises(ValueError):
|
|
api.marshal({"owner": object()}, thing)
|
|
|
|
def test_polymorph_field_does_not_have_ambiguous_mappings(self, api):
|
|
"""
|
|
Regression test for https://github.com/noirbizarre/flask-restx/pull/691
|
|
"""
|
|
parent = api.model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
},
|
|
)
|
|
|
|
child = api.inherit(
|
|
"Child",
|
|
parent,
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
class Parent(object):
|
|
name = "parent"
|
|
|
|
class Child(Parent):
|
|
extra = "extra"
|
|
|
|
mapping = {Parent: parent, Child: child}
|
|
|
|
thing = api.model(
|
|
"Thing",
|
|
{
|
|
"owner": fields.Polymorph(mapping),
|
|
},
|
|
)
|
|
|
|
api.marshal({"owner": Child()}, thing)
|
|
|
|
def test_polymorph_field_required_default(self, api):
|
|
parent = api.model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
},
|
|
)
|
|
|
|
child1 = api.inherit(
|
|
"Child1",
|
|
parent,
|
|
{
|
|
"extra1": fields.String,
|
|
},
|
|
)
|
|
|
|
child2 = api.inherit(
|
|
"Child2",
|
|
parent,
|
|
{
|
|
"extra2": fields.String,
|
|
},
|
|
)
|
|
|
|
class Child1(object):
|
|
name = "child1"
|
|
extra1 = "extra1"
|
|
|
|
class Child2(object):
|
|
name = "child2"
|
|
extra2 = "extra2"
|
|
|
|
mapping = {Child1: child1, Child2: child2}
|
|
|
|
thing = api.model(
|
|
"Thing",
|
|
{
|
|
"owner": fields.Polymorph(
|
|
mapping, required=True, default={"name": "default"}
|
|
),
|
|
},
|
|
)
|
|
|
|
data = api.marshal({}, thing)
|
|
|
|
assert data == {"owner": {"name": "default"}}
|
|
|
|
def test_polymorph_field_not_required(self, api):
|
|
parent = api.model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
},
|
|
)
|
|
|
|
child1 = api.inherit(
|
|
"Child1",
|
|
parent,
|
|
{
|
|
"extra1": fields.String,
|
|
},
|
|
)
|
|
|
|
child2 = api.inherit(
|
|
"Child2",
|
|
parent,
|
|
{
|
|
"extra2": fields.String,
|
|
},
|
|
)
|
|
|
|
class Child1(object):
|
|
name = "child1"
|
|
extra1 = "extra1"
|
|
|
|
class Child2(object):
|
|
name = "child2"
|
|
extra2 = "extra2"
|
|
|
|
mapping = {Child1: child1, Child2: child2}
|
|
|
|
thing = api.model(
|
|
"Thing",
|
|
{
|
|
"owner": fields.Polymorph(mapping),
|
|
},
|
|
)
|
|
|
|
data = api.marshal({}, thing)
|
|
|
|
assert data == {"owner": None}
|
|
|
|
def test_polymorph_with_discriminator(self, api):
|
|
parent = api.model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
"model": fields.String(discriminator=True),
|
|
},
|
|
)
|
|
|
|
child1 = api.inherit(
|
|
"Child1",
|
|
parent,
|
|
{
|
|
"extra1": fields.String,
|
|
},
|
|
)
|
|
|
|
child2 = api.inherit(
|
|
"Child2",
|
|
parent,
|
|
{
|
|
"extra2": fields.String,
|
|
},
|
|
)
|
|
|
|
class Child1(object):
|
|
name = "child1"
|
|
extra1 = "extra1"
|
|
|
|
class Child2(object):
|
|
name = "child2"
|
|
extra2 = "extra2"
|
|
|
|
mapping = {Child1: child1, Child2: child2}
|
|
|
|
thing = api.model(
|
|
"Thing",
|
|
{
|
|
"owner": fields.Polymorph(mapping),
|
|
},
|
|
)
|
|
|
|
def data(cls):
|
|
return api.marshal({"owner": cls()}, thing)
|
|
|
|
assert data(Child1) == {
|
|
"owner": {"name": "child1", "model": "Child1", "extra1": "extra1"}
|
|
}
|
|
|
|
assert data(Child2) == {
|
|
"owner": {"name": "child2", "model": "Child2", "extra2": "extra2"}
|
|
}
|
|
|
|
|
|
class CustomFieldTest(FieldTestCase):
|
|
def test_custom_field(self):
|
|
class CustomField(fields.Integer):
|
|
__schema_format__ = "int64"
|
|
|
|
field = CustomField()
|
|
|
|
assert field.__schema__ == {"type": "integer", "format": "int64"}
|
|
|
|
|
|
class FieldsHelpersTest(object):
|
|
def test_to_dict(self):
|
|
expected = data = {"foo": 42}
|
|
assert fields.to_marshallable_type(data) == expected
|
|
|
|
def test_to_dict_obj(self):
|
|
class Foo(object):
|
|
def __init__(self):
|
|
self.foo = 42
|
|
|
|
expected = {"foo": 42}
|
|
assert fields.to_marshallable_type(Foo()) == expected
|
|
|
|
def test_to_dict_custom_marshal(self):
|
|
class Foo(object):
|
|
def __marshallable__(self):
|
|
return {"foo": 42}
|
|
|
|
expected = {"foo": 42}
|
|
assert fields.to_marshallable_type(Foo()) == expected
|
|
|
|
def test_get_value(self):
|
|
assert fields.get_value("foo", {"foo": 42}) == 42
|
|
|
|
def test_get_value_no_value(self):
|
|
assert fields.get_value("foo", {"foo": 42}) == 42
|
|
|
|
def test_get_value_obj(self, mocker):
|
|
assert fields.get_value("foo", mocker.Mock(foo=42)) == 42
|
|
|
|
def test_get_value_indexable_object(self):
|
|
class Test(object):
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
def __getitem__(self, n):
|
|
if type(n) is int:
|
|
if n < 3:
|
|
return n
|
|
raise IndexError
|
|
raise TypeError
|
|
|
|
obj = Test("hi")
|
|
assert fields.get_value("value", obj) == "hi"
|
|
|
|
def test_get_value_int_indexable_list(self):
|
|
assert fields.get_value("bar.0", {"bar": [42]}) == 42
|
|
|
|
def test_get_value_int_indexable_list_with_str(self):
|
|
assert fields.get_value("bar.abc", {"bar": [42]}) is None
|
|
|
|
def test_get_value_int_indexable_nested_list(self):
|
|
assert fields.get_value("bar.0.val", {"bar": [{"val": 42}]}) == 42
|
|
|
|
def test_get_value_int_indexable_tuple_with_str(self):
|
|
assert fields.get_value("bar.abc", {"bar": (42, 43)}) is None
|
|
|
|
def test_get_value_int_indexable_tuple(self):
|
|
assert fields.get_value("bar.0", {"bar": (42, 43)}) == 42
|
|
|
|
def test_get_value_int_indexable_nested_tuple(self):
|
|
assert fields.get_value("bar.0.val", {"bar": [{"val": 42}]}) == 42
|