Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

How to specify list[Schema] response with a different response statuses? #1436

Unanswered
XCanG asked this question in Q&A
Discussion options

There is currently some problem with that, if I had something like 200 and 403 responses I could write:

@router.get("/url", response={200: SomeSchema, 403: OtherSchema}, auth=...)
def some_func(...):
 if no_perms: return 403, OtherSchema()
 ...
 data = ...
 return 200, [SomeSchema.model_validate(row) for row in data]

This will work fine, but if I use that:

@router.get("/url", response={200: list[SomeSchema], 403: OtherSchema}, auth=...)
def some_func(...):
 if no_perms: return 403, OtherSchema()
 ...
 data = ...
 return 200, [SomeSchema.model_validate(row) for row in data]

It will complain about schema, as if it doesn't have proper fields, but it actually does, since it initiated and validated by Schema that used Pydantic behind scene.

Additionally I find some loop problem here, I tried to specify tuple[...] and list[...] with different variations and if I put list[Literal[200] | list[SomeSchema]] it will try to revalidate same output until it crashes, in my case it did loop for 6453 times based on console output and then get InternalServerError and crashes.

You must be logged in to vote

Replies: 1 comment 7 replies

Comment options

@XCanG hard to tell - you need to provide code for your SomeSchema and OtherSchema

You must be logged in to vote
7 replies
Comment options

@XCanG can be ton of reasons - cannot guess..

on the other hand I see that you use model_validate in responses - you do not have to do this as django-ninja does that automatically

here is a minimal working example of your initial example:

from ninja import NinjaAPI, Schema
api = NinjaAPI()
class SomeSchema(Schema):
 id: int
 name: str
class OtherSchema(Schema):
 code: int
 message: str
@api.get("/url", response={200: list[SomeSchema], 403: OtherSchema})
def some_func(request, action: str = ''):
 if action == "show_403":
 return 403, {"code": 42, "message": "some message"}
 data = [{'id': 1, 'name': 'John'}, {'id': 2, 'name': 'Sarah'}]
 return 200, data

200 reponse:
SCR-20250404-qlhx

403 path:

SCR-20250404-qlke
Comment options

model_validate also should work - but you do not have to do that as you will end up double validation:

@api.get("/url", response={200: list[SomeSchema], 403: OtherSchema})
def some_func(request, action: str = ''):
 if action == "show_403":
 return 403, OtherSchema(code=42, message="some message")
 data = [{'id': 1, 'name': 'John'}, {'id': 2, 'name': 'Sarah'}]
 return 200, [SomeSchema.model_validate(row) for row in data]
Comment options

Ah, didn't know about validation. But I used that because raw django response didn't work out by default.

Since your example seems to work for you, then I will provide more context. 403 used to block users without permissions so the model is actually very minimal (that one that I called OtherSchema):

class BooleanResponse(Schema):
 result: bool

But for the case of the list schemas, I use it in various parts of the code, so in all places it didn't work for me. But example of one schema:

class Payment_Request(Schema):
 amount: Decimal = Field(..., ge=10_000, le=1_000_000)
 company: str = Field(..., max_length=510)
 address: str = Field(..., max_length=510)
 inn: str = Field(..., max_length=510)
 kpp: str = Field(..., max_length=510)
 rsch: str = Field(..., max_length=510)
 bn: str = Field(..., max_length=510)
 kpsch: str = Field(..., max_length=510)
 bik: str = Field(..., max_length=510)
 phone: str = Field(..., max_length=20)
 email: str = Field(..., max_length=255)
class Seller_InvoiceDB(Payment_Request):
 id: int
 amount: Decimal
 seller: SellerDB
 date: datetime
 payment_confirmed: bool = Field(default=False)
 accepted_at: datetime | None = Field(default=None)
 accepted_by: UserDB | None = Field(default=None)

In this case it return {200: list[Seller_InvoiceDB], 403: BooleanResponse} and an example of endpoint:

@router.get(
 "/invoices/{seller_id}",
 response={200: list[Seller_InvoiceDB], 403: BooleanResponse},
 auth=header_key,
)
@paginate
def get_seller_invoices(
 request: HttpRequest,
 seller_id: int,
) -> tuple[Literal[200], Sequence[Seller_InvoiceDB]] | tuple[Literal[403], BooleanResponse]:
 if (extra := validate_permissions(request.user, (UserType.director, UserType.accountant))):
 return status.HTTP_403_FORBIDDEN, BooleanResponse(result=False)
 return status.HTTP_200_OK, [Seller_InvoiceDB.model_validate(inv) for inv in Seller_Invoice.objects.filter(seller_id=seller_id).order_by("-id")]

But I would say that mostly all this comes out to proper schema (OpenAPI and Swagger), rather than actual validation and this is the reason why I putting it in response in the first place.
There sometimes cases where I had to work with the data before proceed to return it to requester, so I would like to have some solution that prevent double validation if I already returning Pydantic schemas.

Comment options

@XCanG well I still do not understand from your description - what's the problem ? :)
Do you see some error ? do you need multiple statuses for your response ?

Comment options

Could you isolate some small example without lot of fields where you can reproduce the "crash" ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Category
Q&A
Labels
None yet
2 participants

AltStyle によって変換されたページ (->オリジナル) /