Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Singleton providers broken in FastAPI with 1.13.0 #47

Closed
rmasters opened this issue Jun 30, 2024 · 3 comments · Fixed by #48
Closed

Singleton providers broken in FastAPI with 1.13.0 #47

rmasters opened this issue Jun 30, 2024 · 3 comments · Fixed by #48
Assignees
Labels
bug Something isn't working

Comments

@rmasters
Copy link

rmasters commented Jun 30, 2024

Singleton providers appear to be broken when used as FastAPI dependencies in 1.13.0 (working in 1.12.0).

This appears to only affect direct dependencies: when a Singleton is used as a sub-dependency in a non-Singleton provider, I don't get an error.

Test case

from datetime import datetime
from pathlib import Path
from typing import Annotated, Iterator

from that_depends import BaseContainer
from that_depends.providers import Singleton, Factory, Resource
from fastapi import FastAPI, Depends


def path_resource() -> Iterator[Path]:
    yield Path(__file__).parent


class Container(BaseContainer):
    singleton: Singleton[Path] = Singleton(lambda: Path(__file__).parent)
    resource: Resource[Path] = Resource(path_resource)
    factory: Factory[datetime] = Factory(datetime.now)


app = FastAPI()


@app.get("/")
async def handler(
    path_singleton: Annotated[Path, Depends(Container.singleton)],
    #path_resource: Annotated[Path, Depends(Container.resource)],
    #factory: Annotated[datetime, Depends(Container.factory)],
) -> None:
    return

Expected

path_singleton is an instance of Path

Actual

TypeError: unexpected object <that_depends.providers.attr_getter.AttrGetter object at 0x105c1f690> in __signature__ attribute
sys:1: RuntimeWarning: coroutine 'AbstractProvider.__call__' was never awaited

Resource and Factory work as expected.

Full stacktrace
Traceback (most recent call last):
  File "/Users/ross/that-depends-testcase/test.py", line 23, in <module>
    @app.get("/")
     ^^^^^^^^^^^^
  File "/Users/ross/that-depends-testcase/.venv/lib/python3.12/site-packages/fastapi/routing.py", line 944, in decorator
    self.add_api_route(
  File "/Users/ross/that-depends-testcase/.venv/lib/python3.12/site-packages/fastapi/routing.py", line 883, in add_api_route
    route = route_class(
            ^^^^^^^^^^^^
  File "/Users/ross/that-depends-testcase/.venv/lib/python3.12/site-packages/fastapi/routing.py", line 513, in __init__
    self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ross/that-depends-testcase/.venv/lib/python3.12/site-packages/fastapi/dependencies/utils.py", line 268, in get_dependant
    sub_dependant = get_param_sub_dependant(
                    ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ross/that-depends-testcase/.venv/lib/python3.12/site-packages/fastapi/dependencies/utils.py", line 111, in get_param_sub_dependant
    return get_sub_dependant(
           ^^^^^^^^^^^^^^^^^^
  File "/Users/ross/that-depends-testcase/.venv/lib/python3.12/site-packages/fastapi/dependencies/utils.py", line 147, in get_sub_dependant
    sub_dependant = get_dependant(
                    ^^^^^^^^^^^^^^
  File "/Users/ross/that-depends-testcase/.venv/lib/python3.12/site-packages/fastapi/dependencies/utils.py", line 250, in get_dependant
    endpoint_signature = get_typed_signature(call)
                         ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ross/that-depends-testcase/.venv/lib/python3.12/site-packages/fastapi/dependencies/utils.py", line 208, in get_typed_signature
    signature = inspect.signature(call)
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ross/.rye/py/[email protected]/lib/python3.12/inspect.py", line 3310, in signature
    return Signature.from_callable(obj, follow_wrapped=follow_wrapped,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ross/.rye/py/[email protected]/lib/python3.12/inspect.py", line 3054, in from_callable
    return _signature_from_callable(obj, sigcls=cls,
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ross/.rye/py/[email protected]/lib/python3.12/inspect.py", line 2530, in _signature_from_callable
    raise TypeError(
TypeError: unexpected object <that_depends.providers.attr_getter.AttrGetter object at 0x10426d910> in __signature__ attribute
sys:1: RuntimeWarning: coroutine 'AbstractProvider.__call__' was never awaited

Environment

Python 3.12.3

that-depends==1.13.0
fastapi==0.111.0
@lesnik512 lesnik512 self-assigned this Jul 1, 2024
@lesnik512 lesnik512 linked a pull request Jul 1, 2024 that will close this issue
@lesnik512 lesnik512 added the bug Something isn't working label Jul 1, 2024
@lesnik512
Copy link
Member

@rmasters, thank you, Ross! Fixed in 1.13.1

@graham-atom
Copy link

graham-atom commented Jul 3, 2024

@lesnik512 I am still seeing this for version 1.13.1 and fastapi 0.111.0:

no signature found for builtin <method-wrapper '__call__' of dependency_injector.providers.Singleton object at 0x112a9cac0>

def init_cognito(
    region: str,
    user_pool_id: str,
    app_client_id: str,
) ->  Iterator[Cognito]:
    """Initialize the cognito client."""
    try:
        yield Cognito(region=region, userPoolId=user_pool_id, client_id=app_client_id)
    finally:
        pass

class CognitoSettings(BaseSettings):
    """Settings for the cognito client."""

    region: str
    user_pool_id: str
    app_client_id: str

def init_settings() -> Iterator[CognitoSettings]:
    """Initialize the settings."""
    try:
        yield CognitoSettings()
    finally:
        pass

class CognitoContainer(BaseContainer):
    """Container for the cognito client."""
    settings: CognitoSettings = providers.Singleton(init_settings)

    cognito: Cognito = providers.Resource(
        init_cognito,
        region=settings.region,
        userPoolId=settings.user_pool_id,
        client_id=settings.app_client_id,
    )


async def cognito_auth(
    bearer_token: HTTPAuthorizationCredentials | None = Depends(HTTPBearer(auto_error=False)),
    cognito: Cognito = Depends(CognitoContainer.cognito),
) -> CurrentUser | None:
    """Authenticate the current user with cognito.

    Args:
        bearer_token (HTTPAuthorizationCredentials): The users bearer token.
        cognito (Cognito): The cognito client.

    Returns:
        CurrentUser | None: The current user if the bearer token exists.

    """

@graham-atom
Copy link

actually nevermind, looks like an install issue on my end, sorry for the confusion!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants