oggit/packages/flask-restx/opengnsys-flask-restx-1.3.0/tests/test_fields.py

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