Advanced¶
init_model_factory
¶
The init_model_factory
interface is less user friendly but perhaps of
interest to advanced users. It accepts the specification in dictionary format
(so it has fewer dependencies than init_yaml and init_json) and
does not construct a declarative base. It accepts the following parameters:
base
: The SQLAlchemy declarative base as a keyword only argument. It is used to as the base class for all SQLAlchemy models.spec
: The OpenAPI specification as a dictionary as a keyword only argument.models_filename
: The name of the file where the SQLAlchemy models will be written as an optional keyword only argument.
Note
the define_all
parameter has been removed and OpenAlchemy
behaves as though it is set to True
.
The return value is the model_factory
as defined as part of the return
value of init_yaml.
build_yaml
¶
Used to build a package with the SQLAlchemy models (including type hints) based on a YAML OpenAPI specification which has been extended with any relevant OpenAlchemy extension properties.
The build_yaml
interface requires the PyYAML
library to be
installed. The build_yaml
interface accepts the following arguments:
spec_filename
: The name of the OpenAPI specification file. The file must be a YAML file.package_name
: The name of the package to be produced. This will bethe name of the package if it is uploaded to a package index and
the name that is used to import the models after they have been installed).
dist_path
: The directory to output the package files.format_
: Optionally, the format of the package to build:PackageFormat.NONE
: no package is built (default)PackageFormat.SDIST
: a sdist package is builtPackageFormat.WHEEL
: a wheel package is built
The formats can be combined with the bitwise operator “or” (
|
), for instance, building both sdist and wheel packages can be specified like this:format_=PackageFormat.SDIST|PackageFormat.WHEEL
Warning
In order to build a wheel distributable archive, the wheel package is necessary.
It can be installed separately with
pip install wheel
, or with OpenAlchemy directly withpip install OpenAlchemy[wheel]
.
See also
- Package Service
Service that creates pip installable models from your OpenAPI specification.
Models File¶
OpenAlchemy
can optionally generate a file with all the
SQLAlchemy
models. Each model is constructed based on the
OpenApi
schema. The class inherits from the SQLAlchemy
model
defined on open_alchemy.models
. The generated classes contain type
information only, they do not provide any additional functionality on top of
what the SQLAlchemy
model provides. They are primarily
used to enable IDE auto-complete and type hint support. The models can be used
in the same way as the models that can be imported from
open_alchemy.models
and provide the full functionality of
SQLAlchemy
models.
The following is a sample file generated for the above example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | """Autogenerated SQLAlchemy models based on OpenAlchemy models.""" # pylint: disable=no-member,super-init-not-called,unused-argument import typing import sqlalchemy from sqlalchemy import orm from open_alchemy import models Base = models.Base # type: ignore class _EmployeeDictBase(typing.TypedDict, total=True): """TypedDict for properties that are required.""" name: str division: str class EmployeeDict(_EmployeeDictBase, total=False): """TypedDict for properties that are not required.""" id: int salary: typing.Optional[float] class TEmployee(typing.Protocol): """ SQLAlchemy model protocol. Person that works for a company. Attrs: id: Unique identifier for the employee. name: The name of the employee. division: The part of the company the employee works in. salary: The amount of money the employee is paid. """ # SQLAlchemy properties __table__: sqlalchemy.Table __tablename__: str query: orm.Query # Model properties id: "sqlalchemy.Column[int]" name: "sqlalchemy.Column[str]" division: "sqlalchemy.Column[str]" salary: "sqlalchemy.Column[typing.Optional[float]]" def __init__( self, name: str, division: str, id: typing.Optional[int] = None, salary: typing.Optional[float] = None, ) -> None: """ Construct. Args: id: Unique identifier for the employee. name: The name of the employee. division: The part of the company the employee works in. salary: The amount of money the employee is paid. """ ... @classmethod def from_dict( cls, name: str, division: str, id: typing.Optional[int] = None, salary: typing.Optional[float] = None, ) -> "TEmployee": """ Construct from a dictionary (eg. a POST payload). Args: id: Unique identifier for the employee. name: The name of the employee. division: The part of the company the employee works in. salary: The amount of money the employee is paid. Returns: Model instance based on the dictionary. """ ... @classmethod def from_str(cls, value: str) -> "TEmployee": """ Construct from a JSON string (eg. a POST payload). Returns: Model instance based on the JSON string. """ ... def to_dict(self) -> EmployeeDict: """ Convert to a dictionary (eg. to send back for a GET request). Returns: Dictionary based on the model instance. """ ... def to_str(self) -> str: """ Convert to a JSON string (eg. to send back for a GET request). Returns: JSON string based on the model instance. """ ... Employee: typing.Type[TEmployee] = models.Employee # type: ignore |
The following information is recorded in the models file:
The name and type of each property of a schema.
The from_dict and to_dict function signatures, including the type of the arguments and return values.
The from_str and to_str function signatures, including the type of the arguments and return values.
The properties created on instance objects due to any Backref.
Special SQLAlchemy properties for interacting with the database.
The object and property descriptions from the OpenAPI specification in the class and function docstrings.
Note
To be able to add relationships created by x-backrefs
to the
type annotations of the models file, the schema stored alongside a model,
which is accessible at the _schema
class variable (not a public
interface so it should not be used or relied upon), will use the
x-backrefs
extension property to record the schema for all back
references for the model. x-backrefs
is not a public interface and
should not be relied upon as it is subject to change.
It is worth noting that the auto-generated models cannot be imported directly.
The generated models are added to the open_alchemy.models
package by the
init_*
functions.
1 2 3 4 5 | from open_alchemy import init_yaml from open_alchemy import models init_yaml('openapi.yml') employee = models.Employee(name="David Andersson", division="Engineering") |
To use the models in another project, refer to the build_yaml or build_json functions.
Model Utilities¶
There are a few common utility functions that are added to the models. The from_dict utility function constructs a model instance from a dictionary. The to_dict function converts a model instance to a dictionary.
from_dict
¶
The from_dict
function is available on all constructed models. It
accepts a dictionary and constructs a model instance based on the dictionary.
It is similar to Employee(**employee_dict)
with a few advantages:
The dictionary based on which the model is constructed is checked against the schema used to define the model.
If the model includes a relationship, the relationship is constructed recursively.
For example:
>>> employee_dict = {
"id": 1,
"name": "David Andersson",
"division": "engineering",
"salary": 1000000,
}
>>> employee = Employee.from_dict(**employee_dict)
>>> employee.name
'David Andersson'
Note
To be able to support relationships, the schema stored alongside a
model, which is accessible at the _schema
class variable (not a
public interface so it should not be used or relied upon), won’t store the
actual schema for the referenced object. Instead, the object
type
is noted for the property alongside the x-de-$ref
extension
property which stores the name of the referenced model.
from_str
¶
The from_str
function is available on all constructed models. It
accepts a JSON formatted string and constructs a model instance by
de-serializing the JSON string and then using from_dict. For example:
>>> employee_str = '''{
"id": 1,
"name": "David Andersson",
"division": "engineering",
"salary": 1000000
}'''
>>> employee = Employee.from_str(employee_str)
>>> employee.name
'David Andersson'
to_dict
¶
The to_dict
function is available on all constructed models. It
converts a model instance into a dictionary based on the schema that was used
to define the model. If the model includes a relationship, the to_dict
function is called recursively on the relationship.
For example:
>>> employee_dict = {
"id": 1,
"name": "David Andersson",
"division": "engineering",
"salary": 1000000,
}
>>> employee = Employee.from_dict(**employee_dict)
>>> employee.to_dict()
{'id': 1, 'name': 'David Andersson', 'division': 'engineering', 'salary': 1000000}
to_str
¶
The to_str
function is available on all constructed models. It converts
a model instance into a JSON formatted string by serializing the output of
to_dict.
For example:
>>> employee_str = '''{
"id": 1,
"name": "David Andersson",
"division": "engineering",
"salary": 1000000
}'''
>>> employee = Employee.from_str(employee_str)
>>> employee.to_str()
'{"id": 1, "name": "David Andersson", "division": "engineering", "salary": 1000000}'
__str__
¶
It is possible to convert any model instance to a string using the
str
function. This is supported as there is a __str__
alias
for the to_str function.
__repr__
¶
Each model includes a __repr__
implementation to support calling
repr
in any model instance. The returned string is the source code
required to construct an equivalent model instance.
For example:
>>> employee_dict = {
"id": 1,
"name": "David Andersson",
"division": "engineering",
"salary": 1000000,
}
>>> employee = Employee.from_dict(**employee_dict)
>>> repr(employee)
"open_alchemy.models.Employee(id=1, name='David Andersson', division='engineering', salary=1000000)"
Extension Property Prefix¶
OpenAlchemy currently supports 2 extension property prefixes. The shorter
x-
and the longer x-open-alchemy-
. Both prefixes behave in the
same way. The longer prefix is offered to avoid extension property name clashes
with other tools.
For example, the tablename can be specified either using the
x-tablename
or the x-open-alchemy-tablename
extension property.
How Does It Work?¶
Given a name for a schema, OpenAlchemy
looks for that schema in the
schemas section of the specification. The schema must have the
x-tablename
property which defines the name of the table. The schema is
required to be an object
. For each property
of the schema, a
column is generated for the table mapping OpenAPI types to equivalent
SQLAlchemy types.
On top of the information in the OpenAPI specification, certain extension properties are used to define the database schema. The following specification defines the format and provides a description for each of the supported extension properties.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | { "x-backref": { "description": "Add a back reference to a relationship.", "type": "string" }, "x-uselist": { "description": "Turn a many to one into a one to one relationship.", "type": "boolean" }, "x-secondary": { "description": "Turn a one to many into a many to many relationship. The value of x-secondary is used as the name of the association table.", "type": "string" }, "x-primary-key": { "description": "Make a column a primary key.", "type": "boolean" }, "x-autoincrement": { "description": "Make an integer primary key column auto increment.", "type": "boolean" }, "x-index": { "description": "Add index to a column.", "type": "boolean" }, "x-composite-index": { "description": "Add composite index to a table.", "$ref": "#/CompositeIndex" }, "x-unique": { "description": "Add unique constraint to a column.", "type": "boolean" }, "x-composite-unique": { "description": "Add composite unique constraint to a table.", "$ref": "#/CompositeUnique" }, "x-json": { "description": "Treat the property as a JSON object rather than a particular type.", "type": "boolean" }, "x-foreign-key": { "description": "Add a foreign key constraint to a column. Must have the format \"<table name>.<column name>\".", "type": "string", "pattern": "^.+\\..+$" }, "x-foreign-key-column": { "description": "Customize the column used for the foreign key constraint of a relationship.", "type": "string" }, "x-foreign-key-kwargs": { "description": "Define kwargs to be passed to the foreign key constructor.", "type": "object", "additionalProperties": true }, "x-server-default": { "description": "Get the server to calculate a default value.", "type": "string" }, "x-tablename": { "description": "Define the name of a table.", "type": "string" }, "x-schema-name": { "description": "Define the name of a schema the table is on.", "type": "string" }, "x-inherits": { "description": "Define that a schema inherits from another schema or specify which schema to inherit from.", "oneOf": [{ "type": "string" }, { "type": "boolean" }] }, "x-kwargs": { "description": "Define kwargs to be passed to a function based on the context.", "type": "object", "additionalProperties": true }, "x-mixins": { "description": "The import path for a mixin class to be added as a parent for a model.", "$ref": "#/Mixins" }, "x-backrefs": { "description": "INTERNAL USE ONLY: Note on a schema that a back reference was created.", "$ref": "#/BackRef" }, "x-de-$ref": { "description": "INTERNAL USE ONLY: Track the name of a constructed model for a property referencing an object.", "type": "string" }, "x-dict-ignore": { "description": "INTERNAL USE ONLY: Do not add to the schema record used by from_- and to_dict functions.", "type": "boolean" }, "x-generated": { "description": "INTERNAL USE ONLY: The property is generated automatically (eg. auto incremented or has a default value) which is used to help determine whether the column is nullable.", "type": "boolean" } } |
To find out more about an extension property, go to the following section of the documentation:
property |
documentation section |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The SQLAlchemy Base
and any constructed database models are dynamically
added to the models
module that is available from OpenAlchemy.
Pylint¶
The auto-generated models file don’t play nice with the pylint rule no-member (E1101) because the models are generated in code which is not currently supported by pylint. To avoid spurious pylint errors add the following line to your .pylintrc file in the [IMPORTS] section:
generated-members=models.*