Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON Schema update/refactor/augment, to conform to spec #308

Merged
merged 66 commits into from Nov 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
9e7c55f
Update schema tests to conform to JSON Schema spec
tiangolo Nov 17, 2018
e3196f6
Add JSON Schema tests for all supported types
tiangolo Nov 17, 2018
f905f83
Add JSON Schema conforming schema sub module
tiangolo Nov 17, 2018
a702d6d
Update BaseModel to use schema module for JSON Schema generation
tiangolo Nov 17, 2018
bc73427
Remove Schema code from Field class, replaced with JSON Schema module
tiangolo Nov 17, 2018
1312ca4
Add submodules to test model name generation for JSON Schemas
tiangolo Nov 17, 2018
18a2b25
Refactor/rewrite schema module to generate definions and refs
tiangolo Nov 17, 2018
c88a760
Update and augment JSON Schema tests to include definitions and refs
tiangolo Nov 17, 2018
b24bb29
Add ref_prefix functionality to JSON Schema generation functions
tiangolo Nov 17, 2018
580cc28
Test custom ref_prefix in JSON Schema generation
tiangolo Nov 17, 2018
0d18d7d
Remove un-used BaseModel method, now refactored to schema module
tiangolo Nov 17, 2018
1ec4aba
Update formating of test_schema
tiangolo Nov 17, 2018
115883b
Fix long lines in test_schema
tiangolo Nov 17, 2018
343b504
Fix imported but unused in fields
tiangolo Nov 17, 2018
0391d62
Fix imported but unused in main.py
tiangolo Nov 17, 2018
fcf34a4
Ignore imported but unused for testing modulec
tiangolo Nov 17, 2018
5c642cb
Refactor schema module for complexity
tiangolo Nov 17, 2018
56b377b
Add conflicting name model to raise coverage
tiangolo Nov 17, 2018
fd8ffc9
Add conflicting model to test other flow and raise coverage
tiangolo Nov 17, 2018
4529c3a
Ignore complexity as destructuring more would make it more complex
tiangolo Nov 17, 2018
a3055dc
Fix import sorting
tiangolo Nov 17, 2018
549a85d
Update formatting with black, with CI settings
tiangolo Nov 17, 2018
c7f1d02
Fix test for schemas with email validation
tiangolo Nov 18, 2018
be5b349
Check if field is class before checking if is subclass
tiangolo Nov 18, 2018
b3953e9
Improve schema error when using unsuported types
tiangolo Nov 18, 2018
1fb23cc
Add additional tests for corner cases, raise coverage to 100%
tiangolo Nov 18, 2018
de2b096
Rename BaseModel.schema_json to schema_str (EAFP Python style)
tiangolo Nov 18, 2018
409eb0c
Add more tests to utils.display_as_type to increase the coverage for …
tiangolo Nov 18, 2018
e306f4e
Remove unused catched error in schema tests
tiangolo Nov 18, 2018
1536c7e
Fix formatting with black
tiangolo Nov 18, 2018
da1e805
Update docs schema example
tiangolo Nov 18, 2018
0671f6a
Add schema examples for top-level schema with multiple models
tiangolo Nov 18, 2018
6eedbf8
Update docs, section Schema, with new JSON Schema generation details
tiangolo Nov 18, 2018
f63084f
Update docs, history, with new features
tiangolo Nov 18, 2018
f88624a
Update fields, remove unnecessary schema code for enums
tiangolo Nov 18, 2018
b368fa0
Update docs, fix links and typos in Schema section
tiangolo Nov 18, 2018
fb3b58d
Trigger CI, as Python 3.7-dev seems to have random CI errors
tiangolo Nov 18, 2018
b310578
Revert Model.schema_str to Model.schema_json as requested
tiangolo Nov 18, 2018
8f40116
Remove unnecessary assert in schema module as requested
tiangolo Nov 18, 2018
16ca546
Remove annotations in internal functions, as requested
tiangolo Nov 18, 2018
c9ff829
Refactor get_flat_models_from_fields and reuse
tiangolo Nov 18, 2018
9d38476
Use set short assignment syntax in schema module
tiangolo Nov 18, 2018
75963a3
Remove unwanted assertion
tiangolo Nov 18, 2018
b253ac8
Make get_long_model_name a single line f-string
tiangolo Nov 18, 2018
eae52f5
Update model_name_map, add docstring and remove first return value
tiangolo Nov 18, 2018
095aaa0
Simplify dict operation in get_model_name_map as requested
tiangolo Nov 18, 2018
8bc6fe7
Make more concise model_name_map computation
tiangolo Nov 18, 2018
6f0815f
Remove bool from field check in schema as is subclass of int
tiangolo Nov 18, 2018
e883bfe
Make ref_prefix default to None and use global default
tiangolo Nov 18, 2018
b3c4262
Fix formatting for schema.py
tiangolo Nov 18, 2018
edf2cc7
Refactor field_singleton_schema to use data structures
tiangolo Nov 18, 2018
ec03e22
Move main functions to top of schema, and add docstrings for them
tiangolo Nov 18, 2018
0a24236
Implement __all__, move and order parts of schema
tiangolo Nov 18, 2018
033670b
Remove schema testing sub-package code as requested
tiangolo Nov 18, 2018
e22a899
Generate schema testing subpackage in code
tiangolo Nov 18, 2018
a73818e
Update schema tests with several related fields to use parametrized p…
tiangolo Nov 18, 2018
d08eafb
Fix formatting and imports I missed after rebase
tiangolo Nov 18, 2018
1870bd2
Fix new formatting errors from CI
tiangolo Nov 18, 2018
66d9f23
Re-trigger Travis CI, Python 3.7-dev random error again, no re-run cl…
tiangolo Nov 18, 2018
b11196d
Trigger annotation error with non-forward references
tiangolo Nov 21, 2018
b714acc
Add docstrings for submodel schema
tiangolo Nov 21, 2018
1604f72
tweaks and rewrite schema mapping table in python
samuelcolvin Nov 22, 2018
1aef328
support complex defaults
samuelcolvin Nov 22, 2018
3ce82a6
use str not int as dict keys
samuelcolvin Nov 22, 2018
229dfaa
Fix links to JSON Schema and OpenAPI
tiangolo Nov 22, 2018
aa3854b
Merge branch 'samuelcolvin-json-schema-flat' into json-schema-flat
tiangolo Nov 22, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -12,6 +12,7 @@ htmlcov/
benchmarks/*.json
docs/_build/
docs/.TMP_HISTORY.rst
docs/.tmp_schema_mappings.rst
.pytest_cache/
.vscode/
_build/
6 changes: 6 additions & 0 deletions HISTORY.rst
Expand Up @@ -3,6 +3,12 @@
History
-------

v0.16.0 (2018-XX-XX)
....................

* refactor schema generation to be compatible with JSON Schema and OpenAPI specs, #308 by @tiangolo
* add ``schema`` to ``schema`` module to generate top-level schemas from base models, #308 by @tiangolo

v0.15.0 (2018-11-18)
....................
* move codebase to use black, #287 by @samuelcolvin
Expand Down
1 change: 1 addition & 0 deletions docs/Makefile
Expand Up @@ -14,6 +14,7 @@ clean:

.PHONY: html
html: clean
./schema_mapping.py
mkdir -p $(STATICDIR)
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
Expand Down
54 changes: 30 additions & 24 deletions docs/examples/schema1.json
@@ -1,42 +1,48 @@
{
"type": "object",
"title": "Main",
"description": "This is the description of the main model",
"type": "object",
"properties": {
"foo_bar": {
"type": "object",
"$ref": "#/definitions/FooBar"
},
"Gender": {
"title": "Gender",
"enum": [
"male",
"female",
"other",
"not_given"
],
"type": "string"
},
"snap": {
"title": "The Snap",
"default": 42,
"description": "this is the value of snap",
"type": "integer"
}
},
"required": [
"foo_bar"
],
"definitions": {
"FooBar": {
"title": "FooBar",
"type": "object",
"properties": {
"count": {
"type": "int",
"title": "Count",
"required": true
"type": "integer"
},
"size": {
"type": "float",
"title": "Size",
"required": false
"type": "number"
}
},
"required": true
},
"Gender": {
"type": "int",
"title": "Gender",
"required": false,
"choices": [
[1, "Male"],
[2, "Female"],
[3, "Other"],
[4, "I'd rather not say"]
"required": [
"count"
]
},
"snap": {
"type": "int",
"title": "The Snap",
"required": false,
"default": 42,
"description": "this is the value of snap"
}
}
}
15 changes: 7 additions & 8 deletions docs/examples/schema1.py
@@ -1,15 +1,15 @@
from enum import IntEnum
from enum import Enum
from pydantic import BaseModel, Schema

class FooBar(BaseModel):
count: int
size: float = None

class Gender(IntEnum):
male = 1
female = 2
other = 3
not_given = 4
class Gender(str, Enum):
male = 'male'
female = 'female'
other = 'other'
not_given = 'not_given'

class MainModel(BaseModel):
"""
Expand All @@ -19,12 +19,11 @@ class MainModel(BaseModel):
gender: Gender = Schema(
None,
alias='Gender',
choice_names={3: 'Other Gender', 4: "I'd rather not say"}
)
snap: int = Schema(
42,
title='The Snap',
description='this is the value of snap'
description='this is the value of snap',
)

class Config:
Expand Down
40 changes: 40 additions & 0 deletions docs/examples/schema2.json
@@ -0,0 +1,40 @@
{
"title": "My Schema",
"definitions": {
"Foo": {
"title": "Foo",
"type": "object",
"properties": {
"a": {
"title": "A",
"type": "string"
}
}
},
"Model": {
"title": "Model",
"type": "object",
"properties": {
"b": {
"$ref": "#/definitions/Foo"
}
},
"required": [
"b"
]
},
"Bar": {
"title": "Bar",
"type": "object",
"properties": {
"c": {
"title": "C",
"type": "integer"
}
},
"required": [
"c"
]
}
}
}
26 changes: 26 additions & 0 deletions docs/examples/schema2.py
@@ -0,0 +1,26 @@
import json
from pydantic import BaseModel
from pydantic.schema import schema


class Foo(BaseModel):
a: str = None


class Model(BaseModel):
b: Foo


class Bar(BaseModel):
c: int


top_level_schema = schema([Model, Bar], title='My Schema')
print(json.dumps(top_level_schema, indent=2))

# {
# "title": "My Schema",
# "definitions": {
# "Foo": {
# "title": "Foo",
# ...
29 changes: 29 additions & 0 deletions docs/examples/schema3.json
@@ -0,0 +1,29 @@
{
"definitions": {
"Foo": {
"title": "Foo",
"type": "object",
"properties": {
"a": {
"title": "A",
"type": "integer"
}
},
"required": [
"a"
]
},
"Model": {
"title": "Model",
"type": "object",
"properties": {
"a": {
"$ref": "#/components/schemas/Foo"
}
},
"required": [
"a"
]
}
}
}
20 changes: 20 additions & 0 deletions docs/examples/schema3.py
@@ -0,0 +1,20 @@
import json
from pydantic import BaseModel
from pydantic.schema import schema

class Foo(BaseModel):
a: int

class Model(BaseModel):
a: Foo


top_level_schema = schema([Model], ref_prefix='#/components/schemas/') # Default location for OpenAPI
print(json.dumps(top_level_schema, indent=2))

# {
# "definitions": {
# "Foo": {
# "title": "Foo",
# "type": "object",
# ...
61 changes: 52 additions & 9 deletions docs/index.rst
Expand Up @@ -219,32 +219,39 @@ The ellipsis ``...`` just means "Required" same as annotation only declarations
Schema Creation
...............

*Pydantic* allows auto creation of schemas from models:
*Pydantic* allows auto creation of JSON Schemas from models:

.. literalinclude:: examples/schema1.py

(This script is complete, it should run "as is")

Outputs:

.. literalinclude:: examples/schema1.json

(This script is complete, it should run "as is")
The generated schemas are compliant with the specifications:
`JSON Schema Core <https://json-schema.org/latest/json-schema-core.html>`__,
`JSON Schema Validation <https://json-schema.org/latest/json-schema-validation.html>`__ and
`OpenAPI <https://github.com/OAI/OpenAPI-Specification>`__.

`schema` will return a dict of the schema, while `schema_json` will return a JSON representation of that.
``BaseModel.schema`` will return a dict of the schema, while ``BaseModel.schema_json`` will return a JSON string
representation of that.

"submodels" are recursively included in the schema.
Sub-models used are added to the ``definitions`` JSON attribute and referenced, as per the spec.

The ``description`` for models is taken from the docstring of the class.
All sub-models (and their sub-models) schemas are put directly in a top-level ``definitions`` JSON key for easy re-use
and reference.

Enums are shown in the schema as choices, optionally the ``choice_names`` argument can be used
to provide human friendly descriptions for the choices. If ``choice_names`` is omitted or misses values,
descriptions will be generated by calling ``.title()`` on the name of the member.
"sub-models" with modifications (via the ``Schema`` class) like a custom title, description or default value,
are recursively included instead of referenced.

The ``description`` for models is taken from the docstring of the class.

Optionally the ``Schema`` class can be used to provide extra information about the field, arguments:

* ``default`` (positional argument), since the ``Schema`` is replacing the field's default, its first
argument is used to set the default, use ellipsis (``...``) to indicate the field is required
* ``title`` if omitted ``field_name.title()`` is used
* ``choice_names`` as described above
* ``alias`` - the public name of the field.
* ``**`` any other keyword arguments (eg. ``description``) will be added verbatim to the field's schema

Expand All @@ -254,6 +261,42 @@ to set all the arguments above except ``default``.
The schema is generated by default using aliases as keys, it can also be generated using model
property names not aliases with ``MainModel.schema/schema_json(by_alias=False)``.

Types, custom field types, and constraints (as ``max_length``) are mapped to the corresponding
`JSON Schema Core <http://json-schema.org/latest/json-schema-core.html#rfc.section.4.3.1>`__ spec format when there's
an equivalent available, next to `JSON Schema Validation <http://json-schema.org/latest/json-schema-validation.html>`__,
`OpenAPI Data Types <https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#data-types>`__
(which are based on JSON Schema), or otherwise use the standard ``format`` JSON field to define Pydantic extensions
for more complex ``string`` sub-types.

The field schema mapping from Python / Pydantic to JSON Schema is done as follows:

.. include:: .tmp_schema_mappings.rst


You can also generate a top-level JSON Schema that only includes a list of models and all their related
submodules in its ``definitions``:

.. literalinclude:: examples/schema2.py

(This script is complete, it should run "as is")

Outputs:

.. literalinclude:: examples/schema2.json

You can customize the generated ``$ref`` JSON location, the definitions will still be in the key ``definitions`` and you can still get them from there, but the references will point to your defined prefix instead of the default.

This is useful if you need to extend or modify JSON Schema default definitions location, e.g. with OpenAPI:

.. literalinclude:: examples/schema3.py

(This script is complete, it should run "as is")

Outputs:

.. literalinclude:: examples/schema3.json


Error Handling
..............

Expand Down