696 lines
18 KiB
Python
696 lines
18 KiB
Python
import copy
|
|
import pytest
|
|
|
|
from collections import OrderedDict
|
|
|
|
from flask_restx import fields, Model, OrderedModel, SchemaModel
|
|
|
|
|
|
class ModelTest(object):
|
|
def test_model_as_flat_dict(self):
|
|
model = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime,
|
|
},
|
|
)
|
|
|
|
assert isinstance(model, dict)
|
|
assert not isinstance(model, OrderedDict)
|
|
|
|
assert model.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_model_as_ordered_dict(self):
|
|
model = OrderedModel(
|
|
"Person",
|
|
[
|
|
("name", fields.String),
|
|
("age", fields.Integer),
|
|
("birthdate", fields.DateTime),
|
|
],
|
|
)
|
|
|
|
assert isinstance(model, OrderedDict)
|
|
|
|
assert model.__schema__ == {
|
|
"type": "object",
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
},
|
|
}
|
|
|
|
def test_model_as_nested_dict(self):
|
|
address = Model(
|
|
"Address",
|
|
{
|
|
"road": fields.String,
|
|
},
|
|
)
|
|
|
|
person = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime,
|
|
"address": fields.Nested(address),
|
|
},
|
|
)
|
|
|
|
assert person.__schema__ == {
|
|
# 'required': ['address'],
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"address": {
|
|
"$ref": "#/definitions/Address",
|
|
},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
assert address.__schema__ == {
|
|
"properties": {
|
|
"road": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_model_as_dict_with_list(self):
|
|
model = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"tags": fields.List(fields.String),
|
|
},
|
|
)
|
|
|
|
assert model.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"tags": {"type": "array", "items": {"type": "string"}},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_model_as_nested_dict_with_list(self):
|
|
address = Model(
|
|
"Address",
|
|
{
|
|
"road": fields.String,
|
|
},
|
|
)
|
|
|
|
person = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime,
|
|
"addresses": fields.List(fields.Nested(address)),
|
|
},
|
|
)
|
|
|
|
assert person.__schema__ == {
|
|
# 'required': ['address'],
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"addresses": {
|
|
"type": "array",
|
|
"items": {
|
|
"$ref": "#/definitions/Address",
|
|
},
|
|
},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
assert address.__schema__ == {
|
|
"properties": {
|
|
"road": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_model_with_required(self):
|
|
model = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String(required=True),
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime(required=True),
|
|
},
|
|
)
|
|
|
|
assert model.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
},
|
|
"required": ["birthdate", "name"],
|
|
"type": "object",
|
|
}
|
|
|
|
def test_model_as_nested_dict_and_required(self):
|
|
address = Model(
|
|
"Address",
|
|
{
|
|
"road": fields.String,
|
|
},
|
|
)
|
|
|
|
person = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime,
|
|
"address": fields.Nested(address, required=True),
|
|
},
|
|
)
|
|
|
|
assert person.__schema__ == {
|
|
"required": ["address"],
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"address": {
|
|
"$ref": "#/definitions/Address",
|
|
},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
assert address.__schema__ == {
|
|
"properties": {
|
|
"road": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_model_with_discriminator(self):
|
|
model = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String(discriminator=True),
|
|
"age": fields.Integer,
|
|
},
|
|
)
|
|
|
|
assert model.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
},
|
|
"discriminator": "name",
|
|
"required": ["name"],
|
|
"type": "object",
|
|
}
|
|
|
|
def test_model_with_discriminator_override_require(self):
|
|
model = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String(discriminator=True, required=False),
|
|
"age": fields.Integer,
|
|
},
|
|
)
|
|
|
|
assert model.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
},
|
|
"discriminator": "name",
|
|
"required": ["name"],
|
|
"type": "object",
|
|
}
|
|
|
|
def test_model_deepcopy(self):
|
|
parent = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer(description="foo"),
|
|
},
|
|
)
|
|
|
|
child = parent.inherit(
|
|
"Child",
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
parent_copy = copy.deepcopy(parent)
|
|
|
|
assert parent_copy["age"].description == "foo"
|
|
|
|
parent_copy["age"].description = "bar"
|
|
|
|
assert parent["age"].description == "foo"
|
|
assert parent_copy["age"].description == "bar"
|
|
|
|
child = parent.inherit(
|
|
"Child",
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
child_copy = copy.deepcopy(child)
|
|
assert child_copy.__parents__[0] == parent
|
|
|
|
def test_clone_from_instance(self):
|
|
parent = Model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime,
|
|
},
|
|
)
|
|
|
|
child = parent.clone(
|
|
"Child",
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
assert child.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"extra": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_clone_from_class(self):
|
|
parent = Model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime,
|
|
},
|
|
)
|
|
|
|
child = Model.clone(
|
|
"Child",
|
|
parent,
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
assert child.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"extra": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_clone_from_instance_with_multiple_parents(self):
|
|
grand_parent = Model(
|
|
"GrandParent",
|
|
{
|
|
"grand_parent": fields.String,
|
|
},
|
|
)
|
|
|
|
parent = Model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime,
|
|
},
|
|
)
|
|
|
|
child = grand_parent.clone(
|
|
"Child",
|
|
parent,
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
assert child.__schema__ == {
|
|
"properties": {
|
|
"grand_parent": {"type": "string"},
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"extra": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_clone_from_class_with_multiple_parents(self):
|
|
grand_parent = Model(
|
|
"GrandParent",
|
|
{
|
|
"grand_parent": fields.String,
|
|
},
|
|
)
|
|
|
|
parent = Model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime,
|
|
},
|
|
)
|
|
|
|
child = Model.clone(
|
|
"Child",
|
|
grand_parent,
|
|
parent,
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
assert child.__schema__ == {
|
|
"properties": {
|
|
"grand_parent": {"type": "string"},
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"extra": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_inherit_from_instance(self):
|
|
parent = Model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
},
|
|
)
|
|
|
|
child = parent.inherit(
|
|
"Child",
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
assert parent.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
assert child.__schema__ == {
|
|
"allOf": [
|
|
{"$ref": "#/definitions/Parent"},
|
|
{"properties": {"extra": {"type": "string"}}, "type": "object"},
|
|
]
|
|
}
|
|
|
|
def test_inherit_from_class(self):
|
|
parent = Model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
},
|
|
)
|
|
|
|
child = Model.inherit(
|
|
"Child",
|
|
parent,
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
assert parent.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
assert child.__schema__ == {
|
|
"allOf": [
|
|
{"$ref": "#/definitions/Parent"},
|
|
{"properties": {"extra": {"type": "string"}}, "type": "object"},
|
|
]
|
|
}
|
|
|
|
def test_inherit_from_class_from_multiple_parents(self):
|
|
grand_parent = Model(
|
|
"GrandParent",
|
|
{
|
|
"grand_parent": fields.String,
|
|
},
|
|
)
|
|
|
|
parent = Model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
},
|
|
)
|
|
|
|
child = Model.inherit(
|
|
"Child",
|
|
grand_parent,
|
|
parent,
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
assert child.__schema__ == {
|
|
"allOf": [
|
|
{"$ref": "#/definitions/GrandParent"},
|
|
{"$ref": "#/definitions/Parent"},
|
|
{"properties": {"extra": {"type": "string"}}, "type": "object"},
|
|
]
|
|
}
|
|
|
|
def test_inherit_from_instance_from_multiple_parents(self):
|
|
grand_parent = Model(
|
|
"GrandParent",
|
|
{
|
|
"grand_parent": fields.String,
|
|
},
|
|
)
|
|
|
|
parent = Model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
},
|
|
)
|
|
|
|
child = grand_parent.inherit(
|
|
"Child",
|
|
parent,
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
assert child.__schema__ == {
|
|
"allOf": [
|
|
{"$ref": "#/definitions/GrandParent"},
|
|
{"$ref": "#/definitions/Parent"},
|
|
{"properties": {"extra": {"type": "string"}}, "type": "object"},
|
|
]
|
|
}
|
|
|
|
# def test_inherit_inline(self):
|
|
# parent = Model('Person', {
|
|
# 'name': fields.String,
|
|
# 'age': fields.Integer,
|
|
# })
|
|
#
|
|
# child = self.api.inherit('Child', parent, {
|
|
# 'extra': fields.String,
|
|
# })
|
|
#
|
|
# Model('Output', {
|
|
# 'child': fields.Nested(child),
|
|
# 'children': fields.List(fields.Nested(child))
|
|
# })
|
|
#
|
|
# self.assertIn('Person', Models)
|
|
# self.assertIn('Child', Models)
|
|
|
|
def test_polymorph_inherit_common_ancestor(self):
|
|
class Child1:
|
|
pass
|
|
|
|
class Child2:
|
|
pass
|
|
|
|
parent = Model(
|
|
"Person",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
},
|
|
)
|
|
|
|
child1 = parent.inherit(
|
|
"Child1",
|
|
{
|
|
"extra1": fields.String,
|
|
},
|
|
)
|
|
|
|
child2 = parent.inherit(
|
|
"Child2",
|
|
{
|
|
"extra2": fields.String,
|
|
},
|
|
)
|
|
|
|
mapping = {
|
|
Child1: child1,
|
|
Child2: child2,
|
|
}
|
|
|
|
output = Model("Output", {"child": fields.Polymorph(mapping)})
|
|
|
|
# Should use the common ancestor
|
|
assert output.__schema__ == {
|
|
"properties": {
|
|
"child": {"$ref": "#/definitions/Person"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
def test_validate(self):
|
|
from jsonschema import FormatChecker
|
|
from werkzeug.exceptions import BadRequest
|
|
|
|
class IPAddress(fields.Raw):
|
|
__schema_type__ = "string"
|
|
__schema_format__ = "ipv4"
|
|
|
|
data = {"ip": "192.168.1"}
|
|
model = Model("MyModel", {"ip": IPAddress()})
|
|
|
|
# Test that validate without a FormatChecker does not check if a
|
|
# primitive type conforms to the defined format property
|
|
assert model.validate(data) is None
|
|
|
|
# Test that validate with a FormatChecker enforces the check of the
|
|
# format property and throws an error if invalid
|
|
with pytest.raises(BadRequest):
|
|
model.validate(data, format_checker=FormatChecker())
|
|
|
|
|
|
class ModelSchemaTestCase(object):
|
|
def test_model_schema(self):
|
|
address = SchemaModel(
|
|
"Address",
|
|
{
|
|
"properties": {
|
|
"road": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
},
|
|
)
|
|
|
|
person = SchemaModel(
|
|
"Person",
|
|
{
|
|
# 'required': ['address'],
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"address": {
|
|
"$ref": "#/definitions/Address",
|
|
},
|
|
},
|
|
"type": "object",
|
|
},
|
|
)
|
|
|
|
assert person.__schema__ == {
|
|
# 'required': ['address'],
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"address": {
|
|
"$ref": "#/definitions/Address",
|
|
},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
assert address.__schema__ == {
|
|
"properties": {
|
|
"road": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
}
|
|
|
|
|
|
class ModelDeprecattionsTest(object):
|
|
def test_extend_is_deprecated(self):
|
|
parent = Model(
|
|
"Parent",
|
|
{
|
|
"name": fields.String,
|
|
"age": fields.Integer,
|
|
"birthdate": fields.DateTime,
|
|
},
|
|
)
|
|
|
|
with pytest.warns(DeprecationWarning):
|
|
child = parent.extend(
|
|
"Child",
|
|
{
|
|
"extra": fields.String,
|
|
},
|
|
)
|
|
|
|
assert child.__schema__ == {
|
|
"properties": {
|
|
"name": {"type": "string"},
|
|
"age": {"type": "integer"},
|
|
"birthdate": {"type": "string", "format": "date-time"},
|
|
"extra": {"type": "string"},
|
|
},
|
|
"type": "object",
|
|
}
|