drf-spectacular
drf-spectacular copied to clipboard
`@extend_schema_field` type hint does not accept simple python types
Describe the bug https://drf-spectacular.readthedocs.io/en/latest/customization.html#step-3-extend-schema-field-and-type-hints states that
In case of basic types (e.g. str, int, etc.) a type hint is already sufficient.
Which seems correct. However, the type hint does no represent that (see screenshot)
To Reproduce
extend_schema_field(list[str])
Example from source code: https://github.com/BesLogic/releaf-canopeum/blob/0f327ab8769fefc188be26f25ccd5eeeb331cfda/canopeum_backend/canopeum_backend/serializers.py#L142
Expected behavior
Supported simple python types to be allowed (type[int]
, type[str]
, type[list]
, etc.)
Even better if the list type is recursive (something like _ManyFieldTypes = type[list[_SingleFieldTypes] | _ManyFieldTypes]
, haven't tested, just writing off the top of my head), but list[Any]
is sufficient for our needs
CC @theo-auffeuvre & @NicolasDontigny for visibility on this
~~The type hint does not reflect that because list[..]
was not supported there.~~
~~We reflected on that deliberate choice and reversed it. list
and other higher order hints were added here: https://github.com/tfranzel/drf-spectacular/pull/1181~~
~~Please note that this change is not yet released.~~
~~But you actually raise a point here, because I think we have not updated the allowed types in the hints accordingly.~~
(I've updated the original description to add the link to our repo where this is used)
Scratch what I said, it was wrong. The feature I was referring to is basically the same thing, but applies somewhere else (not@extend_schema_field
). Shouldn't answer issues that late 😄
Okay so the type hint does not cover that, true, but it was not necessary because you are supposed write it normally for regular types.
def get_sponsors(self, obj) -> list[str]:
return self.context.get("sponsors")
should do just fine. The decorator is just for cases where an actual type hint (->) would be technically wrong and linters would complain (like with WidgetSerializer
or OpenApiTypes.EMAIL
).
Added a fix to fully support regular as well as higher order types (union, dict, list).
The hint now also includes basic types (str, int, ...). Unfortunately, it is tricky to create a type hint for higher order types that works with all supported py version (3.7-3.12) and at the same time satisfy mypy. If someone has a good idea, feel free to submit a PR.
So I would recommend to add a type: ignore
for those cases in the meantime. The functionality itself is there and it works.
def get_sponsors(self, obj) -> list[str]: return self.context.get("sponsors")
should do just fine. The decorator is just for cases where an actual type hint (->) would be technically wrong and linters would complain (like with WidgetSerializer or OpenApiTypes.EMAIL).
I see I forgot to mention why we even added extend_schema_field
in the first place in my example:
The generated OpenAPI schema resulted in a string
rather than a string[]
, hence we ended up having to explicitly add extend_schema_field
. Even though the result of self.context.get("sponsors")
was indeed a list[str]
Similarly with get_widget
below in my given example, many=True
wasn't automatically detected w/o the decorator.
If you're telling me it should've worked as-is w/o the decorator, I can open a different issue for that.
This was done in the context of a weekend hackathon where a handful of us had never even touched django before. So whatever worked quickly.
Okay so the type hint does not cover that, true, but it was not necessary because you are supposed write it normally for regular types.
def get_sponsors(self, obj) -> list[str]: return self.context.get("sponsors")
should do just fine. The decorator is just for cases where an actual type hint (->) would be technically wrong and linters would complain (like with
WidgetSerializer
orOpenApiTypes.EMAIL
).
Hello again! So yeah, you're right all I had to do was to use explicit return types to avoid having to use @extend_schema_field
in most cases, however, the case of list[str]
is still incorrect as is results in a list of "optional" strings!
Edit: see edit below, I had to use list[str]()
instead of []
for empty list fallback at runtime.
Python:
def get_sponsors(self, obj) -> list[str]:
return self.context.get("sponsors", [])
OpenAPI spec output:
"sponsors": {
"type": "array",
"readOnly": true,
"items": {
"type": "string"
}
},
Typescript output:
readonly sponsors!: (string | undefined)[];
Python:
# https://github.com/tfranzel/drf-spectacular/issues/1212
@extend_schema_field(list[str]) # type: ignore[arg-type] # pyright: ignore[reportArgumentType]
def get_sponsors(self, obj) -> list[str]:
return self.context.get("sponsors", [])
edit: or this actually works in more recent python versions
def get_sponsors(self, obj) -> list[str]:
return self.context.get("sponsors", list[str]())
OpenAPI spec output:
"sponsors": {
"type": "array",
"readOnly": true,
"items": {
"type": "string"
}
},
Typescript output:
readonly sponsors!: string[];