axnsan12 / drf-yasg

Automated generation of real Swagger/OpenAPI 2.0 schemas from Django REST Framework code.
https://drf-yasg.readthedocs.io/en/stable/
Other
3.4k stars 437 forks source link

`SerializerMethodField` return type is always 'STRING' when a file uses `from __future__ import annotations` due to PEP 563 #866

Open derlin opened 1 year ago

derlin commented 1 year ago

Bug Report

Description

PEP 563 – Postponed Evaluation of Annotations is coming along, and breaks the inference of return types in SerializerMethodField. In short:

This PEP proposes changing function annotations and variable annotations so that they are no longer evaluated at function definition time. Instead, they are preserved in __annotations__ in string form.

This change is being introduced gradually, starting with a future import in Python 3.7.

It isn't yet activated by default, but gets activated on any file importing:

from __future__ import annotations

When this feature is on, https://github.com/axnsan12/drf-yasg/blob/1.21.7/src/drf_yasg/inspectors/field.py#L616 fails to guess the return type, since it assumes the type returned is actually a Type, but it is now a str (see example below).

This will be completely broken once PEP 563 becomes the default.

Is this a regression?

Well, yes and no. It is a regression in the sense that the from __future__ import annotations is more and more often used, as it solves some problems such as forward references and cyclic imports.

Minimal Reproduction

Here is an example of the problem:

from __future__ import annotations
from rest_framework import serializers

class MySerializer(serializers.Serializer):
   # This allows to return something that is not a direct
   # attribute of a model, but a "computed" value
   is_foo = serializers.SerializerMethodField()

   # The type hint is important for drf-yasg to
   # guess the proper type
   def get_is_foo(self, obj) -> bool:
      return obj.is_foo

In the above example, drf-yasg will wrongly assume {"is_foo": "STRING"} instead of {"is_foo": "BOOLEAN"}, because inspect.get_signature(is_foo) returns 'bool' (str) and not bool (type).

Possible fixes

In order to un-stringize the type, Python 3.10 introduced a new method, inspect.get_annotations(f, eval_str=True) and ported the eval_str parameter to inspect.signature. For Python 3.9 and older, this needs to be done manually.

The possible fixes are thus:

derlin commented 1 year ago

See this blog post for more information Python, type hints, and future annotations

derlin commented 9 months ago

ping?