Capability Convergence¶
Saffier 2.2.0 completes the architectural convergence campaign against the reference repository while keeping Saffier's own product shape intact. The final state is a Saffier-native ORM: SQLAlchemy 2.x Async is the database runtime, Lilya is the admin application layer, and Saffier owns the model, registry, query, migration, admin, tenancy, marshalling, and storage contracts directly.
Verdict¶
OVERALL ACCEPTED. Every capability required by the campaign is now either adopted, adapted, or explicitly rejected with a Saffier-specific reason. The accepted surface is implemented through Saffier modules and public APIs instead of compatibility shims.
ADMIN ACCEPTED. The admin now uses Lilya, Saffier registries, Saffier model configuration, Saffier marshalling hooks, Saffier permissions, Saffier static assets, and Saffier URL generation. It supports configured branding, richer HTML/CSS/JavaScript assets, create/edit/detail/list flows, authentication, model visibility rules, form validation, reverse-proxy prefixes, and mounted admin instances.
Runtime Ownership¶
The database runtime is owned by SQLAlchemy 2.x Async. Saffier uses
AsyncEngine, AsyncConnection, AsyncSession, async_sessionmaker,
SQLAlchemy transactions, SQLAlchemy pools, SQLAlchemy dialects, SQLAlchemy
metadata, and SQLAlchemy compilation directly. No connector abstraction or
external database lifecycle layer remains in Saffier's runtime path.
Decision Matrix¶
| Area | Decision | Rationale | Drift Risk | Follow-up |
|---|---|---|---|---|
| Database runtime | Adopt | SQLAlchemy Async is now the single runtime authority for engines, connections, sessions, execution, transactions, pooling, and dialect behavior. | Low | Keep new runtime work on SQLAlchemy public APIs. |
| Connection lifecycle | Adapt | Saffier keeps its user-facing Database and registry ergonomics while delegating connection state and disposal to SQLAlchemy Async. |
Low | Avoid adding new lifecycle layers unless they expose real Saffier behavior. |
| Transactions | Adopt | Transaction and savepoint behavior now maps to SQLAlchemy async transaction objects, including forced rollback scopes. | Low | Keep regression coverage around nested transactions and sync re-entry. |
| Schema management | Adopt | Create/drop/schema helpers run through AsyncConnection.run_sync() and SQLAlchemy metadata operations. |
Low | Continue treating metadata as the canonical DDL source. |
| Models and fields | Adapt | Saffier keeps its Python-native model and field system while adopting missing field capabilities such as managed files and image metadata. | Low | Add new fields as Saffier fields, not external wrappers. |
| QuerySets and managers | Adapt | Query ergonomics remain Saffier-native while execution uses SQLAlchemy statements and async result handling directly. | Medium | Preserve public query behavior while removing redundant internal indirection. |
| Bulk operations | Adopt | Bulk create, update, delete, and projection flows use SQLAlchemy execution paths without a separate connector model. | Low | Keep performance-sensitive coverage around generated SQL and row counts. |
| Relationships | Adapt | Relationship behavior stays aligned with Saffier's declarative fields, lazy loading, select-related behavior, and tenancy-aware table resolution. | Medium | Keep relationship changes tied to Saffier metadata ownership. |
| Reflection | Adopt | Reflection remains powered by SQLAlchemy inspection and Saffier model construction. | Low | Extend reflection through SQLAlchemy inspection primitives. |
| Tenancy | Adapt | Tenant routing keeps Saffier's schema/database semantics and adds scoped request-level helpers. | Medium | Keep schema context rules explicit and avoid hidden global state. |
| Registry lifecycle | Adapt | Registries now expose Saffier-owned ASGI lifespan wrapping while SQLAlchemy manages the underlying database runtime. | Low | Keep framework integration thin and Lilya/ASGI-compatible. |
| Migrations | Adapt | Migration commands stay Saffier-facing while using Saffier registries, SQLAlchemy metadata, and Alembic integration. | Medium | Keep migration signals and nullable-field handling documented. |
| Admin application | Adapt | Admin capabilities were ported into a Lilya-native, Saffier-configured application rather than copied as another framework's app. | Medium | Treat AdminConfig as the source of branding, prefixes, and permissions. |
| Admin assets | Adopt | HTML, CSS, JavaScript, JSONEditor, and Font Awesome assets were brought into the Saffier admin surface and wired to Saffier URLs. | Low | Keep static assets versioned with admin templates. |
| Admin model hooks | Adopt | Admin create and update flows now call model-owned marshalling hooks for schema generation and write payloads. | Low | Keep hook behavior documented with examples. |
| Reverse proxy support | Adopt | Admin URL generation and redirects honor configured prefixes and forwarded request context. | Low | Keep mounted-admin coverage around prefixed routes. |
| Testing utilities | Adapt | Factory and client helpers use Saffier-native names and Saffier runtime behavior. | Low | Remove old reference-named aliases instead of preserving them. |
| Public exports | Adapt | Public imports expose Saffier-native names, including file objects, middleware, and model-copy helpers. | Low | Prefer explicit Saffier exports over aliases. |
| Optional model engines | Reject | Hard-coupling the ORM to any validation library would weaken Saffier's Python-native identity. Engine integrations remain optional adapters. | Low | Keep engine support opt-in. |
| Unsupported backend claims | Reject | Saffier documents and tests the database backends it can honestly support through SQLAlchemy async drivers. | Low | Add backend claims only with real runtime proof. |
| Compatibility shims | Reject | Shims that only preserve reference-repository names were removed because they add surface area without Saffier value. | Low | Avoid adding legacy aliases in new APIs. |
Implemented Scope¶
- Replaced the database runtime with native SQLAlchemy 2.x Async ownership.
- Updated registry lifecycle, database creation/removal, transactions, schema helpers, sync re-entry, and framework lifespan behavior to use SQLAlchemy async primitives.
- Expanded the Lilya admin with richer templates, static assets, configured branding, URL helpers, reverse-proxy prefixes, authentication, permission classes, model visibility rules, mounted-admin support, and model marshalling hooks.
- Added ORM-managed
FileFieldandImageFieldbehavior backed by Saffier storage, including field-bound file objects, optional size columns, metadata columns, approval columns, image dimensions, MIME extraction, and custom name generation. - Added migration lifecycle hooks and forced-nullable migration selectors.
- Added scoped tenancy helpers and ASGI registry lifespan wrapping.
- Removed reference-named public aliases from model-copy and factory utilities.
- Updated public documentation, release notes, examples, and admin docs to describe the Saffier-native SQLAlchemy Async architecture.
Verification Evidence¶
The final local verification was performed with Hatch. The local test environment resolved to Python 3.13.12, matching the failing CI family reported for this campaign.
| Command | Result |
|---|---|
hatch run test:python -V |
Python 3.13.12 |
hatch run test:test -q |
987 passed, 204 warnings in 84.93s |
hatch run lint |
Passed |
hatch run docs:build |
Passed |
hatch run test:check_types |
Passed |
Final Acceptance¶
- SQLAlchemy Async is the sole database runtime.
- Runtime execution, transaction, schema, and session paths use SQLAlchemy public async APIs.
- The admin is Lilya-native and configured through Saffier.
- Managed file and image fields are Saffier-owned and storage-backed.
- Public docs describe the new architecture.
- Dead reference-name aliases were removed instead of preserved.
- No remaining accepted/adapted gap is intentionally deferred.