I’d like to propose a narrow public extension surface for model-level packages, without introducing a full plugin framework.
The goal is to expose just enough public API for metadata-driven extensions / packages like oxyde-admin to work cleanly, while deferring broader questions like queryset mutation observers or a general event system.
Why this would help
Oxyde already preserves unknown Meta attributes in _db_meta.extra, which makes metadata-driven extensions feel like a natural fit.
What is still missing is a small public surface for packages to:
- act once a model is fully finalized
- serialize model instances into DB-relevant payloads
- participate in the same DB/transaction context as the original model hook
Without that, extension packages depend on private internals, duplicate ORM logic, or rely on import-order/bootstrap workarounds.
Proposed public API
This proposal is intentionally limited to 3 additions.
1. Add a public on_model_finalized(...) hook
A callback registry for fully finalized table models.
Example:
from oxyde.models import on_model_finalized
def my_extension(model: type[Model]) -> None:
if not model._db_meta.extra.get("my_feature"):
return
# inspect model metadata, register related objects, etc.
on_model_finalized(my_extension)
- runs only after FK resolution, field metadata parsing, PK caching, and column type caching are complete
- runs once per finalized table model
- can replay already-finalized models if registered late
- is safe for packages that need to inspect model metadata at startup
This would provide a public alternative to relying on internal model finalization behavior.
2. Add a public DB payload serializer
Expose a stable public serializer for DB-relevant model payloads.
Example:
from oxyde.models import dump_db_payload
payload = dump_db_payload(instance)
payload = dump_db_payload(instance, include_none=True)
payload = dump_db_payload(instance, fields={"name", "status"}, include_none=True)
A minimal initial shape could be:
dump_db_payload(instance, *, include_none=False, fields=None)
Expected behavior:
- excludes virtual relation fields and computed fields
- handles FK fields consistently
- works for both insert-style payloads and field-scoped updates
- returns field-keyed data
This would give package authors a stable alternative to private serializer helpers.
3. Pass using / client into existing lifecycle hooks
Extend the existing hooks so related writes can participate in the same execution context.
Example:
async def pre_save(
self,
*,
is_create: bool,
update_fields: set[str] | None = None,
using: str | None = None,
client: SupportsExecute | None = None,
) -> None: ...
async def post_save(
self,
*,
is_create: bool,
update_fields: set[str] | None = None,
using: str | None = None,
client: SupportsExecute | None = None,
) -> None: ...
async def pre_delete(
self,
*,
using: str | None = None,
client: SupportsExecute | None = None,
) -> None: ...
async def post_delete(
self,
*,
using: str | None = None,
client: SupportsExecute | None = None,
) -> None: ...
Expected behavior:
- hooks can tell which DB alias is active
- hooks can use the active transaction/client for related writes
- multi-DB and transactional extension logic becomes straightforward
- the existing hook model stays intact, with minimal API expansion
What this proposal is not asking for
This proposal intentionally does not ask for:
- a general plugin framework
- a signal/event bus
- queryset mutation observers
- bulk mutation interception
- a broad new extension architecture
Those may be useful later, but this seems like the smallest useful public contract.
Why this feels like the right scope
These 3 additions would let package authors build a first generation of metadata-driven extensions using public APIs, without forcing Oxyde to commit to a larger extension model yet.
It would cover the core needs of:
- safe model discovery after finalization
- stable DB-facing serialization
- correct transaction/connection context in hooks
Acceptance criteria
- model finalization timing
- DB payload serialization
- execution context inside lifecycle hooks
Open question
Would maintainers prefer these additions as:
- direct exports from
oxyde.models
- a small
oxyde.extensions module
- a mix of both
My bias is to keep the surface small and place these in the most obvious public location, even if the longer-term extension story evolves later.
I’d like to propose a narrow public extension surface for model-level packages, without introducing a full plugin framework.
The goal is to expose just enough public API for metadata-driven extensions / packages like oxyde-admin to work cleanly, while deferring broader questions like queryset mutation observers or a general event system.
Why this would help
Oxyde already preserves unknown
Metaattributes in_db_meta.extra, which makes metadata-driven extensions feel like a natural fit.What is still missing is a small public surface for packages to:
Without that, extension packages depend on private internals, duplicate ORM logic, or rely on import-order/bootstrap workarounds.
Proposed public API
This proposal is intentionally limited to 3 additions.
1. Add a public
on_model_finalized(...)hookA callback registry for fully finalized table models.
Example:
This would provide a public alternative to relying on internal model finalization behavior.
2. Add a public DB payload serializer
Expose a stable public serializer for DB-relevant model payloads.
Example:
A minimal initial shape could be:
Expected behavior:
This would give package authors a stable alternative to private serializer helpers.
3. Pass
using/clientinto existing lifecycle hooksExtend the existing hooks so related writes can participate in the same execution context.
Example:
Expected behavior:
What this proposal is not asking for
This proposal intentionally does not ask for:
Those may be useful later, but this seems like the smallest useful public contract.
Why this feels like the right scope
These 3 additions would let package authors build a first generation of metadata-driven extensions using public APIs, without forcing Oxyde to commit to a larger extension model yet.
It would cover the core needs of:
Acceptance criteria
Open question
Would maintainers prefer these additions as:
oxyde.modelsoxyde.extensionsmoduleMy bias is to keep the surface small and place these in the most obvious public location, even if the longer-term extension story evolves later.