Tools to convert SQLAlchemy models to Pydantic models
Project description
Alchemista
Tools to generate Pydantic models from SQLAlchemy models.
Still experimental.
Installation
Alchemista is available in PyPI.
To install it with pip, run:
pip install alchemista
Usage
Simply call the model_from function with a SQLAlchemy model.
Each Column in its definition will result in an attribute of the generated model via the Pydantic Field function.
For example, a SQLAlchemy model like the following
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class PersonDB(Base):
__tablename__ = "people"
id = Column(Integer, primary_key=True)
age = Column(Integer, default=0, nullable=False, doc="Age in years")
name = Column(String(128), nullable=False, doc="Full name")
could have a generated Pydantic model via
from alchemista import model_from
Person = model_from(PersonDB)
and would result in a Pydantic model equivalent to
from pydantic import BaseModel, Field
class Person(BaseModel):
id: int
age: int = Field(0, description="Age in years")
name: str = Field(..., max_length=128, description="Full name")
class Config:
orm_mode = True
Note that the string length from the column definition was sufficient to add a max_length constraint.
Additionally, by default, the generated model will have orm_mode=True.
That can be customized via the __config__ keyword argument.
There is also an exclude keyword argument that accepts a set of field names to not include in the generated model,
and an include keyword argument accepts a set of field names to do include in the generated model.
However, they are mutually exclusive and cannot be used together.
This example is available in a short executable form in the examples/ directory.
Field arguments and info
Currently, the type, default value (either scalar or callable), and the description (from the doc attribute) are
extracted directly from the Column definition.
However, except for the type, all of them can be overridden via the info dictionary attribute.
All other custom arguments to the Field function are specified there too.
The supported keys are listed in alchemista.field.Info.
Everything specified in info is preferred from what has been extracted from Column.
This means that the default value and the description can be overridden if so desired.
Also, similarly to using Pydantic directly, default and default_factory are mutually-exclusive,
so they cannot be used together.
Use default_factory if the default value comes from calling a function (without any arguments).
For example, in the case above,
name = Column(String(128), nullable=False, doc="Full name", info=dict(description=None, max_length=64))
would instead result in
name: str = Field(..., max_length=64)
fields_from and model_from
The fields_from function is the function that actually inspects the SQLAlchemy model and builds a dictionary
in a format that can be used to generate a Pydantic model.
So model_from is just a shortcut for calling fields_from and then pydantic.create_model.
The model name that model_from sets is db_model.__name__.
If desired, or extra control is needed, pydantic.create_model can be used directly, in conjunction with fields_from.
This allows the customization of the name of the model that will be created and the specification of other
create_model arguments, like __base__ and __validators__ (model_from currently only accepts __config__).
For example:
from alchemista import fields_from
from pydantic import create_model
MyModel = create_model("MyModel", **fields_from(DBModel))
transform
Both fields_from and model_from have a transform argument.
It is a callable used to transform the fields before generating a Pydantic model.
The provided transformation functions are in the func module.
By default, the transform argument is set to func.unchanged, which, as the name implies, does nothing.
The other provided transformation function is func.nonify, which makes all fields optional (if they weren't already)
and nullable and sets the default value to None.
This is useful when some kind of "input model" is desired.
For example, the database model might have an auto-generated primary key, and some other columns with default values.
When updating an entity of this model, one wouldn't want to receive the primary key as an update candidate (which can
be solved using the exclude argument) and probably wouldn't want the fields with default values to actually have
these values, since the update is meant to change only what the user asked to update.
For example:
from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import declarative_base
from alchemista import model_from
from alchemista.func import nonify
Base = declarative_base()
class PersonDB(Base):
__tablename__ = "people"
id = Column(Integer, primary_key=True)
age = Column(Integer, default=0, nullable=False, doc="Age in years")
name = Column(String(128), nullable=False, doc="Full name")
PersonInput = model_from(PersonDB, exclude={"id"}, transform=nonify)
The model PersonInput is equivalent to the hand-written version below:
from typing import Optional
from pydantic import BaseModel, Field
class PersonInput(BaseModel):
age: Optional[int] = Field(None, description="Age in years")
name: Optional[str] = Field(None, max_length=128, description="Full name")
class Config:
orm_mode = True
Note that both age and name weren't originally nullable (Optional, in Python) and the name was required (age
wasn't because it has a default value).
Now, both fields are nullable and their default value is None.
User-defined transformations
You can also create your own transformation functions. The expected signature is as follows:
from typing import Tuple
from pydantic.fields import FieldInfo
def transformation(name: str, python_type: type, field: FieldInfo) -> Tuple[type, FieldInfo]:
pass
Where name is the name of the field currently being created, python_type is its Python type, and field is its full
Pydantic field specification.
The return type is a tuple of the Python type and the field specification.
These two can be changed freely (the name can't).
License
This project is licensed under the terms of the MIT license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file alchemista-0.3.0.tar.gz.
File metadata
- Download URL: alchemista-0.3.0.tar.gz
- Upload date:
- Size: 9.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.1 importlib_metadata/4.3.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.61.0 CPython/3.8.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b08e5f6597f1ca108aff4bffba050d213ebab52c96642078fa155442948f17d
|
|
| MD5 |
4189e866a1e29c7c453bb9b1fd76575c
|
|
| BLAKE2b-256 |
aa8c631aa6fcf7bf81a76e4288bc571e36af4b696b9d31a9edc842c6565638cf
|
File details
Details for the file alchemista-0.3.0-py3-none-any.whl.
File metadata
- Download URL: alchemista-0.3.0-py3-none-any.whl
- Upload date:
- Size: 8.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.1 importlib_metadata/4.3.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.61.0 CPython/3.8.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
655176386261f8c06552977e35c0efdf1bb61e25372917f1bb5e765908cbd0af
|
|
| MD5 |
922ae06ea6d028af8d91401355c68a0a
|
|
| BLAKE2b-256 |
2987324ba589dfe9ee09424aff900736f972bfd9ec5e42f1ca47051cd900988f
|