Decorator-based gRPC integration for Django.
Project description
django-grpc-extra
django-grpc-extra provides a decorator-based workflow for building gRPC APIs in Django:
- declare services and RPC methods with decorators
- generate
.protofiles (and optionallypb2/pb2_grpc) - run a gRPC server with auto-discovery, request conversion, and optional reload/logging
Acknowledgements
This project is inspired by:
Quick Start (5 minutes)
- Install the package:
pip install "django-grpc-extra[codegen]"
-
Add
grpc_extratoINSTALLED_APPS. -
Scaffold gRPC folders for your app:
python -m django init_grpc --app example_app
- Create
example_app/grpc/services.py:
from pydantic import BaseModel
from grpc_extra import grpc_method, grpc_pagination, grpc_service
class PingRequest(BaseModel):
message: str
class PingResponse(BaseModel):
message: str
@grpc_service(app_label="example_app", package="example_app")
class ExampleService:
@grpc_method(request_schema=PingRequest, response_schema=PingResponse)
def ping(self, request, context):
return {"message": request.message}
- Generate proto and pb2 files:
python -m django generate_proto --app example_app
- Run the server (without creating a custom entrypoint module):
python manage.py run_grpcserver
Or run it from your own module if you prefer:
import os
import django
from grpc_extra import GrpcExtra
def main() -> None:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings")
django.setup()
GrpcExtra(pattern="{app}.grpc.services").run_server()
if __name__ == "__main__":
main()
PYTHONPATH=app python -m example_project.grpc_server
Installation
pip install django-grpc-extra
Optional extras:
pip install "django-grpc-extra[codegen]" # grpcio-tools
pip install "django-grpc-extra[health]" # health checking
pip install "django-grpc-extra[reflection]" # server reflection
pip install "django-grpc-extra[reload]" # watchfiles live reload
1) Scaffold gRPC folders in Django apps
Create grpc/ structure in one app:
python -m django init_grpc --app example_app
Options:
--app <label>: target app label (repeatable)--all: all installed apps--force: overwrite existing files
If no options are provided, it behaves like --all.
2) Declare services and methods with decorators
Create your_app/grpc/services.py:
from pydantic import BaseModel
from grpc_extra import grpc_method, grpc_service
class PingRequest(BaseModel):
message: str
class PingResponse(BaseModel):
message: str
@grpc_service(
name="ExampleService",
app_label="example_app",
package="example_app",
proto_path="grpc/proto/example_app.proto",
)
class ExampleService:
@grpc_method(
request_schema=PingRequest,
response_schema=PingResponse,
)
def ping(self, request, context):
# request is already PingRequest (Pydantic), not pb2.
return {"message": f"pong: {request.message}"}
@grpc_method(
request_schema=PingRequest,
response_schema=PingResponse,
server_streaming=True,
)
def ping_stream(self, request, context):
return [
{"message": f"chunk-1: {request.message}"},
{"message": f"chunk-2: {request.message}"},
]
@grpc_method(response_schema=PingResponse)
@grpc_pagination() # must be placed under @grpc_method
def list_ping(self, request, context):
return [
{"message": "a"},
{"message": "b"},
{"message": "c"},
]
Runtime conversion behavior
- Request:
pb2 -> request_schema (Pydantic) - Response:
dict/Pydantic/dataclass/model -> pb2 - Stream responses support iterables and objects with
.iterator() Decimalvalues are coerced tostringbefore pb2 encoding (useful for DjangoDecimalFieldmapped to protostring)ValidationErrormaps toINVALID_ARGUMENTPermissionErrormaps toPERMISSION_DENIEDgrpc_paginationapplies pagination in runtime and augments proto schemas
Naming behavior
- Python method names can stay
snake_case(PEP 8), for exampleget_user. - If
name=is not provided, RPC name is converted toUpperCamelCase, for exampleGetUser. - Service class names are used as-is (typically already
UpperCamelCase). - If you need strict naming, pass
name=...explicitly. - Message names are normalized for top-level method schemas:
PingSchema->PingRequest/PingResponsePing->PingRequest/PingResponse
2.1) Build CRUD gRPC methods from model config
Use ModelService when you want prebuilt CRUD methods generated from a config.
from django.db import models
from pydantic import BaseModel, ConfigDict, Field
from grpc_extra import (
AllowedEndpoints,
ModelFilterSchema,
ModelService,
ModelServiceConfig,
grpc_service,
)
class Product(models.Model):
name = models.CharField(max_length=100)
class Meta:
app_label = "example_app"
class ProductOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: int
name: str
class ProductCreate(BaseModel):
name: str
class ProductListFilter(ModelFilterSchema):
name: str | None = None
ids: list[int] | None = Field(default=None, json_schema_extra={"op": "in", "field": "id"})
min_id: int | None = Field(default=None, json_schema_extra={"op": "gte", "field": "id"})
@grpc_service(app_label="example_app", package="example_app")
class ProductService(ModelService):
config = ModelServiceConfig(
model=Product,
allowed_endpoints=[
AllowedEndpoints.LIST,
AllowedEndpoints.STREAM_LIST,
AllowedEndpoints.DETAIL,
AllowedEndpoints.CREATE,
],
list_schema=ProductOut,
list_filter=ProductListFilter,
detail_schema=ProductOut,
create_schema=ProductCreate,
)
This class generates and registers RPC handlers on the service class itself. For this config, the service gets:
List(single response with pagination metadata andresults)StreamList(server-streaming)DetailCreate
Detail maps missing objects to NOT_FOUND (ObjectDoesNotExist -> gRPC NOT_FOUND).
List uses pagination by default via DEFAULT_PAGINATION_CLASS.
Set list_pagination_class=None in ModelServiceConfig to disable pagination.
list_filter is used for both List and StreamList request schemas.
Default helper supports these filter operators via field metadata:
exact(default)innot_inltgtltegte
You can customize data access by providing data_helper_class.
The helper must inherit ModelDataHelper and implement CRUD methods.
from grpc_extra import ModelDataHelper
class CustomDataHelper(ModelDataHelper):
def list_objects(self, request):
...
def get_object(self, request):
...
def create_object(self, request):
...
def update_object(self, request):
...
def patch_object(self, request):
...
def delete_object(self, request):
...
2.2) Permissions
Permissions can be declared:
- at service level via
@grpc_service(..., permissions=[...]) - at method level via
@grpc_method(..., permissions=[...])or@grpc_permissions(...)under@grpc_method - service-level permissions are the default policy
- explicit method-level permissions override service-level permissions
Permission contract:
has_perm(self, request, context, service, method_meta) -> boolhas_obj_perm(self, request, context, service, method_meta, obj) -> bool
Runtime behavior:
- service-level:
has_permfor all methods - method-level:
has_permandhas_obj_perm - for
Detail/Get: service-levelhas_obj_permis also applied
3) Generate .proto and pb2
Generate proto and compiled files for one app:
python -m django generate_proto --app example_app
Default proto location:
<app>/grpc/proto/<app>.proto
Generate only .proto:
python -m django generate_proto --app example_app --no-compile
Generate .pyi as well:
python -m django generate_proto --app example_app --pyi
4) Manual protoc (optional)
If you want to run protoc yourself:
python -m grpc_tools.protoc \
-I app \
--python_out=app \
--grpc_python_out=app \
app/example_app/grpc/proto/example_app.proto
With .pyi:
python -m grpc_tools.protoc \
-I app \
--python_out=app \
--grpc_python_out=app \
--pyi_out=app \
app/example_app/grpc/proto/example_app.proto
5) Run the gRPC server
Option A: manage.py run_grpcserver (quick local testing)
python manage.py run_grpcserver
Common flags:
--bind [::]:50051--max-workers 10--max-msg-mb 32--health / --no-health--reflection / --no-reflection--reload / --no-reload--reload-path app(repeatable)--discover / --no-discover--auth-backend path.to.auth_backend--interceptor path.to.interceptor(repeatable)--request-logging / --no-request-logging--logger-name grpc_extra
Examples:
python manage.py run_grpcserver --bind [::]:50051 --health --reflection
python manage.py run_grpcserver --reload --reload-path app
python manage.py run_grpcserver --auth-backend path.to.auth_backend --interceptor path.to.interceptor
Option B: custom server entrypoint module
Create a server entrypoint module:
# app/example_project/grpc_server.py
import os
import django
from grpc_extra import GrpcExtra
def main() -> None:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example_project.settings")
django.setup()
grpc_extra = GrpcExtra(pattern="{app}.grpc.services")
grpc_extra.run_server()
if __name__ == "__main__":
main()
Run it:
PYTHONPATH=app python -m example_project.grpc_server
For development live reload:
grpc_extra.run_server(reload=True, reload_paths=("app",))
6) Django settings integration
grpc_extra reads runtime config from GRPC_EXTRA:
GRPC_EXTRA = {
"ENABLE_HEALTH": True,
"ENABLE_REFLECTION": False,
"ENABLE_REQUEST_LOGGING": True,
"LOGGER_NAME": "grpc_extra",
"BIND": "[::]:50051",
"MAX_WORKERS": 10,
"MAX_MSG_MB": 32,
"AUTH_BACKEND": "path.to.auth_backend", # optional
"EXCEPTION_MAPPER": "path.to.mapper", # optional
"SCHEMA_SUFFIX_STRIP": ("Schema",), # optional
"REQUEST_SUFFIX": "Request", # optional
"RESPONSE_SUFFIX": "Response", # optional
"DEFAULT_PAGINATION_CLASS": "grpc_extra.pagination.LimitOffsetPagination", # optional
"ENABLE_RELOAD": False, # optional
"RELOAD_PATHS": ("app",), # optional
}
Notes:
AUTH_BACKEND: callable, class (instantiated without args), or import path- auth result semantics:
FalseorNonemeansUNAUTHENTICATED EXCEPTION_MAPPER: callable(exc: Exception) -> MappedErroror import path to callable (class is not supported)SCHEMA_SUFFIX_STRIP,REQUEST_SUFFIX,RESPONSE_SUFFIX: proto message naming adapter for top-levelrequest_schema/response_schemaDEFAULT_PAGINATION_CLASS: default class used by@grpc_pagination()andModelServicelist endpoint- request logs go to logger defined by
LOGGER_NAMEand then through standard DjangoLOGGING
7) Generate Python SDK (experimental)
Status: Experimental
SDK generator behavior and generated file layout may change between releases.
Generate SDK from discovered proto files:
python -m django generate_client_sdk --language python --all
Common options:
--language python--out <path>--name <sdk-package-name>--app <label>(repeatable)--all--skip-proto(skip proto regeneration, use existing proto files)
Generated layout
SDK now uses per-app generated modules to avoid huge monolithic files:
<sdk>/src/<package>/
<app>/grpc/proto/ # generated protobuf artifacts
*_pb2.py
*_pb2_grpc.py
*.pyi
client.py # custom layer (not overwritten if exists)
client_generated.py # regenerated
helpers.py # custom-safe helper module (created if missing)
services.py # facade, regenerated
typed_services.py # facade, regenerated
models.py # facade, regenerated
generated/
<app>/
services.py # regenerated
typed_services.py # regenerated
models.py # regenerated
Files that are safe for your custom code:
client.pyhelpers.py
Files that are always regenerated:
client_generated.pyservices.pytyped_services.pymodels.py- everything in
generated/<app>/
Raw vs typed client
from my_sdk import ClientConfig, GrpcClient, extract_results, message_to_dict
client = GrpcClient(ClientConfig(host="localhost:50051"))
# Raw protobuf response
raw_resp = client.product.list()
raw_dict = message_to_dict(raw_resp)
rows = extract_results(raw_resp) # for paginated list envelopes
# Typed response (Pydantic models generated from proto)
typed_resp = client.typed.product.list()
Notes:
client.<service>.<method>()-> raw protobuf messages (*_pb2).client.typed.<service>.<method>()-> Pydantic models generated from proto schema.extract_results(...)is a convenience helper for paginated list responses.
8) Recommended .gitignore entries
If generated files are not committed:
app/**/grpc/**/proto/*_pb2.py
app/**/grpc/**/proto/*_pb2_grpc.py
app/**/grpc/**/proto/*_pb2.pyi
9) Common issues
ModuleNotFoundError: <name>_pb2:
- proto was generated with wrong include path or output path
- re-run generation with correct
-Iand output directories
RESOURCE_EXHAUSTED:
- message size limits are too small
- increase
MAX_MSG_MB(server) and client message limits
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_grpc_extra-0.2.4.tar.gz.
File metadata
- Download URL: django_grpc_extra-0.2.4.tar.gz
- Upload date:
- Size: 50.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb5bf853bfa93740b122369aaf78223e407879c60413c7ae7d5c1a5c4f531fb9
|
|
| MD5 |
fce2bc99712979dfa33cee9eb084ffd3
|
|
| BLAKE2b-256 |
da7bcf7e6c1822cb14ac0bebb59d467c6b2f0bc67993212b32de3854b03da841
|
Provenance
The following attestation bundles were made for django_grpc_extra-0.2.4.tar.gz:
Publisher:
publish.yml on QtiQla/django-grpc-extra
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_grpc_extra-0.2.4.tar.gz -
Subject digest:
bb5bf853bfa93740b122369aaf78223e407879c60413c7ae7d5c1a5c4f531fb9 - Sigstore transparency entry: 1227236233
- Sigstore integration time:
-
Permalink:
QtiQla/django-grpc-extra@0650c20bb4ed7f0159fae0e25a7de9925c64670f -
Branch / Tag:
refs/tags/0.2.4 - Owner: https://github.com/QtiQla
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0650c20bb4ed7f0159fae0e25a7de9925c64670f -
Trigger Event:
release
-
Statement type:
File details
Details for the file django_grpc_extra-0.2.4-py3-none-any.whl.
File metadata
- Download URL: django_grpc_extra-0.2.4-py3-none-any.whl
- Upload date:
- Size: 58.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eece544b41b0480c69586a376aa556f6dc4ec4f9db421176a87167dfcb1513aa
|
|
| MD5 |
96f831cecefe684739d64072b9d68a84
|
|
| BLAKE2b-256 |
e2aee503038b5ccdcd20165eb5204d3151cfc070508f52339671e1cee7d0b911
|
Provenance
The following attestation bundles were made for django_grpc_extra-0.2.4-py3-none-any.whl:
Publisher:
publish.yml on QtiQla/django-grpc-extra
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
django_grpc_extra-0.2.4-py3-none-any.whl -
Subject digest:
eece544b41b0480c69586a376aa556f6dc4ec4f9db421176a87167dfcb1513aa - Sigstore transparency entry: 1227236318
- Sigstore integration time:
-
Permalink:
QtiQla/django-grpc-extra@0650c20bb4ed7f0159fae0e25a7de9925c64670f -
Branch / Tag:
refs/tags/0.2.4 - Owner: https://github.com/QtiQla
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0650c20bb4ed7f0159fae0e25a7de9925c64670f -
Trigger Event:
release
-
Statement type: