Skip to content

FastAPI does not respect __wrapped__ attribute, causing NameError for forward references in decorated functions. #5065

@lucaswiman

Description

@lucaswiman

First Check

  • I added a very descriptive title to this issue.
  • I used the GitHub search to find a similar issue and didn't find it.
  • I searched the FastAPI documentation, with the integrated search.
  • I already searched in Google "How to X in FastAPI" and didn't find any information.
  • I already read and followed all the tutorial in the docs and didn't find an answer.
  • I already checked if it is not related to FastAPI but to Pydantic.
  • I already checked if it is not related to FastAPI but to Swagger UI.
  • I already checked if it is not related to FastAPI but to ReDoc.

Commit to Help

  • I commit to help with one of those options 👆

Example Code

# This needs to be in two files because the nature of the bug involves different globalns
# entries for a decorated function versus the module of the original function.
# If you put the code here into two files (func.py and main.py) in a directory, then the
# bug can be reproduced by running:
# PYTHONPATH=.:$PYTHONPATH python ./main.py
# This should cause an exception `NameError: name 'MyModel' is not defined`

# func.py
from pydantic import BaseModel


def foo(obj: "MyModel") -> "MyModel":
    return obj


class MyModel(BaseModel):
    x: int

# main.py
import functools, typing

from fastapi import FastAPI
from pydantic import BaseModel
from func import foo

app = FastAPI()


def deco(func):
    @functools.wraps(func)
    def passthrough(*args, **kwargs):
        return func(*args, **kwargs)
    return passthrough

decorated = deco(foo)

assert issubclass(typing.get_type_hints(decorated)["obj"], BaseModel)
assert decorated.__annotations__ == foo.__annotations__
assert isinstance(decorated.__annotations__["obj"], str)

method = app.post('/endpoint')(decorated)

Description

Using a decorator employing functools.wraps or a similar causes a NameError in fastapi/dependencies/utils.py in get_typed_annotation. The implementation there differs from the implementation in typing, which dereferences the __wrapped__ attribute to find the namespace the original function was defined in. This allows forward references to be handled correctly:
https://github.com/python/cpython/blob/576dd901170af30fc50b0a7f07a388b38fd724a9/Lib/typing.py#L2314-L2315

In the "Example Code" section, my example includes assertions showing that typing.get_type_hints handles this situation correctly. I believe this can be fixed either by using the same implementation as typing uses, or by calling typing.get_type_hints, and only doing special FastAPI-specific logic if that fails.

Operating System

macOS

Operating System Details

I first saw this on Ubuntu Linux with python 3.8, then later reproduced it on my Macbook in python 3.9. The example code was tested on the Mac.

I do not believe the bug is related to operating systems. I think it's a bug in how pure python code handles type annotations.

FastAPI Version

0.78.0

Python Version

Python 3.9.1

Additional Context

This bug is unlikely to come up in most ordinary code, but is likely to come up when using metaprogramming or decorators (especially in programmatically adding endpoints). The context I found it in was in a in-house workflow framework we are building at my job. The framework uses some decorators to alter the type signature of methods, and passes them to FastAPI to expose task endpoints. This leads to the above error.

I definitely understand that such an odd use case is probably not high priority for the maintainers, but I hope you'll be willing to review a PR from me fixing the bug.

Thank you for all your work on FastAPI! It is an amazingly well-made piece of software.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions