Advanced Tutorials¶
This section looks at some more complicated use cases in statham
.
Updating Generated Models Against Schema Changes¶
In the Quickstart Tutorial we looked at how to integrate an external data source using statham
. This section looks at how statham
can help manage updates to that external source.
Let’s suppose there is a new version of the Polls API, containing a breaking change. We access their new schema and take a look:
{
"type": "object",
"required": ["question", "choices"],
"properties": {
"question": {"type": "string"},
"choices": {"type": "array", "items": {"$ref": "#/definitions/choice"}}
},
"definitions": {
"choice": {
"type": "object",
"required": ["text"],
"properties": {
"text": {"type": "string", "maxLength": 200},
"votes": {"type": "integer", "default": 0}
}
}
}
}
Note that there is a breaking change, "choice_text"
has been renamed to "text"
! Our code is already using the models for the previous version like so:
from typing import List
from app.poll import Poll, Choice
def sort_choices(poll: Poll) -> List[str]:
"""Display the choices, ordered by votes descending."""
return [
choice.choice_text
for choice in sorted(
poll.choices,
key=lambda choice: -choice.votes,
)
]
Let’s re-run statham
to generate the new models:
$ statham --input schemas/v2/poll.json --output app/poll.py
We’ve now overwritten our models, and they should look something like this:
from typing import List
from statham.schema.elements import Array, Integer, Object, String
from statham.schema.property import Property
class Choice(Object):
text: str = Property(String(maxLength=200), required=True)
votes: int = Property(Integer(default=0))
class Poll(Object):
question: str = Property(String(maxLength=200), required=True)
choices: List[Choice] = Property(Array(Choice), required=True)
Now all we need to do is run mypy to find where that breaks our code:
$ mypy app
app/__main__.py:8: error: "Choice" has no attribute "choice_text"
Found 1 error in 1 file (checked 3 source files)
We now have an immediate progress bar on our work to integrate with the new API.
Note
If you are planning on extending the generated models, as shown in Quickstart Tutorial, then it’s a good idea to extend the generated models in sub-classes. This will ease the task of model generation.
Converting statham
Elements to JSON Schema¶
statham
includes some tools for converting schemas defined in statham
to raw JSON Schema. This allows you to use statham
for writing your schemas, whilst still exposing a standard schema externally. The simplest way to do this is:
>>> import json
>>> from statham.serializers import serialize_json
>>> from app.poll import Poll
>>>
>>> schema = serialize_json(Poll)
>>> print(json.dumps(schema, indent=2))
{
"properties": {
"question": {
"type": "string"
},
"choices": {
"items": {
"$ref": "#/definitions/Choice"
},
"type": "array"
}
},
"required": [
"question",
"choices"
],
"type": "object",
"title": "Poll",
"definitions": {
"Choice": {
"properties": {
"text": {
"maxLength": 200,
"type": "string"
},
"votes": {
"default": 0,
"type": "integer"
}
},
"required": [
"text"
],
"type": "object",
"title": "Choice"
}
}
}
By default, only Object
sub-elements are placed in the "definitions"
section. However, you can add more definitions manually:
>>> from statham.schema.elements import Integer
>>> schema = serialize_json(
... Poll, definitions={"Votes": Integer(default=0)}
... )
>>> print(json.dumps(schema, indent=2))
{
"properties": {
"question": {
"type": "string"
},
"choices": {
"items": {
"$ref": "#/definitions/Choice"
},
"type": "array"
}
},
"required": [
"question",
"choices"
],
"type": "object",
"title": "Poll",
"definitions": {
"Choice": {
"properties": {
"text": {
"maxLength": 200,
"type": "string"
},
"votes": {
"$ref": "#/definitions/Votes"
}
},
"required": [
"text"
],
"type": "object",
"title": "Choice"
},
"Votes": {
"default": 0,
"type": "integer"
}
}
}
Generating Other Types of Models¶
It may be desirable to generate other types of models (e.g. Django models) from JSON Schema. Whilst statham
doesn’t specifically cater for this, it is easier write code which generates from statham
elements than raw JSON Schema.
The orderer
function iterates dependent Object
sub-elements in a valid class declaration order:
>>> from statham.serializers.orderer import orderer
>>> list(orderer(Poll))
[Choice, Poll]
The get_children
function iterates all sub-elements of a schema element:
>>> from statham.serializers.orderer import get_children
>>> list(get_children(Poll))
[String(), Array(Choice), Choice, String(maxLength=200), Integer(default=0)]