The answer is yes!
The create_model
function has the optional __base__
parameter (as mentioned in the docs), which accepts any subclass (or sequence of subclasses) of the Pydantic BaseModel
. The SQLModel
base class happens to directly inherit from BaseModel
and can thus be passed here.
However, this is not sufficient to have a model that maps to a table. The SQLModelMetaclass
requires table=True
to be passed as a keyword argument during subclassing of SQLModel
. Luckily, there is a solution for this built into Pydantic as well.
While this is mentioned nowhere on Pydantic's documentation website, the create_model
function (source here) has a __cls_kwargs__
parameter for being able to pass arbitrary keyword arguments to the metaclass during class creation.
These two components, together with the actual field definitions, are actually all we need to dynamically create our ORM class. Here is a full working example:
from typing import Optional
from pydantic import create_model
from sqlmodel import Field, Session, SQLModel, create_engine
field_definitions = {
"id": (Optional[int], Field(default=None, primary_key=True)),
"name": (str, ...),
"secret_name": (str, ...),
"age": (Optional[int], None),
}
Hero = create_model(
"Hero",
__base__=SQLModel,
__cls_kwargs__={"table": True},
**field_definitions,
)
if __name__ == '__main__':
sqlite_url = "sqlite:///test.db"
engine = create_engine(sqlite_url, echo=True)
SQLModel.metadata.create_all(engine)
session = Session(engine)
hero = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero)
session.commit()
session.refresh(hero)
print(hero)
That print
statement gives the following output:
secret_name='Pedro Parqueador' id=1 age=None name='Spider-Boy'
That demonstrates that the id
was created by the database upon insertion.
The SQL statements printed to stdout by the engine show that everything went as planned:
CREATE TABLE hero (
id INTEGER NOT NULL,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
...
INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
('Spider-Boy', 'Pedro Parqueador', None)
...
SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.id = ?
(1,)
So far, I have not encountered any caveats to this approach, beyond those that apply to dynamic model creation in Pydantic as well, such as the obvious lack of static type checking support or auto-suggestions, if a model was defined dynamically.
Tested with pydantic>=1.10.4,<2.*
and sqlmodel==0.0.8
.