SQLModel – SQL Databases in FastAPI
github.com
github.com
Thanks for sharing!
This is the biggest thing I've built since FastAPI and Typer...
SQLModel is a library for interacting with SQL DBs, based on Python type hints.
Each model is both a Pydantic and SQLAlchemy model, and it's all optimized for FastAPI.
GitHub here: https://github.com/tiangolo/sqlmodel
More info in this Twitter thread: https://twitter.com/tiangolo/status/1430252646968004612
And the docs are here: https://sqlmodel.tiangolo.com/
In short, what motivated you to write your own rather than "bless" one of the existing ones?
I see the approach that this isn't exactly an orm, it's more of an api wrapper around it (SQLalchemy in that case), is that correct?
I do love Tortoise though, and it is what I am using currently -- but also one thing I'm currently battling with it is its approach to working with Pydantic. So I'll be giving this a try.
And in particular, other libraries have done a great job, but I wanted to take advantage of the features that editors can provide, with autocompletion, inline errors, etc. SQLModel has lots of tricks to provide the best developer experience possible, e.g. autocompletion while creating a new model instance.
For example, the style of querying stuff, for example: https://sqlmodel.tiangolo.com/tutorial/where/#where-and-expr...
Just wanted to mention that you emphasize "optimized for FastAPI" but it looks like it does nothing to do with FastAPI.
Do you have any reason to narrow down the scope to FastAPI when other frameworks like Flask are still more widely used?
Specifically mentioning only one framework causes loosing interest of people who use other frameworks.
A SQLModel is also a Pydantic model. FastAPI uses Pydantic.
FastAPI is basically the (async) 2021 version of Flask just built on Starlette instead.
If you have used Flask before, the learning curve will be pretty low, and the documentation is great.
But of course people have been using SQLAlchemy and Flask together for ages so I don't see any reason you couldn't use this with Flask instead if you prefer. That said, I bet you'll be able to whip up the MVP even faster using FastAPI.
I do not have any affiliation with the project other than being a happy user today.
Because he is the author of FastAPI
But we are at the point where async python has really taken off, as well as where the performance gains are just impossible to ignore except for apps thats are either dead-simple or that are super CPU-bound. And FastAPI is unquestionably leading the pack with regards to async frameworks, so it makes sense to target that. (Also, yes, he's the creator.)
(Additionally, FastAPI is using Starlette under-the-hood, which is built on ASGI, part of the point of which is to standardize async servers and frameworks to allow for wide compatibility. Anything that works on Starlette or any ASGI framework will work with FastAPI, and vice versa unless the component is tightly coupled to FastAPI-specific code.)
But I agree largely that Flask is now a thing of the past. If you want to very quickly bring a small API server or a simple view page up then it's fine. For almost anything more complex, it's a huge pain. The ecosystem is extremely fragmented and lots of vital plugins for core systems like caching, auth and such aren't maintained anymore. For someone starting a new project, even if it's a very small web server I'd prefer FastAPI
But as @tomnipotent says below, a SQLModel is also a Pydantic model, so, you can use the same model to do automatic API data validation, serialization, documentation, filtering... and now, also define the database.
There's no more integration, apart from the fact that I built it to do just that, and I made sure everything works just right with FastAPI.
But for any other framework that doesn't have automatic data validation, documentation, serialization, etc. (and you are just doing that by hand) it's an even simpler problem, so SQLModel will work just right, as any other ORM.
Am I understanding right that the main benefit of using sqlmodel is not needing to maintain the 2 separate models for the app vs db that one would need in most cases?
Also, have you thought about an approach to migrations yet? Curious to hear your thoughts there as well.
It's all just SQLAlchemy, so the same migrations with Alembic would do it, I plan on documenting that in the future and probably adding a thin layer on top.
I have a huge respect for your work.
@tiangolo I would like to use this for production at work, however I'm a bit hesitant about using the 0.0.x versions. Would you recommend me to wait until version 0.1.0 or 1.0.0?
But it's all SQLAlchemy underneath, the most wiidely used Python SQL library, with tons of years of usage, so that gives some safety.
And the test coverage is at 97%. I will release 0.1.0 once test coverage reaches 100% and I have the main docs I want there.
The only caveat with the version is upgrades, as, as of now, anything could change (probably I won't change anything, just add stuff). But you can solve it by pinning the version, writing tests, and upgrading the version only after tests pass.
The only other issue you might have is that you really want to do some very advanced trick with SQLAlchemy and in some way it's still not supported. In that case you might want to use SQLAlchemy directly for that model, you can mix SQLAlchemy and SQLModel (although I haven't documented that yet).
Or rather can I pass an existing pydantic class to SQLModel somehow?
In fact, I made a small utility library some time ago to create dynamic Pydantic models from SQLAlchemy models (https://github.com/tiangolo/pydantic-sqlalchemy), but that's only useful in a few cases, e.g. for response_model in FastAPI.
https://gist.github.com/sandys/671b8b86ba913e6436d4cb22d04b1...
This example trys to play with some quirky table structures - Mixins, Surrogate primary keys, index, etc
Both async and sync modes.
I like what @tiangolo has done with SQLModel, but i suspect that a lot of people who are already using sqlalchemy in production will prefer to unify via dataclasses versus switching the DX to a new library.
And this is possible today.
Nothing stuck out to me when perusing through the code
Edit: seems pylance can figure it out but mypy can't, maybe pylance is special casing something?
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
reveal_type(Hero.__init__)
# pylance: Type of "Hero.__init__" is "(self: Hero, *, id: int | None = Field(default=None, primary_key=True), name: str, secret_name: str, age: int | None = None) -> None"
# mypy: main.py:18:13: note: Revealed type is "def (__pydantic_self__: sqlmodel.main.SQLModel, **data: Any)"Anecdata, but when I was digging into how Dataclasses worked, Pylance had great completion, but only if I imported from the dataclass module itself. If I pasted the whole implementation into a local file and used that, I didn't get the hints.
This makes me think that Pylance _does_ have some secret sauce.
https://github.com/microsoft/pyright/blob/main/specs/datacla...
As Pyright already supports it, and Pylance is built on Pyright, they can use it directly. Hopefully more editors will use it and hopefully it will be part of the standard Python (in a PEP, with typing.dataclass_transform, and with mypy support).
And just in case you're wondering, there's no downside to having the dataclass_transform, anything that doesn't support it is unaffected, and nothing else would have completion either way.
And SQLModel, the same as those two dependencies, are independent of any framework, so you can combine them with anything you need.
But I haven't documented it yet.