v0.11.0
Added
- Default model service factories — six new top-level exports that
return ready-made service callables for the common case where the
entire body is a one-line wrapper over the mutation helpers:
create_model(Model, *, field_map=, exclude_fields=, m2m=),
update_model(Model, *, field_map=, exclude_fields=, m2m=, update_fields=), anddelete_model(Model, *, soft_delete=), plus
the matchingacreate_model/aupdate_model/adelete_model
async variants that wrapacreate_from_input/aupdate_from_input
/await instance.adelete().m2maccepts either a static mapping
or a callable receiving the validateddata— the typical shape
when M2M values live on the input itself. The returned callables
conform to the unifiedCreateService/UpdateService/
DeleteServiceProtocols, so they absorb arbitrary framework-pool
keys (request,user, URL kwargs,ServiceSpec.kwargsreturns)
and the existing view layer routes them — sync or async — without
changes.
Removed
- The pre-merge
StrictCreateService/StrictUpdateService/
StrictDeleteService/StrictListSelector/StrictRetrieveSelector
/StrictOutputSelectorclasses. Rename every import to its unified
equivalent (StrictCreateService→CreateService, etc.) and drop
the trailingExtraTtype argument from each call site (extras are
now typed on the function signature instead — see Changed below).
The@implements(...)decorator pattern keeps working unchanged
once the names update. NoKwargs(the emptyTypedDictpreviously used as theExtraT
slot of strict service Protocols). The slot no longer exists. Drop
imports ofNoKwargs; if you were also writing
**extras: Unpack[NoKwargs]in a service body, replace it with
**extras: Any.
Changed
-
ChangeResultis now generic over the model type. The four mutation
helpers (create_from_input,acreate_from_input,
update_from_input,aupdate_from_input) thread the model TypeVar
through their signatures, socreate_from_input(Author, ...)returns
aChangeResult[Author]whose.instanceis typed asAuthor—
removing thecast(Author, result.instance)boilerplate that used to
be necessary. BareChangeResult(no parameter) keeps resolving to
ChangeResult[Model], so existing annotations continue to work
unchanged. Runtime behaviour is identical; this is a typing-only
change. -
Breaking (typing only): the lenient and strict service / selector
Protocols are merged into a single shape per kind, with**extras
typed asAny. The strict form's trailingExtraTtype argument is
gone. Names and call sites:CreateService[InputT, ResultT].UpdateService[InputT, InstanceT, ResultT].DeleteService[InputT, InstanceT, ResultT].ListSelector[ResultT].RetrieveSelector[ResultT].OutputSelector[InT, OutT].
Strict-typed extras stay possible on your own function signature:
declare aTypedDictwithtotal=False(or per-fieldNotRequired)
and annotate**extras: Unpack[YourKw]. Inside the function body,
extras["foo"]is typed byYourKw. The Protocol does not enforce
a kwargs-shape match — that cross-check only worked under one minor
version of one type checker (ty0.0.32) and never undermypyor
pyright. Putting the typing on the function instead works on every
modern checker.Three migration notes:
- Drop the trailing
ExtraTfrom every parameterised call site:
StrictCreateService[AuthorIn, MyKw, Author]→
CreateService[AuthorIn, Author]. Keep**extras: Unpack[MyKw]
on your function for typed extras. - Strict extras
TypedDicts must declare keys asNotRequired
(or settotal=Falseon the class) — required keys would make
the function reject callers that omit them, breaking Protocol
conformance under PEP 692. - The lenient Protocols no longer name
requestanduseras
fixed parameters — they flow through**extraslike any other
framework-pool key (matching the strict Protocols, which already
dropped these in 0.9.0). Services that declared
def fn(*, data, request, user, **kwargs)keep working at
runtime; to satisfy the new Protocol annotation either drop the
namedrequest/userparameters and read them off**extras,
or subclassHttpExtras[YourUser](nowtotal=False) and use
it as theUnpacktarget.
Runtime behaviour of every callable is unchanged. The merge unblocks
the default model service factories above and restores Python 3.10+
support (the previous design needed PEP 728'sextra_items=Any,
which is not in mypy yet and forced a 3.13 floor). -
HttpExtras[UserT]is now declaredtotal=False: every key is
optional, matching the framework's runtime contract (the kwargs
pool may or may not containrequest/userdepending on the
caller). Subclass withtotal=False(or annotate fields as
NotRequired) for the same reason — see migration note 2 above. -
Version is now tracked in a single source of truth at
rest_framework_services/version.py.pyproject.tomldeclares
dynamic = ["version"]and hatchling reads the value fromversion.py
at build time.rest_framework_services.__version__continues to
re-export the same value. Pure refactor — no behaviour change.