question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Forward return type of injected dependencies with Depends()

See original GitHub issue

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

"""
I have a dependency that specifies a return type, but that type is erased 
when I inject it into an endpoint using `Depends()`
"""
from fastapi import FastAPI, Depends

app = FastAPI()

async def dependency() -> int:
    return 0

@app.get("/")
async def root(dep = Depends(dependency)):
    # Get mypy to emit the return type of the value returned by the dependency
    reveal_type(dep)  # Revealed type is "Any"

Description

When injecting dependency it into my view function with Depends(dependency), the return type (int) is erased and replaced with Any.

This can be verified by running mypy against my code sample above (reveal_type() should cause mypy to emit the type it infers)

Wanted Solution

It would be great if the return type of dependency were preserved, i.e.

@app.get("/")
async def root(dep = Depends(dependency)):
    reveal_type(dep)  # Revealed type is "int"

Depends is currently defined as:

def Depends(dependency: Optional[Callable[..., Any]] = None, *, use_cache: bool = True) -> Any:

I’m no expert on the Python type system or Mypy, but I think the following could get Mypy to correctly infer the return type of dependency:

from typing import TypeVar, Awaitable, Union

T_ = TypeVar("T_")
MaybeAwaitable = Union[T_, Awaitable[T_]]

T = TypeVar("T")
def Depends(dependency: Optional[Callable[..., MaybeAwaitable[T]]] = None, *, use_cache: bool = True) -> T:
    return params.Depends(dependency=dependency, use_cache=use_cache)

Wanted Code

"""
The return type of my dependency should be preserved 
when injecting it with `Depends(dependency)`
"""
from fastapi import FastAPI, Depends

app = FastAPI()

async def dependency() -> int:
    return 0

@app.get("/")
async def root(dep = Depends(dependency)):
    reveal_type(dep)  # Revealed type is "int"

Alternatives

No response

Operating System

macOS

Operating System Details

No response

FastAPI Version

0.75.0

Python Version

3.7.9

Additional Context

My current approach is to explicitly annotate my injected dependencies, but this is prone to error since Mypy doesn’t help catch these issues.

Consider:

from fastapi import FastAPI, Depends

app = FastAPI()

# Returns a different type now
async def dict_dependency() -> dict:
    return {}

@app.get("/")
async def root(dict_dep: int = Depends(dict_dependency)):
	# But Mypy doesn't catch the issue
    reveal_type(dict_dep)  # Revealed type is "int"

Issue Analytics

  • State:open
  • Created a year ago
  • Reactions:2
  • Comments:14 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
invokermaincommented, Apr 25, 2022

Is there not a middle ground where we can forward the return type if we are 100% sure, and otherwise return Any? It’s a massive shame to discard this completely because of how useful it would be for FastAPI users? Something like:

_DependenciesWithYield = Union[
    Callable[..., Iterable[T]],
    Callable[..., Generator[T, None, None]],
    Callable[..., AsyncIterable[T]],
    Callable[..., AsyncGenerator[T, None]],
]

@typing.overload
def Depends(dependency: None = None) -> Any:
    ...


@typing.overload
def Depends(dependency: _DependenciesWithYield[T]) -> Any:
    ...


@typing.overload
def Depends(dependency: Union[Callable[..., T], Callable[..., Awaitable[T]]]) -> T:
    ...


def Depends(dependency = None) -> Any:
    # Implementation
    pass
1reaction
ThirVondukrcommented, Apr 23, 2022

After some analysis i think it’s not possible to add correct type annotation to Depends mostly because of context manager-like dependencies that are usually annotated with Iterable[T] or Generator[T, None, None]

If we assume that all dependencies annotated with Iterable[T] return T would break dependencies that return iterable objects:

_DependenciesWithYield = Union[
    Callable[..., Iterable[T]],
    Callable[..., Generator[T, None, None]],
    Callable[..., AsyncIterable[T]],
    Callable[..., AsyncGenerator[T, None]],
]


@typing.overload
def Depends(dependency: _DependenciesWithYield[T]) -> T:
    ...


def dependency() -> list[int]:
    return [42]
    

def dependency_with_yield() -> Iterable[T]:
    yield 42


reveal_type(Depends(dependency))  # int, Correct return type is `list[int]` or `Iterable[int]`
reveal_type(Depends(dependency_with_yield))  # int

Perhaps some context manager specific type annotations could be used to resolve problem with iterables and generators and wrap fastapi dependencies into @contextlib.contextmanager manually

Read more comments on GitHub >

github_iconTop Results From Across the Web

Advanced Dependencies - FastAPI
Let's imagine that we want to have a dependency that checks if the query parameter q contains some fixed content. But we want...
Read more >
The Simplest Guide to FastAPI Dependency Injection using ...
Learn how to use FastAPI Dependency Injection using Depends keywords to handle dependencies in the form of dict, classes, global dependency.
Read more >
python - Unable to inject dependencies to FastAPI endpoints
@lru_cache(max_size=1) def get_chords_service(): return ChordsService() @router.get("") def get_chords(chords_service: ChordsService=Depends( ...
Read more >
Dependency injection in action - Angular
The useClass provider key lets you create and return a new instance of the specified class. You can use this type of provider...
Read more >
Setting Up Dependency Injection with TypeScript in an Object ...
In general, the concept of a dependency depends on a context, but for simplicity, ... return randomSource.random() * (max - min) + min;...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found