Skip to content

Release Notes

2.2.0

Saffier 2.2.0 moves the database runtime fully onto native SQLAlchemy 2.x Async.

The ORM now owns its AsyncEngine, AsyncConnection, AsyncSession, and async_sessionmaker lifecycle directly. Query execution, transactions, schema helpers, database creation and removal, and framework lifespan integration all flow through SQLAlchemy's public async APIs.

Changed

  • Replaced the previous database runtime layer with a direct SQLAlchemy 2.x Async implementation.
  • Database URLs now normalize common plain dialects to SQLAlchemy async drivers while preserving the public DatabaseURL helper.
  • Transactions now use SQLAlchemy async transaction objects and savepoints directly, including forced rollback scopes.
  • Schema helpers now run through AsyncConnection.run_sync() and SQLAlchemy metadata APIs.
  • The built-in admin app is now Lilya-native and uses Saffier's registry and SQLAlchemy Async runtime directly.
  • The admin HTML interface now includes the richer Tailwind, Font Awesome, and JSONEditor-powered pages for dashboard, model lists, object detail, create, and edit flows.
  • Admin model visibility now follows Meta.in_admin, while Meta.no_admin_create keeps models browsable but blocks creation through both UI and service paths.
  • Admin links, redirects, and the 404 page now honor AdminConfig, including admin_prefix_url for reverse-proxy deployments.
  • Admin Basic auth is now available both as ASGI middleware and as a Lilya permission-protocol class.
  • Admin create and update flows now honor model-level admin marshalling hooks for schemas and writes.
  • Tenant routing now includes with_tenant, a scoped context manager for request-level multi-tenancy.
  • Migration commands now emit pre_migrate and post_migrate lifecycle signals around revision, upgrade, and downgrade flows.
  • Migration generation now supports --nf forced-nullable field selectors so required fields can be added safely to populated tables and backfilled through the registry.
  • Registries can now wrap ASGI applications directly with Registry.asgi() for whole-registry lifespan management.
  • Factory documentation now covers model factories, relationship factories, and async persisted factory rows.
  • FileField and ImageField now use Saffier's storage subsystem directly, returning field-bound file objects with optional size, metadata, and approval columns.
  • Documentation now describes Saffier as a first-class SQLAlchemy Async ORM throughout.
  • Added a Saffier-native architecture acceptance report for the 2.2.0 convergence campaign.

Fixed

  • Scoped re-entered sync lazy loads so nested event-loop state is restored immediately, preserving Python 3.14 asyncpg compatibility.
  • saffier admin_serve now mounts the Lilya session middleware required by the admin session context.

Removed

  • Removed the external database runtime dependency from Saffier's runtime requirements.
  • Removed extra database execution indirection from Saffier's runtime path.
  • The admin extra now depends on Lilya for the admin application runtime.
  • Removed old reference-named model-copy and factory utility aliases in favor of Saffier-native names.
  • Removed documentation language that pointed users at the previous database runtime model.

2.1.0

Saffier 2.1.0 expands the ORM into its next layer: engine-pluggable models.

This release keeps the Saffier core as the source of truth while adding an optional adapter layer for external model engines, richer serialization and validation entry points, and substantially expanded documentation for adopting or extending the new architecture.

Added

  • Engine-pluggable model support through Registry(model_engine=...) and Meta.model_engine.
  • Built-in pydantic and msgspec model engine adapters with lazy imports.
  • New engine-facing model APIs: get_model_engine(), get_engine_model_class(), engine_validate(), from_engine(), to_engine_model(), engine_dump(), engine_dump_json(), and engine_json_schema().
  • Dedicated model-engine documentation, reference pages, runnable examples, and a custom-engine implementation guide.

Changed

  • Saffier models remain fully functional with no engine configured; engine support is now a pure opt-in adapter layer on top of the existing ORM behavior.
  • Engine selection now participates in model inheritance, proxy-model generation, and registry-level defaults.
  • New and touched engine/model code paths now include comprehensive docstrings across both public and internal helpers.

Fixed

  • Model-engine metadata now propagates correctly through proxy models, inherited models, and copied registries.
  • Engine-backed payload generation preserves Saffier-owned serialization semantics while allowing per-engine projection and validation behavior.

2.0.0

Saffier 2.0.0 marks the beginning of a new chapter for the project: a sharper, fully modernized ORM with a pure-Python core, stronger query and relationship capabilities, and a cleaner long-term foundation for framework-agnostic async applications.

This release focuses on the areas that matter most in real projects: model behavior, queryset power, relation handling, migration tooling, tenancy support, compatibility, and documentation.

Saffier became faster, better, cleaner. This is the same Saffier you know and love, but with a sharper edge and a stronger core and... a lot of new features and improvements but also... Faster!

Added

  • QuerySet set operations: union, union_all, intersect, intersect_all, except_, and except_all.
  • QuerySet bulk_get_or_create with bulk_select_or_insert alias.
  • QuerySet local_or, batch_size, extra_select, and reference_select APIs.
  • Settings runtime helpers: configure_settings, reload_settings, and override_settings.
  • Compatibility modules for legacy imports across queryset, tenancy, admin, Lilya middleware, and model helpers.
  • Pure-Python Model.model_json_schema(...) compatibility and richer tenancy helpers such as with_schema(...) and using(database=..., schema=...).

Changed

  • Saffier now ships with a fully pure-Python model/runtime layer and no longer depends on Pydantic internally.
  • Query and model delete() now return deleted row counts.
  • Documentation tooling now follows the Zensical workflow with Hatch/Taskfile docs commands (docs_prepare, docs_build, docs_clean, serve).
  • CLI, migration, and application-discovery flows were refreshed for the current runtime and template system.

Fixed

  • Tenant schema table metadata now keeps foreign-key relationships consistent across related models in non-default schemas.
  • QuerySet raw_delete() added and delete filtering now respects accumulated OR clauses.
  • Removed loguru; Saffier now uses Python standard-library logging in core modules.
  • QuerySet cache behavior improved for all(clear_cache=True), cached get(), SQL rendering, and select_for_update(...).
  • Permission, pagination, lazy-import, and nested exclude_secrets() compatibility are more complete and consistent.
  • Model save/create extraction now preserves nullable/default fields, explicit read-only primary-key values, and composite-key / related-field inserts.

1.4.2

Changed

  • Updated the database integration used by that release.
  • Internal refactor of the registry.

Fixed

  • CI integration.

1.4.1

Added

  • Support for list and tuples as a type for model_apps.

1.4.0

Added

  • Support for model_apps inside the Migrate object allowing global discovery by application. This will make sure all apps will be properly inspected.
  • Add documentation about the new model_apps.

Changed

  • Upgrade internal requirements.

1.3.7

Changed

  • New lazy loading settings system making runtime configuration more dynamic and centralized.

1.3.6

Changed

  • Update the internal settings implementation.

1.3.5

Changed

BREAKING CHANGE

Due to some internal compatibilities, Saffier is rolling back to SAFFIER_SETTINGS_MODULE from SETTINGS_MODULE

  • SETTINGS_MODULE was renamed to SAFFIER_SETTINGS_MODULE.

1.3.4

Changed

  • Update internal anyio dependency.

1.3.3

Changed

  • Upgrade internal requirements.

Fixed

  • auto_now and auto_now_add on save() and update() wasn't only updating the field with auto_now.
  • Extraction of the default field for date and datetime.

1.3.2

Fixed

1.3.1

Fixed

  • Fix default for SAFFIER_SETTINGS_MODULE if nothing is provided.

1.3.0

Added

  • Added new experimental activate_schema for tenant models using the using queryset operator.
  • Support for ManyToMany to accept strings to the to attribute.
  • Support for new queryset operations only() and defer.
  • Intenal ModelProxy allowing to manipulate objects querysets such as only and defer.
  • Support for secrets and secret queryset.

Changed

Breaking changes

Saffier now uses Dymmond Settings which this simlpy affects the way the settings module is loaded. Prior to version 1.3.0 it was like this:

SAFFIER_SETTINGS_MODULE=...

From version 1.3.0 is:

SAFFIER_SETTINGS_MODULE=...

The rest remains as it. More information about how to use it in the official documentation.

Fixed

  • Multiple join tables were not generating the complete join statement when using select_related.
  • Fixed metaclass for TenantMixin making sure all the queries are correctly pointing to the right tenant.
  • When generating a many to many through model, the maximum length is enforced to be 63 characters.
  • Object discovery for intellisense.
  • Allow ManyToMany to also accept a string as a parameter for the to.

1.2.0

Added

  • Support for sync queries. This will enable Saffier to run in blocking frameworks like Flask, bottle or any other by using the newly added run_sync.

Fixed

  • Fixed multi tenancy from contrib.
  • Fixed using where schema name was raising a not found reference for foreign key when querying the tenant.

1.1.0

Added

  • Support for or_, and_ and not_ for SQLAlchemy style queries and Saffier syntax sugar queries.

Changed

  • inspectdb is now handled by an independent isolated called InspectDB.
  • Updated internal database URL support and fixed URL parsing errors for complex passwords caused by the urlsplit.

Fixed

  • server_default does not raise a ValueError.
  • server_default added as validation for nullable.

Warning

This could impact your migrations, so the advise would be to generate a new migration after upgrading to the new version of Saffier to make sure the database reflects the proper nullables/non-nullable fields.

1.0.2

Added

  • inspectdb allowing to generate saffier.ReflectModel from the database.

Changed

  • Added name for saffier.UniqueConstraint allowing unique custom names for the unique_together.
  • max_name_length in the datastuctures changed to __max_name_length__ and ClassVar.

1.0.1

Changed

Fixed

  • Database object docstring.

1.0.0

Added

  • Support for Python 3.12

Changed

  • Update base requirements.

0.18.0

Added

  • New Prefetch support allowing to simultaneously load nested data onto models.
  • New Signal support allowing to "listen" to model events upon actions being triggered.

Changed

  • Updated pydantic and alembic

0.17.1

Fixed

  • DeclarativeModel generating internal mappings names was breaking for class objects.

0.17.0

Added

  • Multi tenancy support by updating the registry and allowing to create the multi schema.
  • Add new using(schema=...) and using_with_db(database=..., schema=...) to querysets.
  • Add support for create_schema and drop_schema via registry.
  • Add support to get_default_schema from the registry.schema.
  • Documentation for tenancy.
  • Improved the documentation for schemas.
  • Added a new parameter extra to registry allowing to pass a Dict like object containing more database connections. This is an alternative to the registries.
  • Improved documentation for registry explaining how to use the extra parameters. and query them.
  • Added a new ConnectionConfig TypedDict for the registry extra.

Changed

  • Update the build for Model and ReflectModel to allow passing the schema.

Fixed

  • Registry metaclass wasn't reflecting 100% the schema being passed into the metadata and therefore, querying the database public schema.

0.16.0

Changed

  • Updated versions of the requirements to the latest.
  • Internal file structure
  • Breaking change. Before for fields the import was from saffier.db.models.fields import ... and that was now changed to from saffier.db.fields import ...

Added

  • values() and values_list() to the queryset.

Fixed

  • ConfigDict in settings.

0.15.0

Added

  • SaffierExtra class allowing the use of Saffier tools without depending on the Migrate object.

0.14.2

Fixed

  • AsyncIO event loop blocking the reflection.

0.14.1

Fixed

  • Remove super init from Registry.

0.14.0

Changed

  • Update Saffier core to start using Pydantic 2.0 and improved performance.

Note

This is a massive performance improvement done by Pydantic that is now compiled in Rust. This bring a whole new level of performance to Saffier as well.

Warning

To use this version of Saffier with Ravyn, until it is announced compatibility with pydantic 2.0 with Ravyn, it is recommended to use saffier prior to this release.

0.13.0

Changed

  • fields are now imported in a different path. This is a breaking change. PR #62 by @tarsil

Before

from saffier import fields

Now

from saffier.db import fields

Added

  • Added server_default option for fields allowing to specify if the value should be generated from the DB and how to.
  • Added support for save() of the model. PR #62 by @tarsil

0.12.0

Added

  • New version of the declarative(). PR #60 by @tarsil.
  • ManyToMany and OneToOne added as alternatives to ManyToManyField and OneToOneField. The latter will always exist but you can also import the ManyToMany and OneToOne as alternative instead.

Fixed

  • Registry now allowing the lru_caching to happen properly.

0.11.0

Added

0.10.2

Added

  • comment option for the fields. PR #57 by @tarsil.

0.10.1

Changed

  • Minor database integration maintenance update. PR #56 by @tarsil.

0.10.0

Changed

  • Updated to the latest version of pydantic making sure all the fixes are in place.

0.9.0

Added

0.8.0

Changed

  • Updated relationships document with more examples regarding multiple foreign key declarations.

Added

  • contains method to queryset allowing to query if a given model or reflected model exists in the queryset.
  • related_name is now supported on ForeignKey allowing transverse queries.
  • Allow reverse queries using nested fields.
  • on_update for ForeignKey and OneToOne fields
  • Multiple ForeignKeys to the same table is now possible.
  • Related Name document added
  • Nested queries using related_name

0.7.4

Fixed

  • Removed nested_asyncio causing infinite loops.

0.7.3

Added

  • postgresql Typo in requirement installation.

0.7.2

Added

  • db_schema - Added Registry objects of the metadata.

0.7.1

Fixed

  • Lifespan event on shell returning async manager.

0.7.0

Changed

  • Renamed saffier-admin to saffier.
  • Deprecate saffier-admin. Now you can simply call saffier with the same commands as before.

Added

  • New shell command that allows interactive shell with saffier models.
  • New SAFFIER_SETTINGS_MODULE allowing to create and pass specific and unique settings to any saffier instance.
  • Added support for ipython and ptpython for shell access via saffier.

Fixed

  • Linting and formatting issues with Ruff.
  • Bug with ReflectModel. A ReflectModel might not need all the fields from the database and the mapping should reflect that.
  • run_until_complete issues fixed with nest_asyncio.

0.6.1

Fixed

  • UUIField generations with Alembic.

0.6.0

Added

  • Support for SQLAlchemy 2.

Changed

  • Moved from the original async database package to its then-current fork and updated internal references.
  • DatabaseClient was updated to reuse the then-current test database helper.

Fixed

  • Updated requirements.

0.5.0

Changed

  • Updated requirements to support Ravyn >= 1.1.0 for testing.
  • Updated testing and docs requirements.

Added

  • Metaclass option to support database tables reflection. Allowing reading tables from existing database. 35
  • Documentation regarding the reflection of tables. #37

Fixed

  • Typos in documentation

0.4.0

Changed

  • Fixed mypy typing in the codebase #26
  • Updated pyproject.toml requirements #26

Added

  • UniqueConstraint object for the unique_together #29
  • UniqueConstraint documentation #29

0.3.0

Added

  • Integrated the support for native migrations with Saffier.

    • This brings native generated migrations within Saffier under Alembic's package, allowing a seemless integration and cross-compatibility with any framework using Saffier.
  • Added new DatabaseTestClient for creating the test database for each connection string provided.

    • No more needed to manually create two separate databases thanks to the client that does the automatic management for you.

0.2.1

Changed

  • This was supposed to go in the release 0.2.0 and it was missed. Updated queryset lookup for functions allowing accesing the model functions from the manager directly.

0.2.0

Added

  • New Index object allowing the creation of internal SQLAlchemy indexes.

Changed

  • Updated metaclass to validate the fields being added to indexes.

0.1.0

This is the initial release of Saffier.

  • 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.
  • Model operators - Classic operations such as update, get, get_or_none, bulk_create, bulk_update and a lot more.
  • Relationships made it easy - Support for OneToOne and ForeignKey in the same Django style.
  • Constraints - Unique constraints through meta fields.