Services¶
Protocols¶
Each Protocol is parameterised by input, instance (where applicable), and
result. **extras is typed Any, so the framework's kwargs pool flows
through without the service having to declare each key. Strict-typed
extras live on the user's function signature via **extras: Unpack[YourKw]
— see Typing services and selectors for the full pattern.
CreateService ¶
Bases: Protocol[InputT, ResultT]
Structural shape for a create-action service callable.
data carries the validated input from the framework. **extras
absorbs whatever else the framework's kwargs pool delivers — request,
user, and the ServiceSpec.kwargs / get_service_kwargs returns —
without the service having to declare each key. The Protocol types
**extras as Any so services on every major type checker (ty,
mypy, pyright) conform.
Strict-typed extras stay possible on your own function signature: declare
your extras as a TypedDict with NotRequired keys and annotate
**extras: Unpack[YourKw]. Inside the function body, extras["foo"]
is then typed by YourKw. The Protocol no longer carries a third type
argument for the kwargs shape — that cross-check only ever worked under
one minor version of one type checker (ty 0.0.32) and is not portable.
UpdateService ¶
Bases: Protocol[InputT, InstanceT, ResultT]
Structural shape for an update-action service callable.
Receives the resolved instance plus the validated data. Returning
None instructs the framework to render the in-memory instance
(mirroring DRF's UpdateAPIView shape).
See :class:CreateService for the extras-typing notes.
DeleteService ¶
Bases: Protocol[InputT, InstanceT, ResultT]
Structural shape for a delete-action service callable.
Receives the resolved instance. Most delete services return None;
if you need a response body, return a value and configure
ServiceSpec.output_selector_spec with an output_serializer
(and optionally a re-fetch selector).
For delete with payload — when the spec carries an input_serializer
— bind InputT to your input dataclass and declare data on the
service. data is optional in the Protocol (default :data:Ellipsis)
so services that don't read a body can still match the shape by binding
InputT to :class:~rest_framework_services.types.no_input.NoInput.
See :class:CreateService for the extras-typing notes.
Default model service factories¶
create_model¶
create_model ¶
create_model(
model: type[ModelT],
*,
field_map: dict[str, str] | None = None,
exclude_fields: list[str] | None = None,
m2m: Mapping[str, Any] | Callable[[Any], Mapping[str, Any]] | None = None,
) -> Callable[..., ModelT]
Return a service callable that builds model from validated input.
Equivalent to writing the canonical glue stub by hand::
def create_author(*, data: AuthorIn, **_: Any) -> Author:
return create_from_input(Author, data).instance
field_map and exclude_fields are forwarded to
:func:~rest_framework_services.mutations.create_from_input.
m2m accepts either a static mapping (passed straight through) or a
callable that receives the validated data and returns the mapping —
the common case where M2M values live on the input dataclass / dict
itself::
create_model(
Post,
m2m=lambda data: {"tags": data.tags},
)
The returned closure accepts **kwargs so the framework's kwargs pool
(request, user, URL kwargs, ServiceSpec.kwargs returns) is
absorbed without the service caring — matching the unified
:class:~rest_framework_services.services.CreateService Protocol's
default ExtraT (open extras).
update_model¶
update_model ¶
update_model(
model: type[ModelT],
*,
field_map: dict[str, str] | None = None,
exclude_fields: list[str] | None = None,
m2m: Mapping[str, Any] | Callable[[Any], Mapping[str, Any]] | None = None,
update_fields: bool | list[str] = True,
) -> Callable[..., ModelT]
Return a service callable that updates the resolved instance in place.
Equivalent to::
def update_author(*, instance: Author, data: AuthorIn, **_: Any) -> Author:
return update_from_input(instance, data).instance
model is accepted for symmetry with
:func:create_model / :func:delete_model and to bind ModelT for
the type checker; the instance itself comes from the view's
get_object(). field_map, exclude_fields, m2m, and
update_fields are forwarded to
:func:~rest_framework_services.mutations.update_from_input. m2m
accepts either a static mapping or a callable receiving the validated
data (see :func:create_model for the common shape).
delete_model¶
delete_model ¶
delete_model(
model: type[ModelT], *, soft_delete: Callable[[ModelT], None] | None = None
) -> Callable[..., None]
Return a service callable that deletes the resolved instance.
Equivalent to::
def delete_author(*, instance: Author, **_: Any) -> None:
instance.delete()
model is accepted for symmetry / type binding; the instance comes
from the view's get_object().
soft_delete is an optional hook called instead of
instance.delete() — covers the common archive case::
def _archive(instance: Author) -> None:
instance.is_archived = True
instance.save(update_fields=["is_archived"])
delete_model(Author, soft_delete=_archive)
acreate_model¶
acreate_model ¶
acreate_model(
model: type[ModelT],
*,
field_map: dict[str, str] | None = None,
exclude_fields: list[str] | None = None,
m2m: Mapping[str, Any] | Callable[[Any], Mapping[str, Any]] | None = None,
) -> Callable[..., Awaitable[ModelT]]
Async sibling of
:func:~rest_framework_services.services.create_model.
Returns an async def closure that wraps
:func:~rest_framework_services.mutations.acreate_from_input. The
framework's :func:~rest_framework_services.is_async.is_async
detection routes it through the async dispatch path automatically.
aupdate_model¶
aupdate_model ¶
aupdate_model(
model: type[ModelT],
*,
field_map: dict[str, str] | None = None,
exclude_fields: list[str] | None = None,
m2m: Mapping[str, Any] | Callable[[Any], Mapping[str, Any]] | None = None,
update_fields: bool | list[str] = True,
) -> Callable[..., Awaitable[ModelT]]
Async sibling of
:func:~rest_framework_services.services.update_model.
adelete_model¶
adelete_model ¶
adelete_model(
model: type[ModelT],
*,
soft_delete: Callable[[ModelT], Awaitable[None]] | None = None,
) -> Callable[..., Awaitable[None]]
Async sibling of
:func:~rest_framework_services.services.delete_model.
Calls await instance.adelete() by default (Django 4.1+; the
package floor is 4.2, so this is always available). soft_delete
is an optional async hook called instead of adelete.
Decorators¶
implements ¶
Identity decorator: assert fn structurally matches proto.
proto is a parameterised service or selector :class:~typing.Protocol::
@implements(CreateService[AuthorIn, Author])
def create_author(
*,
data: AuthorIn,
**extras: Any,
) -> Author: ...
Strict-typed extras stay on your function: declare a TypedDict with
NotRequired keys (so the function still conforms to a Protocol whose
caller may not supply those keys) and annotate
**extras: Unpack[YourKw]. The Protocol itself does not carry an
extras-shape parameter — see :class:CreateService for the rationale.
Drift between the decorated function and proto is reported at the
decorator line by ty. mypy refuses type[Protocol] arguments (the
type-abstract rule); mypy users either silence that with
# type: ignore[type-abstract] or keep using the legacy
_: CreateService[...] = create_author shim alongside the def.
Returns the function unchanged at runtime.
Helpers¶
call_service¶
call_service ¶
call_service(
service: Callable[..., ResultT],
*,
request: Request,
data: Any = UNSET,
instance: Any = UNSET,
**extras: Any,
) -> ResultT
Invoke service with the framework's kwargs pool.
request is required — the helper is HTTP-scoped by design. user
is derived from request.user (None if the request bypassed
authentication middleware), matching the framework's own pool
construction.
data and instance are passed through when not UNSET;
omitting them mirrors the create / list call shape. Anything else
goes into **extras and merges into the pool — the framework's
standard signature filter (:func:resolve_callable_kwargs) decides
which keys actually reach the service.
Async services are bridged transparently via async_to_sync;
sync services are called inline.
acall_service¶
acall_service
async
¶
acall_service(
service: Callable[..., ResultT] | Callable[..., Awaitable[ResultT]],
*,
request: Request,
data: Any = UNSET,
instance: Any = UNSET,
**extras: Any,
) -> ResultT
Invoke service from async code with the framework's kwargs pool.
Same contract as :func:call_service. Async services are awaited
directly; sync services are called inline (no thread hop) — caller is
responsible for any sync-side I/O safety.