An exploration of ideas for abstracting generation of SQLModel objects + FastAPI endpoints.
SQLModel is an elegant and intuitive package for building Pydantic-parsed SQL databases with a FastAPI interface.
We (core developers of Pangeo Forge) are exploring SQLModel for implementing a database + API for that project.
In the course of prototyping, I (@cisaacstern) came to wonder if introducing an abstraction/convenience layer on top of some of SQLModel's core functionality might make it easier to develop our application. This repo is an experimental implemention of that convenience layer.
- Clone this repo and
cdinto it - Create and activate a new virtual environment with Python >= 3.6, < 3.10
- From the repo root, install project dependencies with
pip install -r requirements.txt
From the repo root, run
$ pytest
to confirm that the project passes its tests in your local environment.
Importantly, in this repo the tests are identical to the tests in the SQLModel docs tutorial.
As such, the abstracted approach proposed here passes all of the same tests that the official tutorial example does.
You will note that at 40 lines of code, this repo's main.py module is less than half as long as the 107-line main.py module in the docs tutorial (also copied here).
A full diff between the tutorial's
main.pyand this repo's main module is viewable on GitHub here.
In brief, the conciseness gains in this repo's main.py are acheived via the addition of two objects in a new abstractions.py module:
MultipleModels: A dataclass that generates classes for the multiple models with inheritance SQLModel design pattern and places them alongside a specified API endpoint path. This class requires a base model and a response (i.e. reader) model as input, and generates the remaining classes from these two in__post_init__. Theoretically, the response model can be deterministically generated from the base model as well (it requires only the addition of a requiredidfield). I have not included this feature, however, because I'm not sure if there is a concise way to specify additional required (non-default) fields on a derived class using Python's built-intype()ortypes.new_class().RegisterEndpoints: This dataclass takes aMultipleModelsinstance (along with aFastAPIinstance andget_sessioncallable) as input, and from these automatically registers create, read, update, and delete (CRUD) endpoints for theMultipleModelsinstance with theFastAPIapplication instance.
register_endpointsis a convenience function wrappingRegisterEndpoints, which is how this object is instantiated withinmain.py
We will greatly appreciate any feedback about potential issues which may arise if we opt to run this approach in production. Are there edge cases, not covered by the tutorial tests, where this style may falter? How might these abstractions be improved or refactored for increased reliability and/or readability? Thank you in advance for your consideration and feedback.