Some context
I am prototyping a custom ORM inspired by SQLModel and SQLAlchemy. I tested both and they seem to have different annotation behaviour for fields.
Let's consider the two following implementations of a User model in those two frameworks:
SQLModel:
from typing import Optional
from sqlmodel import SQLModel, Field, Session, select
from sqlmodel import create_engine
class User(SQLModel, table=True):
__tablename__ = "user_account"
id: Optional[int] = Field(primary_key=True, default=None)
name: str
if __name__ == '__main__':
# Create database
engine = create_engine("sqlite+pysqlite:///:memory:")
SQLModel.metadata.create_all(engine)
session = Session(engine)
# Add some test data
test = User(name="test")
session.add(test)
session.commit()
# Test a query
stmt = select(User).where(User.name.in_(["test"]))
for user in session.scalars(stmt):
print(user.name)
SQLAlchemy:
from sqlalchemy import create_engine, String, select
from sqlalchemy.orm import DeclarativeBase, Session, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column(String(30))
if __name__ == '__main__':
# Create database
engine = create_engine("sqlite+pysqlite:///:memory:")
Base.metadata.create_all(engine)
session = Session(engine)
# Add some test data
test = User(name="test")
session.add(test)
session.commit()
# Test a query
stmt = select(User).where(User.name.in_(["test"]))
for user in session.scalars(stmt):
print(user.name)
- SQLModel infer the type properly on the instances of the class. PyCharm linter properly indicates
user.nameis a string. HoweverUser.name.in_is seen as an error. This is not really practical if we need to pass filtering object to/from functions. Note thatmypyfails on this implementation. - SQLAlchemy does the opposite:
User.name.in_is properly type checked but theuser.nameis not. Passing model instance attributes to function is not very handy as there is basically no type checking. Heremypygives expected success.
Above implementations were tested with sqlmodel-0.0.14 and SQLAlchemy-2.0.26 on Python 3.11.
Just for clarification, I am not planning on using any SQLModel nor SQLAlchemy for my project. They are just a inspiration for elaborating a completely different ORM not even based on SQL databases.
Best of both worlds?
While keeping the model implementation as concise as possible, I'd like to provide a proper type checking for both class variables and instance variables.
My guess is that both SQLModel and SQLAlchemy rely on meta-classes for dealing with the field/attribute parts which makes proper type checking not possible but I might be wrong.
Considering the following (crud) base implementation, is there a way to provide type hints so both class and instance variable are properly type checked and where PyCharm's linter properly autocomplete Example.name and model_instance.name?
class Field:
def filter(self):
pass
class BaseModel(object):
def __init__(self, **kwargs):
# Crud way of setting the instance variables
for k, v in kwargs.items():
setattr(self, k, v)
class ExampleModel(BaseModel):
name: str = Field()
if __name__ == '__main__':
assert isinstance(ExampleModel.name, Field)
model_instance = ExampleModel(name="test")
assert isinstance(model_instance.name, str)