Files
pydantic/tests/test_schema.py
T
Sebastián Ramírez 94706bc834 JSON Schema update/refactor/augment, to conform to spec (#308)
* Update schema tests to conform to JSON Schema spec

* Add JSON Schema tests for all supported types

including datetime and all supported Pydantic.types

* Add JSON Schema conforming schema sub module

* Update BaseModel to use schema module for JSON Schema generation

and update/simplify internal Schema methods

* Remove Schema code from Field class, replaced with JSON Schema module

* Add submodules to test model name generation for JSON Schemas

* Refactor/rewrite schema module to generate definions and refs

* Update and augment JSON Schema tests to include definitions and refs

and generation of a single JSON Schema with definitions from multiple (unrelated) models

* Add ref_prefix functionality to JSON Schema generation functions

* Test custom ref_prefix in JSON Schema generation

* Remove un-used BaseModel method, now refactored to schema module

* Update formating of test_schema

* Fix long lines in test_schema

* Fix imported but unused in fields

* Fix imported but unused in main.py

* Ignore imported but unused for testing modulec

* Refactor schema module for complexity

* Add conflicting name model to raise coverage

* Add conflicting model to test other flow and raise coverage

* Ignore complexity as destructuring more would make it more complex

and more difficult to understand, similar to .fields.validate

* Fix import sorting

* Update formatting with black, with CI settings

* Fix test for schemas with email validation

* Check if field is class before checking if is subclass

* Improve schema error when using unsuported types

* Add additional tests for corner cases, raise coverage to 100%

* Rename BaseModel.schema_json to schema_str (EAFP Python style)

* Add more tests to utils.display_as_type to increase the coverage for enums

* Remove unused catched error in schema tests

* Fix formatting with black

* Update docs schema example

* Add schema examples for top-level schema with multiple models

* Update docs, section Schema, with new JSON Schema generation details

* Update docs, history, with new features

* Update fields, remove unnecessary schema code for enums

* Update docs, fix links and typos in Schema section

* Trigger CI, as Python 3.7-dev seems to have random CI errors

* Revert Model.schema_str to Model.schema_json as requested

* Remove unnecessary assert in schema module as requested

* Remove annotations in internal functions, as requested

* Refactor get_flat_models_from_fields and reuse

* Use set short assignment syntax in schema module

* Remove unwanted assertion

* Make get_long_model_name a single line f-string

* Update model_name_map, add docstring and remove first return value

* Simplify dict operation in get_model_name_map as requested

* Make more concise model_name_map computation

* Remove bool from field check in schema as is subclass of int

* Make ref_prefix default to None and use global default

* Fix formatting for schema.py

* Refactor field_singleton_schema to use data structures

* Move main functions to top of schema, and add docstrings for them

* Implement __all__, move and order parts of schema

* Remove schema testing sub-package code as requested

* Generate schema testing subpackage in code

* Update schema tests with several related fields to use parametrized pytest

* Fix formatting and imports I missed after rebase

* Fix new formatting errors from CI

* Re-trigger Travis CI, Python 3.7-dev random error again, no re-run click in Travis for non owners

* Trigger annotation error with non-forward references

* Add docstrings for submodel schema

* tweaks and rewrite schema mapping table in python

* support complex defaults

* use str not int as dict keys

* Fix links to JSON Schema and OpenAPI
2018-11-22 16:00:06 +00:00

858 lines
24 KiB
Python

import os
import sys
import tempfile
from datetime import date, datetime, time, timedelta
from decimal import Decimal
from enum import Enum, IntEnum
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple, Union
from uuid import UUID
import pytest
from pydantic import BaseModel, Schema, ValidationError
from pydantic.schema import get_flat_models_from_model, get_flat_models_from_models, get_model_name_map, schema
from pydantic.types import (
DSN,
UUID1,
UUID3,
UUID4,
UUID5,
ConstrainedDecimal,
ConstrainedFloat,
ConstrainedInt,
ConstrainedStr,
DirectoryPath,
EmailStr,
FilePath,
Json,
NameEmail,
NegativeFloat,
NegativeInt,
NoneBytes,
NoneStr,
NoneStrBytes,
PositiveFloat,
PositiveInt,
PyObject,
StrBytes,
StrictStr,
UrlStr,
condecimal,
confloat,
conint,
constr,
urlstr,
)
try:
import email_validator
except ImportError:
email_validator = None
def test_key():
class ApplePie(BaseModel):
"""
This is a test.
"""
a: float
b: int = 10
s = {
'type': 'object',
'properties': {'a': {'type': 'number', 'title': 'A'}, 'b': {'type': 'integer', 'title': 'B', 'default': 10}},
'required': ['a'],
'title': 'ApplePie',
'description': 'This is a test.',
}
assert True not in ApplePie._schema_cache
assert False not in ApplePie._schema_cache
assert ApplePie.schema() == s
assert True in ApplePie._schema_cache
assert False not in ApplePie._schema_cache
assert ApplePie.schema() == s
def test_by_alias():
class ApplePie(BaseModel):
a: float
b: int = 10
class Config:
title = 'Apple Pie'
fields = {'a': 'Snap', 'b': 'Crackle'}
s = {
'type': 'object',
'title': 'Apple Pie',
'properties': {
'Snap': {'type': 'number', 'title': 'Snap'},
'Crackle': {'type': 'integer', 'title': 'Crackle', 'default': 10},
},
'required': ['Snap'],
}
assert ApplePie.schema() == s
assert list(ApplePie.schema(by_alias=True)['properties'].keys()) == ['Snap', 'Crackle']
assert list(ApplePie.schema(by_alias=False)['properties'].keys()) == ['a', 'b']
def test_sub_model():
class Foo(BaseModel):
"""hello"""
b: float
class Bar(BaseModel):
a: int
b: Foo = None
assert Bar.schema() == {
'type': 'object',
'title': 'Bar',
'definitions': {
'Foo': {
'type': 'object',
'title': 'Foo',
'description': 'hello',
'properties': {'b': {'type': 'number', 'title': 'B'}},
'required': ['b'],
}
},
'properties': {'a': {'type': 'integer', 'title': 'A'}, 'b': {'$ref': '#/definitions/Foo'}},
'required': ['a'],
}
def test_schema_class():
class Model(BaseModel):
foo: int = Schema(4, title='Foo is Great')
bar: str = Schema(..., description='this description of bar')
with pytest.raises(ValidationError):
Model()
m = Model(bar=123)
assert m.dict() == {'foo': 4, 'bar': '123'}
assert Model.schema() == {
'type': 'object',
'title': 'Model',
'properties': {
'foo': {'type': 'integer', 'title': 'Foo is Great', 'default': 4},
'bar': {'type': 'string', 'title': 'Bar', 'description': 'this description of bar'},
},
'required': ['bar'],
}
def test_schema_class_by_alias():
class Model(BaseModel):
foo: int = Schema(4, alias='foofoo')
assert list(Model.schema()['properties'].keys()) == ['foofoo']
assert list(Model.schema(by_alias=False)['properties'].keys()) == ['foo']
def test_choices():
FooEnum = Enum('FooEnum', {'foo': 'f', 'bar': 'b'})
BarEnum = IntEnum('BarEnum', {'foo': 1, 'bar': 2})
class SpamEnum(str, Enum):
foo = 'f'
bar = 'b'
class Model(BaseModel):
foo: FooEnum
bar: BarEnum
spam: SpamEnum = Schema(None)
assert Model.schema() == {
'type': 'object',
'title': 'Model',
'properties': {
'foo': {'title': 'Foo', 'enum': ['f', 'b']},
'bar': {'type': 'integer', 'title': 'Bar', 'enum': [1, 2]},
'spam': {'type': 'string', 'title': 'Spam', 'enum': ['f', 'b']},
},
'required': ['foo', 'bar'],
}
def test_json_schema():
class Model(BaseModel):
a = b'foobar'
b = Decimal('12.34')
assert Model.schema_json(indent=2) == (
'{\n'
' "title": "Model",\n'
' "type": "object",\n'
' "properties": {\n'
' "a": {\n'
' "title": "A",\n'
' "default": "foobar",\n'
' "type": "string",\n'
' "format": "binary"\n'
' },\n'
' "b": {\n'
' "title": "B",\n'
' "default": 12.34,\n'
' "type": "number"\n'
' }\n'
' }\n'
'}'
)
def test_list_sub_model():
class Foo(BaseModel):
a: float
class Bar(BaseModel):
b: List[Foo]
assert Bar.schema() == {
'title': 'Bar',
'type': 'object',
'definitions': {
'Foo': {
'title': 'Foo',
'type': 'object',
'properties': {'a': {'type': 'number', 'title': 'A'}},
'required': ['a'],
}
},
'properties': {'b': {'type': 'array', 'items': {'$ref': '#/definitions/Foo'}, 'title': 'B'}},
'required': ['b'],
}
def test_optional():
class Model(BaseModel):
a: Optional[str]
assert Model.schema() == {'title': 'Model', 'type': 'object', 'properties': {'a': {'type': 'string', 'title': 'A'}}}
def test_any():
class Model(BaseModel):
a: Any
assert Model.schema() == {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A'}},
'required': ['a'],
}
def test_set():
class Model(BaseModel):
a: Set[int]
assert Model.schema() == {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'array', 'uniqueItems': True, 'items': {'type': 'integer'}}},
'required': ['a'],
}
@pytest.mark.parametrize(
'field_type,expected_schema',
[
(
Tuple[str, int, Union[str, int, float], float],
[
{'type': 'string'},
{'type': 'integer'},
{'anyOf': [{'type': 'string'}, {'type': 'integer'}, {'type': 'number'}]},
{'type': 'number'},
],
),
(Tuple[str], {'type': 'string'}),
],
)
def test_tuple(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'array', 'items': None}},
'required': ['a'],
}
base_schema['properties']['a']['items'] = expected_schema
assert Model.schema() == base_schema
def test_bool():
class Model(BaseModel):
a: bool
assert Model.schema() == {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'boolean'}},
'required': ['a'],
}
class Foo(BaseModel):
a: float
@pytest.mark.parametrize(
'field_type,expected_schema',
[
(
Union[int, str],
{
'properties': {'a': {'title': 'A', 'anyOf': [{'type': 'integer'}, {'type': 'string'}]}},
'required': ['a'],
},
),
(
List[int],
{'properties': {'a': {'title': 'A', 'type': 'array', 'items': {'type': 'integer'}}}, 'required': ['a']},
),
(
Dict[str, Foo],
{
'definitions': {
'Foo': {
'title': 'Foo',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'number'}},
'required': ['a'],
}
},
'properties': {
'a': {'title': 'A', 'type': 'object', 'additionalProperties': {'$ref': '#/definitions/Foo'}}
},
'required': ['a'],
},
),
(
Union[None, Foo],
{
'definitions': {
'Foo': {
'title': 'Foo',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'number'}},
'required': ['a'],
}
},
'properties': {'a': {'$ref': '#/definitions/Foo'}},
},
),
(Dict[str, Any], {'properties': {'a': {'title': 'A', 'type': 'object'}}, 'required': ['a']}),
],
)
def test_list_union_dict(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {'title': 'Model', 'type': 'object'}
base_schema.update(expected_schema)
assert Model.schema() == base_schema
@pytest.mark.parametrize(
'field_type,expected_schema', [(datetime, 'date-time'), (date, 'date'), (time, 'time'), (timedelta, 'time-delta')]
)
def test_date_types(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'string', 'format': ''}},
'required': ['a'],
}
base_schema['properties']['a']['format'] = expected_schema
assert Model.schema() == base_schema
@pytest.mark.parametrize(
'field_type,expected_schema',
[
(NoneStr, {'properties': {'a': {'title': 'A', 'type': 'string'}}}),
(NoneBytes, {'properties': {'a': {'title': 'A', 'type': 'string', 'format': 'binary'}}}),
(
StrBytes,
{
'properties': {
'a': {'title': 'A', 'anyOf': [{'type': 'string'}, {'type': 'string', 'format': 'binary'}]}
},
'required': ['a'],
},
),
(
NoneStrBytes,
{
'properties': {
'a': {'title': 'A', 'anyOf': [{'type': 'string'}, {'type': 'string', 'format': 'binary'}]}
}
},
),
],
)
def test_str_basic_types(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {'title': 'Model', 'type': 'object'}
base_schema.update(expected_schema)
assert Model.schema() == base_schema
@pytest.mark.parametrize(
'field_type,expected_schema',
[
(StrictStr, {'title': 'A', 'type': 'string'}),
(ConstrainedStr, {'title': 'A', 'type': 'string'}),
(
constr(min_length=3, max_length=5, regex='^text$'),
{'title': 'A', 'type': 'string', 'minLength': 3, 'maxLength': 5, 'pattern': '^text$'},
),
],
)
def test_str_constrained_types(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {'title': 'Model', 'type': 'object', 'properties': {'a': {}}, 'required': ['a']}
base_schema['properties']['a'] = expected_schema
assert Model.schema() == base_schema
@pytest.mark.parametrize(
'field_type,expected_schema',
[
(UrlStr, {'title': 'A', 'type': 'string', 'format': 'uri', 'minLength': 1, 'maxLength': 2 ** 16}),
(
urlstr(min_length=5, max_length=10),
{'title': 'A', 'type': 'string', 'format': 'uri', 'minLength': 5, 'maxLength': 10},
),
(DSN, {'title': 'A', 'type': 'string', 'format': 'dsn'}),
],
)
def test_special_str_types(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {'title': 'Model', 'type': 'object', 'properties': {'a': {}}, 'required': ['a']}
base_schema['properties']['a'] = expected_schema
assert Model.schema() == base_schema
@pytest.mark.skipif(not email_validator, reason='email_validator not installed')
@pytest.mark.parametrize('field_type,expected_schema', [(EmailStr, 'email'), (NameEmail, 'name-email')])
def test_email_str_types(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'string'}},
'required': ['a'],
}
base_schema['properties']['a']['format'] = expected_schema
assert Model.schema() == base_schema
@pytest.mark.parametrize(
'field_type,expected_schema',
[
(ConstrainedInt, {}),
(conint(gt=5, lt=10), {'exclusiveMinimum': 5, 'exclusiveMaximum': 10}),
(conint(ge=5, le=10), {'minimum': 5, 'maximum': 10}),
(PositiveInt, {'exclusiveMinimum': 0}),
(NegativeInt, {'exclusiveMaximum': 0}),
],
)
def test_special_int_types(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'integer'}},
'required': ['a'],
}
base_schema['properties']['a'].update(expected_schema)
assert Model.schema() == base_schema
@pytest.mark.parametrize(
'field_type,expected_schema',
[
(ConstrainedFloat, {}),
(confloat(gt=5, lt=10), {'exclusiveMinimum': 5, 'exclusiveMaximum': 10}),
(confloat(ge=5, le=10), {'minimum': 5, 'maximum': 10}),
(PositiveFloat, {'exclusiveMinimum': 0}),
(NegativeFloat, {'exclusiveMaximum': 0}),
(ConstrainedDecimal, {}),
(condecimal(gt=5, lt=10), {'exclusiveMinimum': 5, 'exclusiveMaximum': 10}),
(condecimal(ge=5, le=10), {'minimum': 5, 'maximum': 10}),
],
)
def test_special_float_types(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'number'}},
'required': ['a'],
}
base_schema['properties']['a'].update(expected_schema)
assert Model.schema() == base_schema
@pytest.mark.parametrize(
'field_type,expected_schema',
[(UUID, 'uuid'), (UUID1, 'uuid1'), (UUID3, 'uuid3'), (UUID4, 'uuid4'), (UUID5, 'uuid5')],
)
def test_uuid_types(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'string', 'format': ''}},
'required': ['a'],
}
base_schema['properties']['a']['format'] = expected_schema
assert Model.schema() == base_schema
@pytest.mark.parametrize(
'field_type,expected_schema', [(FilePath, 'file-path'), (DirectoryPath, 'directory-path'), (Path, 'path')]
)
def test_path_types(field_type, expected_schema):
class Model(BaseModel):
a: field_type
base_schema = {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'string', 'format': ''}},
'required': ['a'],
}
base_schema['properties']['a']['format'] = expected_schema
assert Model.schema() == base_schema
def test_json_type():
class Model(BaseModel):
a: Json
model_schema = Model.schema()
assert model_schema == {
'title': 'Model',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'string', 'format': 'json-string'}},
'required': ['a'],
}
def test_error_non_supported_types():
class Model(BaseModel):
a: PyObject
with pytest.raises(ValueError):
Model.schema()
def create_testing_submodules():
base_path = Path(tempfile.mkdtemp())
mod_root_path = base_path / 'pydantic_schema_test'
os.makedirs(mod_root_path, exist_ok=True)
open(mod_root_path / '__init__.py', 'w').close()
for mod in ['a', 'b', 'c']:
module_name = 'module' + mod
model_name = 'model' + mod + '.py'
os.makedirs(mod_root_path / module_name, exist_ok=True)
open(mod_root_path / module_name / '__init__.py', 'w').close()
with open(mod_root_path / module_name / model_name, 'w') as f:
f.write('from pydantic import BaseModel\n' 'class Model(BaseModel):\n' ' a: str\n')
module_name = 'moduled'
model_name = 'modeld.py'
os.makedirs(mod_root_path / module_name, exist_ok=True)
open(mod_root_path / module_name / '__init__.py', 'w').close()
with open(mod_root_path / module_name / model_name, 'w') as f:
f.write('from ..moduleb.modelb import Model')
sys.path.insert(0, str(base_path))
def test_flat_models_unique_models():
create_testing_submodules()
from pydantic_schema_test.modulea.modela import Model as ModelA
from pydantic_schema_test.moduleb.modelb import Model as ModelB
from pydantic_schema_test.moduled.modeld import Model as ModelD
flat_models = get_flat_models_from_models([ModelA, ModelB, ModelD])
assert flat_models == set([ModelA, ModelB])
def test_flat_models_with_submodels():
class Foo(BaseModel):
a: str
class Bar(BaseModel):
b: List[Foo]
class Baz(BaseModel):
c: Dict[str, Bar]
flat_models = get_flat_models_from_model(Baz)
assert flat_models == set([Foo, Bar, Baz])
def test_flat_models_with_submodels_from_sequence():
class Foo(BaseModel):
a: str
class Bar(BaseModel):
b: Foo
class Ingredient(BaseModel):
name: str
class Pizza(BaseModel):
name: str
ingredients: List[Ingredient]
flat_models = get_flat_models_from_models([Bar, Pizza])
assert flat_models == set([Foo, Bar, Ingredient, Pizza])
def test_model_name_maps():
create_testing_submodules()
from pydantic_schema_test.modulea.modela import Model as ModelA
from pydantic_schema_test.moduleb.modelb import Model as ModelB
from pydantic_schema_test.modulec.modelc import Model as ModelC
from pydantic_schema_test.moduled.modeld import Model as ModelD
class Foo(BaseModel):
a: str
class Bar(BaseModel):
b: Foo
class Baz(BaseModel):
c: Bar
flat_models = get_flat_models_from_models([Baz, ModelA, ModelB, ModelC, ModelD])
model_name_map = get_model_name_map(flat_models)
assert model_name_map == {
Foo: 'Foo',
Bar: 'Bar',
Baz: 'Baz',
ModelA: 'pydantic_schema_test__modulea__modela__Model',
ModelB: 'pydantic_schema_test__moduleb__modelb__Model',
ModelC: 'pydantic_schema_test__modulec__modelc__Model',
}
def test_schema_overrides():
class Foo(BaseModel):
a: str
class Bar(BaseModel):
b: Foo = Foo(a='foo')
class Baz(BaseModel):
c: Optional[Bar]
class Model(BaseModel):
d: Baz
model_schema = Model.schema()
assert model_schema == {
'title': 'Model',
'type': 'object',
'definitions': {
'Bar': {
'title': 'Bar',
'type': 'object',
'properties': {
'b': {
'title': 'Foo',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'string'}},
'required': ['a'],
'default': {'a': 'foo'},
}
},
},
'Baz': {'title': 'Baz', 'type': 'object', 'properties': {'c': {'$ref': '#/definitions/Bar'}}},
},
'properties': {'d': {'$ref': '#/definitions/Baz'}},
'required': ['d'],
}
def test_schema_from_models():
class Foo(BaseModel):
a: str
class Bar(BaseModel):
b: Foo
class Baz(BaseModel):
c: Bar
class Model(BaseModel):
d: Baz
class Ingredient(BaseModel):
name: str
class Pizza(BaseModel):
name: str
ingredients: List[Ingredient]
model_schema = schema(
[Model, Pizza], title='Multi-model schema', description='Single JSON Schema with multiple definitions'
)
assert model_schema == {
'title': 'Multi-model schema',
'description': 'Single JSON Schema with multiple definitions',
'definitions': {
'Pizza': {
'title': 'Pizza',
'type': 'object',
'properties': {
'name': {'title': 'Name', 'type': 'string'},
'ingredients': {
'title': 'Ingredients',
'type': 'array',
'items': {'$ref': '#/definitions/Ingredient'},
},
},
'required': ['name', 'ingredients'],
},
'Ingredient': {
'title': 'Ingredient',
'type': 'object',
'properties': {'name': {'title': 'Name', 'type': 'string'}},
'required': ['name'],
},
'Model': {
'title': 'Model',
'type': 'object',
'properties': {'d': {'$ref': '#/definitions/Baz'}},
'required': ['d'],
},
'Baz': {
'title': 'Baz',
'type': 'object',
'properties': {'c': {'$ref': '#/definitions/Bar'}},
'required': ['c'],
},
'Bar': {
'title': 'Bar',
'type': 'object',
'properties': {'b': {'$ref': '#/definitions/Foo'}},
'required': ['b'],
},
'Foo': {
'title': 'Foo',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'string'}},
'required': ['a'],
},
},
}
def test_schema_with_ref_prefix():
class Foo(BaseModel):
a: str
class Bar(BaseModel):
b: Foo
class Baz(BaseModel):
c: Bar
model_schema = schema([Bar, Baz], ref_prefix='#/components/schemas/') # OpenAPI style
assert model_schema == {
'definitions': {
'Baz': {
'title': 'Baz',
'type': 'object',
'properties': {'c': {'$ref': '#/components/schemas/Bar'}},
'required': ['c'],
},
'Bar': {
'title': 'Bar',
'type': 'object',
'properties': {'b': {'$ref': '#/components/schemas/Foo'}},
'required': ['b'],
},
'Foo': {
'title': 'Foo',
'type': 'object',
'properties': {'a': {'title': 'A', 'type': 'string'}},
'required': ['a'],
},
}
}
def test_schema_no_definitions():
model_schema = schema([], title='Schema without definitions')
assert model_schema == {'title': 'Schema without definitions'}
def test_list_default():
class UserModel(BaseModel):
friends: List[int] = [1]
assert UserModel.schema() == {
'title': 'UserModel',
'type': 'object',
'properties': {'friends': {'title': 'Friends', 'default': [1], 'type': 'array', 'items': {'type': 'integer'}}},
}
def test_dict_default():
class UserModel(BaseModel):
friends: Dict[str, float] = {'a': 1.1, 'b': 2.2}
assert UserModel.schema() == {
'title': 'UserModel',
'type': 'object',
'properties': {
'friends': {
'title': 'Friends',
'default': {'a': 1.1, 'b': 2.2},
'type': 'object',
'additionalProperties': {'type': 'number'},
}
},
}