NoOp Materia¶
NoOpMateria is the default, backend-free materia. It is active automatically — you don't
instantiate it or bless() anything. Just define transmuters and use them like ordinary Pydantic
models: no engine, no session, no setup.
from arcanus.base import BaseTransmuter, Identity
from pydantic import Field
from typing import Annotated, Optional
class Author(BaseTransmuter):
id: Annotated[Optional[int], Identity] = Field(default=None, frozen=True)
name: str
author = Author(name="Isaac Asimov") # works immediately, no backend
Because there is no backend, there is no session and no lifecycle to manage — a transmuter is a plain Python object that lives while referenced and is collected when it isn't.
When to use it — and when not to¶
Don't reach for NoOp just to get objects
If you only need plain validated objects and have no other backend in play, use Pydantic
directly — NoOpMateria is pure overhead over raw Pydantic and buys you nothing on its own. It
exists so that the transmuter machinery (relationships, expressions, partial schemas, the
provider-binding protocol) works before a real backend is attached: in unit tests, in
prototyping, and as the fallback when no other materia is registered. Choosing it deliberately
as your "object layer" is a waste.
The legitimate uses:
- Tests & prototyping — exercise transmuter logic without standing up a database.
- The default fallback — code written against transmuters keeps working with no backend
configured, then gains persistence the moment you bind a
SqlalchemyMateria.
A note on performance¶
A materia is not free. Arcanus adds machinery on top of your objects — validation that defers relationship loading, provider proxying, an identity-aligned context — and that machinery costs CPU. Be clear-eyed about two distinct gaps the project tracks (and measures; see the benchmark suite and the CodSpeed dashboard):
- Schema overhead (NoOp vs pure Pydantic). A transmuter under
NoOpMateriais measurably slower than the equivalent plain Pydantic model — that delta is the transmuter machinery, with no backend to justify it. This is exactly why NoOp is not a thing to adopt for its own sake. - Backend overhead (a materia vs hand-rolled glue). Against the hand-written "validate with
Pydantic, then build/realize ORM objects" pattern, a real materia (e.g.
SqlalchemyMateria) adds overhead too — you are paying for the unified object, the deferred loading, and the bookkeeping. The trade is fewer conversion bugs and one object instead of two, not raw speed.
In short: reach for a materia when its backend integration earns the cost. If nothing needs a backend, plain Pydantic wins.
We're working to shrink the gap
Narrowing this overhead is an active focus — we really are trying (the benchmark suite exists precisely to keep us honest about it). It's hard, and progress is incremental. If performance is your thing, contributions are very welcome — profiles, fixes, and ideas all help.
The components, introduced here¶
Every core component is backend-agnostic, so this NoOp section is where each is introduced with the fewest moving parts. The SQLAlchemy section then expands each with persistence detail and real-world examples.
- Relationships —
Relation,RelationCollection,RelationSet,RelationMap,TypedRelationMap, and the field helpers. - Expressions & Cursors — typed columns, expressions, criteria, and the pre-built cursor/pagination helpers.
- Partial Schemas — the runtime-only
.Create/.Updatemodels and theIdentityannotation. (experimental)