Runtime
This section describes what happens between incoming gRPC request and your service method result.
High-level Flow
For each RPC call runtime performs:
- decode incoming pb2 request into Pydantic schema
- apply service/method permissions
- call your Python method
- apply searching/ordering/pagination (if configured)
- encode method result into outgoing pb2 response
- map exceptions to gRPC status
Request Decode
Request decode function:
- input: protobuf message instance
- output:
request_schemaPydantic model (or raw object if schema isNone)
Unary request
@grpc_method(request_schema=PingRequest, response_schema=PingResponse)
def ping(self, request, context):
# request is PingRequest here
return {"message": request.message}
Stream request
For client_streaming=True, runtime decodes each incoming item lazily.
Response Encode
Encoder accepts these return types:
dictpydantic.BaseModel- dataclass instance
- Django model instance
- generic object with attributes
Then it validates against response_schema and constructs pb2 response.
Supported Method Shapes
All four gRPC shapes are supported:
- unary-unary
- unary-stream
- stream-unary
- stream-stream
Streaming Rules
- For server-streaming and bidi methods your method must return iterable.
- For stream-unary pagination is not supported.
- For stream responses object permissions are checked per yielded item.
Search/Order/Pagination Pipeline
For unary list-like responses runtime applies modifiers in this order:
- searching
- ordering
- pagination
This order is used in ModelService list endpoints and decorator-based methods.
Collection and Wrapper Behavior
Top-level list response
If you declared:
@grpc_method(response_schema=list[ItemSchema])
runtime auto-wraps output into internal schema:
{"items": [...]}
Works with:
listQuerySet- iterable objects
- objects exposing
.iterator()
Decimal handling
Decimal values are coerced to string before pb2 construction.
Useful when Django DecimalField maps to proto string.
Permissions in Runtime
Checks are executed before/after method call depending on type:
- service-level
has_permfor all methods - method-level
has_permfor all methods - method-level
has_obj_permfor object/response checks - service-level
has_obj_permalso applies toDetail/Get
Exception Mapping
Default mapping:
pydantic.ValidationError->INVALID_ARGUMENT- request decode with validation cause ->
INVALID_ARGUMENT ObjectDoesNotExist->NOT_FOUNDPermissionError->PERMISSION_DENIEDOrderingError/SearchingError->INVALID_ARGUMENTRequestDecodeError/ResponseEncodeError->INTERNAL- any other exception ->
UNKNOWN
Custom Exception Mapper
You can override mapper in settings:
GRPC_EXTRA = {
"EXCEPTION_MAPPER": "path.to.custom_exception_mapper",
}
Mapper contract:
from grpc_extra.exceptions import MappedError
def custom_exception_mapper(exc: Exception) -> MappedError:
...
EXCEPTION_MAPPER must resolve to callable (function/callable object), not class type.
Runtime Pitfalls
1) invalid wire type
Usually client descriptor is stale after proto changes.
Fix:
- regenerate proto/pb2
- restart server
- recreate gRPC connection in client tool
2) Failed to encode response
Most often schema/payload mismatch.
Typical causes:
- wrong field type in returned dict/model
- relation object returned where scalar is expected
- stale proto versus current schema
3) Search/order silently not applied
Ensure fields are configured explicitly in decorators/model config.
4) Auth backend rejects before token validate
Most often metadata extraction mismatch (header/scheme/value format).
Performance Notes
- Prefer returning
QuerySetfor large data, let runtime iterate lazily where possible. - For model-heavy endpoints define
queryset/detail_querysetwithselect_related/prefetch_related. - Keep response schemas tight; avoid expensive nested relations unless needed.