Skip to content

When using postponed annotations (from __future__ import annotations), class based dependencies fail to resolve their dependencies with NameErrorr #4557

@waweber

Description

@waweber

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

from __future__ import annotations

from fastapi import FastAPI, Depends

app = FastAPI()

CoolType = int

def get_cool_type() -> int:
    return 123

class MyClass:
    def __init__(self, val: CoolType = Depends(get_cool_type)):
        self.val = val

def get_my_class(val: CoolType = Depends(get_cool_type)):
    return MyClass

@app.get("/")
def my_view(mycls: MyClass = Depends()):
    return mycls.val

@app.get("/test2")
def my_view_working(mycls: MyClass = Depends(get_my_class)):
    return mycls.val

Description

Run the example app, and you will get a NameError because CoolType cannot be resolved. If you comment out the first view, the program will work correctly. The second view just uses a function to construct and return MyClass instead using MyClass as a dependency directly.

A possible root of the problem is in fastapi.dependency.utils:get_typed_signature:

def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
    signature = inspect.signature(call)
    globalns = getattr(call, "__globals__", {})
    ...

To resolve the annotations, this function uses the dependency's __globals__ property to look up the names. When call is a function, this works just fine. When call is a class, it doesn't have a __globals__, so we get an empty namespace as a fallback.

However, when we specify a class as a dependency, what we really care about is MyClass.__init__. This is a function, and it has a __globals__ containing all the types we need to resolve its dependencies.

A possible fix for this would be to make this function check if call is a class and use it's __init__ method instead.

Operating System

Linux

Operating System Details

No response

FastAPI Version

0.73.0

Python Version

Python 3.10.2

Additional Context

Also using pydantic 1.9.0

See also this related issue: #1654

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