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.