OpenAlchemy

OpenAlchemy translates OpenAPI schemas to SQLAlchemy models to reduce duplication when defining your API and database models. Any required additions to the OpenAPI specification are compliant with the OpenAPI standard.

See also

Online Editor

Online editor including schema validation.

Getting Started

If you have the following OpenAPI specification:

 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
openapi: "3.0.0"

info:
  title: Test Schema
  description: API to illustrate OpenAlchemy MVP.
  version: "0.1"

paths:
  /employee:
    get:
      summary: Used to retrieve all employees.
      responses:
        200:
          description: Return all employees from the database.
          content:
            application/json:
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/Employee"

components:
  schemas:
    Employee:
      description: Person that works for a company.
      type: object
      x-tablename: employee
      properties:
        id:
          type: integer
          description: Unique identifier for the employee.
          example: 0
          x-primary-key: true
          x-autoincrement: true
        name:
          type: string
          description: The name of the employee.
          example: David Andersson
          x-index: true
        division:
          type: string
          description: The part of the company the employee works in.
          example: Engineering
          x-index: true
        salary:
          type: number
          description: The amount of money the employee is paid.
          example: 1000000.00
      required:
        - name
        - division

To use SQLAlchemy to retrieve Employees from a database you need the following models.py file:

1
2
3
from open_alchemy import init_yaml

init_yaml("example-spec.yml", models_filename="models_auto.py")

The Base for the SQLAlchemy models and the Employee model is now available from open_alchemy.models:

from open_alchemy.models import Base
from open_alchemy.models import Employee

Interfaces

The most user friendly interfaces are the init_yaml and init_json interfaces. The init_model_factory interface is a lower level interface which has fewer dependencies but is not as user friendly.

init_yaml

Used to initialize the SQLAlchemy models based on a YAML OpenAPI specification which has been extended with any relevant OpenAlchemy extension properties.

The init_yaml interface requires the PyYAML library to be installed. The init_yaml interface accepts the following arguments:

  • spec_filename: The name of the file as a positional argument. The file must by a YAML file.

  • base: The SQLAlchemy declarative base as an optional keyword only argument. It is used to as the base class for all SQLAlchemy models. If it is not passed in, a new declarative base is constructed.

  • models_filename: The name of the file where the SQLAlchemy models will be written as an optional keyword only argument.

  • spec_path: The path to the OpenAPI specification (what would need to be passed to the open function to read the file) as an optional keyword only argument. Used to support remote references.

Note

the define_all parameter has been removed and OpenAlchemy behaves as though it is set to True.

The return value is a tuple consisting of:

  • Base: The SQLAlchemy declarative based used for the models. It is also importable: from open_alchemy.models import Base.

  • model_factory: The factory that can be used to construct the SQLAlchemy models using the name of the schema in the OpenAPI specification. All constructed models are added to the open_alchemy.models module and are importable. For example: from open_alchemy.models import Employee.

init_json

The init_json interface is similar to the init_yaml interface except that spec_filename must be a JSON file and PyYAML is not a required dependency.

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.

To build models from the command line, run:

openalchemy build openapi.yml simple dist

This interface is described in details in the build_yaml section of the Advanced chapter.

build_json

The build_json interface is similar to the build_yaml interface except that spec_filename must be a JSON file and PyYAML is not a required dependency.

Models File

Optionally, model definitions can be persisted to disk, mainly for type hinting and IDE auto-completion.

To discover the internal details of the models file, refer to the Models File section in the Advanced chapter.

Alembic

The standard method for automatically generating database migrations for alembic is supported. The following instructions show how to get started:

# Alembic Interoperability

The following steps were used to generate the migration.

1. Run `alembic init alembic`
2. Modify [alemnbic.ini](alembic.ini) `driver://user:pass@localhost/dbname`
   to `sqlite:///:memory:` to use an in-memory SQLite database.
3. Modify [env.py](alembic/env.py) by adding `import open_alchemy` at the
   bottom of the import section and
   `open_alchemy.init_yaml(spec_filename="../app/api.yaml")` below that to load
   the SQLAlchemy models for the example [app](../app).
4. Modify [env.py](alembic/env.py) by changing `target_metadata = None` to
   `target_metadata = open_alchemy.models.Base.metadata`
5. Run `alembic revision --autogenerate -m "Add employee table"` to generate
   the [revision](alembic/versions)

The below shows you the diff from the default code generated by alembic:

```diff
diff --git a/alembic.ini b/alembic.ini
index bfcc3c7..f65a55d 100644
--- a/alembic.ini
+++ b/alembic.ini
@@ -35,7 +35,7 @@ script_location = alembic
 # are written from script.py.mako
 # output_encoding = utf-8

-sqlalchemy.url = driver://user:pass@localhost/dbname
+sqlalchemy.url = sqlite:///:memory:


 [post_write_hooks]
diff --git a/alembic/env.py b/alembic/env.py
index c58b79f..71f8edf 100644
--- a/alembic/env.py
+++ b/alembic/env.py
@@ -4,6 +4,10 @@ from alembic import context
 from sqlalchemy import engine_from_config
 from sqlalchemy import pool

+import open_alchemy
+
+open_alchemy.init_yaml(spec_filename="../app/api.yaml")
+
 # this is the Alembic Config object, which provides
 # access to the values within the .ini file in use.
 config = context.config
@@ -16,7 +20,7 @@ fileConfig(config.config_file_name)
 # for 'autogenerate' support
 # from myapp import mymodel
 # target_metadata = mymodel.Base.metadata
-target_metadata = None
+target_metadata = open_alchemy.models.Base.metadata

 # other values from the config, defined by the needs of env.py,
 # can be acquired:
```

How Does It Work?

Helped by a series of extension properties, OpenAlchemy turns OpenAPI schemas into SQL entities.

To understand how all this works under the hood, refer to How Does It Work? section in the Advanced chapter.