Saffier¶
🚀 The only Async ORM you need. 🚀
Documentation: https://saffier.tarsild.io 📚
Source Code: https://github.com/tarsil/saffier
Announcement¶
Saffier is and always will be free and community-owned. The project is being actively modernized across ORM capabilities, testing, tooling, and documentation while preserving its Python-native ORM identity.
Motivation¶
Almost every project, in one way or another uses one (or many) databases. An ORM is simply an mapping of the top of an existing database. ORM extends for Object Relational Mapping and bridges object-oriented programs and relational databases.
Two of the most well known ORMs are from Django and SQLAlchemy. Both have their own strenghts and weaknesses and specific use cases.
This ORM is built on the top of SQLAlchemy core and aims to simplify the way the setup and queries are done into a more common and familiar interface.
Before continuing¶
Saffier is a Python-native ORM focused on query ergonomics, model expressiveness, and predictable async behavior. If your project also needs dedicated data validation or alternate model representations, you can integrate Saffier with the engine layer of your choice.
Why this ORM¶
When investigating for a project different types of ORMs and compared them to each other, for a lot of use cases, SQLAlchemy always took the win but had an issue, the async support (which now there are a few solutions). While doing the research I came across Encode ORM.
The team is the same behind of Databases, Django Rest Framework, Starlette, httpx and a lot more tools used by millions.
There was one issue though, although ORM was doing a great familiar interface with SQLAlchemy and providing the async solution needed, it was, by the time of this writting, incomplete and they even stated that in the documentation and that is how Saffier was born.
Saffier uses some of the same concepts of ORM from Encode but with its own Python-native model layer.
Saffier¶
Saffier is some sort of a fork from Encode ORM but rewritten at its core and with a complete set of tools with a familiar interface to work with. If you are familiar with Django, then you came for a treat 😄.
Saffier provides a Python-native field system while offering a friendly, familiar, and easy-to-use interface.
This ORM was designed to be flexible and compatible with pretty much every ASGI framework, like Ravyn, Starlette, FastAPI, Sanic, Quart... with simple pluggable design thanks to its origins.
Special notes¶
Saffier couldn't exist without Encode ORM and the continous work done by the amazing team behind it. For that reason, thank you!
Features¶
While adopting a familiar interface, it offers some cool and powerful features on the top of SQLAlchemy core.
Key features¶
- Model inheritance - For those cases where you don't want to repeat yourself while maintaining intregity of the models.
- Abstract classes - That's right! Sometimes you simply want a model that holds common fields that doesn't need to created as a table in the database.
- Meta classes - If you are familiar with Django, this is not new to you and Saffier offers this in the same fashion.
- Managers - Versatility at its core, you can have separate managers for your models to optimise specific queries and querysets at ease.
- Filters - Filter by any field you want and need.
- Q expressions - Complex boolean query composition with
Qobjects. - Model operators - Classic operations such as
update,get,get_or_none,bulk_create,bulk_updateand a lot more. - Pagination - Numbered and cursor pagination in
saffier.contrib.pagination. - Relationships made it easy - Support for
OneToOneandForeignKeyin the same Django style. - Auto reflection patterns - Pattern-driven reflected models via
AutoReflectModel. - Content types - Generic content type support via
Registry(..., with_content_type=True). - Permissions contrib - Reusable permission model and permission manager helpers.
- Admin subsystem - Python-native admin site and optional ASGI admin app.
- Python-native marshalls - DTO/serializer layer with custom fields, context, schema output, and save support.
- Model engines - Optional adapter layer for built-in Pydantic and msgspec support, plus custom future engines such as attrs.
- Standalone file storage API -
saffier.filesnow exposes file objects, storage backends, and safe filesystem handling. - Constraints - Unique constraints through meta fields.
- Native test client - We all know how hard it can be to setup that client for those tests you need so we give you already one.
- Multi-tenancy - Saffier supports multi-tenancy and even offers a possible solution to be used out of the box if you don't want to waste time.
And a lot more you can do here.
Migrations¶
Since Saffier, like Encode ORM, is built on the top of SQLAlchemy core, it brings its own native migration system running on the top of Alembic but making it a lot easier to use and more pleasant for you.
Have a look at the migrations for more details.
Installation¶
To install Saffier, simply run:
$ pip install saffier
You can pickup your favourite database driver by yourself or you can run:
Postgres
$ pip install saffier[postgres]
MySQL/MariaDB
$ pip install saffier[mysql]
SQLite
$ pip install saffier[sqlite]
Quick Start¶
The following is an example how to start with Saffier and more details and examples can be found throughout the documentation.
Use ipython to run the following from the console, since it supports await.
import saffier
from saffier import Database, Registry
database = Database("sqlite:///db.sqlite")
models = Registry(database=database)
class User(saffier.Model):
"""
The User model to be created in the database as a table
If no name is provided the in Meta class, it will generate
a "users" table for you.
"""
id = saffier.IntegerField(primary_key=True)
is_active = saffier.BooleanField(default=False)
class Meta:
registry = models
# Create the db and tables
# Don't use this in production! Use Alembic or any tool to manage
# The migrations for you
await models.create_all() # noqa
await User.query.create(is_active=False) # noqa
user = await User.query.get(id=1) # noqa
print(user)
# User(id=1)
As stated in the example, if no tablename is provided in the Meta class, Saffier automatically
generates the name of the table for you by pluralising the class name.
Connect your application¶
Do you want to have more complex structures and connect to your favourite framework? Have a look at connections to understand how to do it properly.
Exciting!
In the documentation we go deeper in explanations and examples, this was just to warm up. 😁
