SQLModel is heavily based on Pydantic. The latter has the create_model
function allowing you to create/define a model class at runtime, by passing the field definitions as arbitrary keyword arguments.
There seems to be no special version of the create_model
function built into SQLModel and there is no mention of dynamic model creation in the documentation.
Is it possible to leverage the Pydantic create_model
function to define a fully-functioning SQLModel class?
Functioning in the sense that it will properly serve as an ORM class, play nice with a database engine, and its instances can be added to and refreshed by database sessions, just like any statically defined SQLModel
subclass.
As a proof of concept, it should be possible to dynamically construct a working equivalent of the following statically defined model and perform the aforementioned operations with it:
from typing import Optional
from sqlmodel import Field, SQLModel
class Hero(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
name: str
secret_name: str
age: Optional[int] = None
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
.