django-ninja
django-ninja copied to clipboard
Pydantic context in response
I am trying to use Pydantic context to add different fields to the response dynamically. For example:
Based on the include_admin_part
flag, this schema will include the admin_part
class _JobResponseAdminPartSchema(ModelSchema):
"""
The optional admin part of the response schema for job api
"""
class Meta:
model = Job
fields = [...]
class JobResponseSchema(ModelSchema):
admin_part: Optional[_JobResponseAdminPartSchema] = Field(default=None)
@staticmethod
def resolve_admin_part(
job: Job, context: dict[str, bool]
) -> Optional[_JobResponseAdminPartSchema]:
if context.get("include_admin_part"):
return _JobResponseAdminPartSchema.from_orm(job)
return None
class Meta:
model = Job
fields = [...]
The method above works when I call this:
JobResponseSchema.from_orm(job_obj, {"include_admin_part": True}) # Works!
However, when I added the response option in @router.get
, the response data was passed into JobResponseSchema
again without the include_admin_part
context!:
@router.get(
"/",
response={
200: JobResponseSchema,
400: ErrorSchema,
401: ErrorSchema,
403: ErrorSchema,
},
)
One solution is to remove response
entirely, and then it works. However I need the response typing in Swagger for typescript generation.
Can I either
- Disable the auto response serializing of an API?, like this:
@router.get(
"/",
response={
200: JobResponseSchema,
400: ErrorSchema,
401: ErrorSchema,
403: ErrorSchema,
},
parse_response=False,
)
- Or, when I return from the API function, I can provide my context, like this.
return job, {"include_admin_part": True}
Thank you!
@kingychiu there are no mechanics to pass context yet from view function...
maybe something like could work in future:
@api.get("/some", response=SchemaWithContext)
def some(request...)
return WithContext({"some": "data"}, context={"some": "context"})
but as for now you can use request object to pass extra context to Schema:
class JobResponseSchema(ModelSchema):
admin_part: Optional[_JobResponseAdminPartSchema] = Field(default=None)
@staticmethod
def resolve_admin_part(job, context):
request = context["request"]. # <------- !
if request.include_admin_part:
return {extra}
@api.get("/some", response=JobResponseSchema)
def some(request...)
request.include_admin_part = True # <------- !
return job
Thanks @vitalik; yeah, I am looking forward to official support!
For now, I found another workaround that solved the issue.
# from django.http import JsonResponse
response_dict = JobResponseSchema.from_orm(job_obj, {"include_admin_part": True}).dict()
return JsonResponse(data=response_dict)
By doing this, I can trigger the schema in my view function + having response
defined in my @router.get
. It seems like JsonResponse
will be ignored (Not parsing again) as a return type.
For your SchemaWithContext
suggestion, do you think we can have inferred types in the resolve_admin_part
? I think now the context
of the resolver__
functions is set to Any
type.
@kingychiu
the WithContext
can be just a special django-ninja wrapper that will just notify renderer to include passed context to a desired response
I would just like to add that I'm currently looking for this exact functionality. So I'm supporting adding it and thanks for giving some pointers on how to work around it for the time being.