drf-spectacular icon indicating copy to clipboard operation
drf-spectacular copied to clipboard

`@extend_schema_field` type hint does not accept simple python types

Open Samuel-Therrien-Beslogic opened this issue 11 months ago • 6 comments

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]) image (1)

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.~~

tfranzel avatar Mar 23 '24 23:03 tfranzel

(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).

tfranzel avatar Mar 24 '24 00:03 tfranzel

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.

tfranzel avatar Mar 31 '24 20:03 tfranzel

    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 or OpenApiTypes.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[];