Fastapi: [QUESTION] Dependency Injection - Singleton?

Created on 4 Sep 2019  路  33Comments  路  Source: tiangolo/fastapi

Don't you think the dependency injection framework needs a singleton type?

I checked the documentation and as I can see there is no option for creating a class as a singleton. If I want to pass in a class as a dependency it will be created every time. So a feature to create singleton and if that already exists just pass it in as a dependency would be great. Because if you create a huge dependency tree than you have to specify one-by-one to make singletons.

Maybe I missed something how to do that but if not that could be a new feature.

question

Most helpful comment

@euri10 Sure I could but if we have this DI framework and it has the dependency tree it should be able to instantiate the classes at its own. It could raise some questions, for example, the use_cache field won't be enough. There should be types of lifecycle like request (=use_cache) and global(?) for "singleton".
I guess it points a bit far and it's not a little request but I'm just curious maybe you guys think that this is a good feature to have.

All 33 comments

I think the easiest way to achieve this would be to put a functools.lru_cache on a function that returns an instance of the class, and use that as your dependency. Would that work for you?

If that doesn鈥檛 work for you there may be another (related) approach that does (likely involving the metaclass), but I don鈥檛 see why this should be a fastapi feature if you can just make the class itself a singleton.

can't remember for certain but I thought @tiangolo had implemented a cache mechanism for dependencies where it's called only once, am I confused ?

seems like it's in 0.28 is there a difference with what you want to achieve ? I may have misunderstood the goal obviously, but it seems like it's called once already

@euri10 My interpretation was that @Gui-greg wanted it to be a singleton across multiple requests (i.e., the state is shared across all requests); the caching mechanism just prevents the dependency from being called multiple times within the same request.

agreed @dmontagu , I read it too fast.
another option then could be to instantiate your class dependency once in the app.on_event("startup")

@euri10 Sure I could but if we have this DI framework and it has the dependency tree it should be able to instantiate the classes at its own. It could raise some questions, for example, the use_cache field won't be enough. There should be types of lifecycle like request (=use_cache) and global(?) for "singleton".
I guess it points a bit far and it's not a little request but I'm just curious maybe you guys think that this is a good feature to have.

Yes, I also think this would be a nice extension of the dependency system.
Some dependencies should be per request, others global, others e.g. per user.

As a reference, Spring includes the concept of "scope" for its dependency injection system.

I just don't see a benefit to fastapi injecting global dependencies for you when you can just pull in the global objects. And if you want to use the current dependency injection system's API, you already can -- you just create a normal dependency that always returns the global instance.

In order to prevent feature bloat, I think any custom logic for the lifecycle of a dependency should probably happen inside of the dependency function -- this is already supported and very readily supports the "global" lifecycle option by just using global variables / non-fastapi-managed singleton patterns.

(And I think it is critical to prevent feature bloat in fastapi -- not only does it make it harder to maintain, but it makes the project less approachable for beginners, which I think is currently one of fastapi's greatest strengths!)

My suspicion is that in most cases there is a minimal-compromise refactor that doesn't require fastapi to support more complex caching behavior. But I could be convinced otherwise if someone could show a real example where having fastapi manage the lifecycle would meaningfully improve the dependency injection ergonomics.

I agree with @dmontagu. And further to the points made above, how would you achieve a true singleton across multi process? Would it additionally work across multiple machines? Something like Redis object storage may be the way to go in such cases.

I think what is actually missing is class based views. With CBV one could inject a singleton dependency using the __init__ and reuse it in all requests if needed. A real example why this is needed - take one from the tutorial: https://fastapi.tiangolo.com/tutorial/sql-databases/.

The example contains a lot of duplicated code for injecting the db to every view - db: Session = Depends(get_db). With a CBV, you could inject it once in __init__ and use in all the views without the need of injecting it again.

@MateuszCzubak I agree completely. I wrote this https://gist.github.com/dmontagu/87e9d3d7795b14b63388d4b16054f0ff and use it extensively now to implement precisely that pattern.

@dmontagu That's helpful but it would be great to have it built in the framework.

@MateuszCzubak Thank you for pointing to a real problem. I hope this could be a new feature for one of the upcoming releases.

Thanks for the discussion here everyone!

I think several comments here might refer to different ideas but using the same name...

What I'm understanding from the original issue is that the idea is to be able to instantiate an object of some class, passing the parameters needed for class creation, and then use that singleton parameterized object as a dependency. If that's what you want, it was supported from the beginning, and it's in the docs. Check the docs: https://fastapi.tiangolo.com/tutorial/dependencies/classes-as-dependencies/

But maybe you are talking about something different than what I'm understanding... If that's the case, then please write a minimal self-contained app with the additional API features you would imagine.

@tiangolo and @dmontagu sorry for hacking into the thread. I have similar requirements where I want to load up a csv (10000) rows, search to find index in it based on the input and pass it on to a machine learning web service. In this case, I would not want to load the csv again and again for every request. Is "functools.lru_cache" is the best approach for this use case?

I tried using startup event and that never gets completed and goes into hanging state for some reason.

Sorry if my question is quite basic but I am new to python and fastapi!

Hi @tiangolo! What you linked can be true for easy examples but for a little more complex it shows it's weakness. Let me have an example. In this case, the "Depends(class_name)" will always instantiate the class. If the class only has 3 integers it can be okay but what if it has a connection to the database? Like every request, you will close and open a new connection?
What I would like to see is like a "global store" that can store your instantiated classes and whenever you need something it will return from there. Like you have a class that can execute queries to your database. I could imagine it like the "Depends(class_name)" is not instantiating your class but it will store it in memory and if you need it just hand over to your other class and you can use it. I think the same thing happens with the use_cache=True but its lifecycle is only request and not singleton.
Is that make sense?

@tiangolo for example, see the "Service Lifetimes" section for .NET Core: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#service-lifetimes

My understanding is that:

  • .NET Transient -> FastAPI default
  • .NET Scoped -> FastAPI cached
  • .NET Singleton -> FastAPI ??????

It sounds like what you (@Gui-greg) are looking for is something like an in-process Redis like key value store.

I require something similar to maintain an ongoing connection to an external message broker.
I have not invested much time into looking for alternative ways to accomplish this. Being able to request it's object reference using just a key name would be slick though.

In my case the async coroutine would need to be running the entire time the process is active and it would need the ability to send out over websocket connections.

I had considered doing a special websocket connection (using a known username/password) that would take the place of the message broker connection but this is not as elegant in my opinion.

Edit: @dmontagu seems to be explaining that my particular use-case is already accounted for by using global variables.

One of the reasons I was attracted to fastapi is its dependency injection system, and I was a bit surprised that global objects were encouraged. I've been trying to come up with a real example, but came to the conclusion that there's never really a need for singleton dependencies, but then again no one really needs dependency injection either.

I did end up writing a small singleton decorator for my application though (see example in the docstring):

"""
``fastapi.Depends`` is called on every request.
This wraps ``fastapi.FastAPI.on_event("startup"|"shutdown")`` and ``fastapi.Depends``,
and provides a single :func:`singleton` decorator to declare a dependency that is
setup and shutdown with the application.

.. note::
    So as to keep things simple, the implementation has one caveat: the dependency is
    systematically setup/shutdown with the application, regardless of whether its
    dependant is called.

.. seealso::
    - https://github.com/tiangolo/fastapi/issues/504
    - https://github.com/tiangolo/fastapi/issues/617
"""
import contextlib
from typing import Any, AsyncGenerator, Callable

import fastapi


def singleton(app: fastapi.FastAPI) -> Any:
    """
    Decorator that can be used to define a resource with the same lifetime as the
    fastapi application.

    :param app: fastapi application.
    :return: A decorator that can be used anywhere ``@contextlib.asynccontextmanager``
        would be used. Specifically, it must be an async generator yielding exactly once.
        The decorated function can then be used where the return value of ``fastapi.Depends()``
        would be used.

    Example::

        from typing import AsyncIterator

        import fastapi

        # For demo purposes
        class Database:
            async def start(): ...
            async def shutdown(): ...

        app = fastapi.FastAPI()

        @singleton(app)
        async def database() -> AsyncIterator[Database]:
            db = Database()
            await db.start()
            yield db
            await db.shutdown()

        @app.get("/users")
        def get_users(db: Database = database):
            ...
    """

    def decorator(func: Callable[[], AsyncGenerator[Any, None]]) -> Any:
        # Don't instantiate the context manager yet, otherwise it can only be used once
        cm_factory = contextlib.asynccontextmanager(func)
        stack = contextlib.AsyncExitStack()
        sentinel_start = object()
        sentinel_shutdown = object()
        value: Any = sentinel_start

        @app.on_event("startup")
        async def _startup() -> None:
            nonlocal value
            value = await stack.enter_async_context(cm_factory())

        @app.on_event("shutdown")
        async def _shutdown() -> None:
            nonlocal value
            await stack.pop_all().aclose()
            value = sentinel_shutdown

        def get_value() -> Any:
            if value is sentinel_start:
                raise RuntimeError("Application not started yet.")
            if value is sentinel_shutdown:
                raise RuntimeError("Application already shut down.")
            return value

        return fastapi.Depends(get_value)

    return decorator

This is basically the same as what @sm-Fifteen suggests in issue 726, but neatly wrapped in a function.

Ideally, I'd be able to just call fastapi.Depends(get_database, singleton=True) (or something like that). I'm not so sure that would confuse users, seeing as how I've stumbled upon a lot (I think?) of questions
(maybe because I've stumbled upon all of them while looking for an answer) of the form "where should I instantiate service X"?

Oh, wow, I'd completely missed that issue, thanks for pinging me @selimb.

One of the things that became clear to me in the discussion over #617 (which is extremely similar to what's proposed here, I think) is that those "singleton dependencies" shouldn't actually be singletons, but more like lifetime-scoped dependencies, based on the startup and shutdown ASGI events. There's an upcoming Starlette PR to allow replacing the startup/shutdown events with a context-manager generator, so that part is likely to get easier to manage in the near future.

Ideally, I'd be able to just call fastapi.Depends(get_database, singleton=True) (or something like that). I'm not so sure that would confuse users, seeing as how I've stumbled upon a lot (I think?) of questions

Personally, I don't think the singleton-like nature of those dependencies should be exposed to the user, much like the user doesn't have ot know whether dependencies are currently context managers or not. All that matters is that depending on that callable object nets you the thing you want. Your approach is really neat, though.

What I would like to see is like a "global store" that can store your instantiated classes and whenever you need something it will return from there.

@Gui-greg: That's been available in Starlette and FastAPI for a few months as app.state, similar to Flask's g dict/object.

but more like lifetime-scoped dependencies, based on the startup and shutdown ASGI events

@sm-Fifteen I can't speak for the others, but in my mind that's what "singleton dependency" means, and I believe that's the nomenclature used by most DI frameworks (ASP.NET, Masonite, BlackSheep), although I haven't actually used those and could be misinterpreting.

Personally, I don't think the singleton-like nature of those dependencies should be exposed to the user, much like the user doesn't have ot know whether dependencies are currently context managers or not.

Yeah I guess it makes more sense to define this in the dependency (as in my snippet above, actually) than in the dependent (path operation function). Is that what you meant? That doesn't quite fit with the way fastapi dependencies are defined at the moment though, where the distinction between "transient" (called every time) and "request-scoped" (once per request) is made in Depends() with use_cache.

@selimb I think when most people say "singleton", they mean something that if "instantiated" multiple times actually returns the same instance each time (like a global variable). Like this:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class MyClass(BaseClass, metaclass=Singleton):
    pass

(from this stackoverflow answer).

The distinction that I think @sm-Fifteen is making is that there isn't a single global instance, or any sort of forced singleton behavior, but rather just a (long-lived) instance created at application startup and cleaned up on shutdown. The difference between this and a singleton/global dependency is subtle, but for example, it would allow you to create separate instances of the dependency on demand if desired, with no metaclass-style singleton logic.

@sm-Fifteen Please correct me if I'm misunderstanding/misrepresenting your perspective.

@dmontagu: That's pretty much what I meant, yeah. I perfonally think such lifetime-scoped dependencies (not quite singletons, but functionally the same in the context of an application) would be preferable to an actual singleton in pretty much all cases, and implementing the singleton pattern doesn't require the framework to be involved.

Interesting to note, also, from the ASP.Net Core doc:

Singleton lifetime services (AddSingleton) are created the first time they're requested (or when Startup.ConfigureServices is run and an instance is specified with the service registration). Every subsequent request uses the same instance. If the app requires singleton behavior, allowing the service container to manage the service's lifetime is recommended. Don't implement the singleton design pattern and provide user code to manage the object's lifetime in the class.

And from the Masonite doc

You can bind singletons into the container. This will resolve the object at the time of binding. This will allow the same object to be used throughout the lifetime of the server.

Both use the word "singleton" but seem to refer to some kind of framework-managed scoped singleton rather than the actual singleton design pattern.

This is actually something I'm really interested in - I'm basically writing an API orchestrator that talks to a whole bunch of external services, some of them over persistent connections and some over HTTP (through a connection pool). The credentials for the connections are loaded from the configuration, so ideally I'd love to be able to do something like this:

@scope("app")  # or whatever
def load_configuration() -> Config:
    return Config.from_file('config.toml')

CONFIG = Depends(load_configuration)

@scope("app")
async def get_connection_to_yak_shaver(config: Config = CONFIG) -> YakShaverConnection:
     async with YakShaverConnection(host=config.yak.host, yak=config.yak.yak) as shaver:
          yield shaver

SHAVER = Depends(get_connection_to_yak_shaver)

# ...

@app.post('/yaks/shave')
async def shave_yak(shaver: YakShaverConnection = SHAVER):
     return await SHAVER.shave()

I'm using something that could look like it, not sure it's the best implementation though, I threw a gist that shows it

Oh no, I think we are again talking about different things on the same thread. :scream:

So, I think lifespan-scoped dependencies/resources are probably something we would want, as @dmontagu and @sm-Fifteen say. :heavy_check_mark:

But I think that's quite a different thing than what @Gui-greg wants to have...

About "Class-Based Views" like @MateuszCzubak and @Gui-greg mention, you should probably check @dmontagu 's fastapi-utils: https://fastapi-utils.davidmontague.xyz/user-guide/class-based-views/ :heavy_check_mark:

If that solves the original problem, we can probably close this issue.

If lifespan-scoped dependencies would solve it, then let's close this issue and keep @sm-Fifteen 's one, waiting for the implementation in Starlette.

Otherwise, I think it would be better to describe what you want to solve in plain terms, avoiding the word "singleton" or similar words, that I think might refer to widely different things depending on the context. Ideally with a minimal self-contained example that shows the problem. And probably another different one that explains what you would want.

Also, if anyone else has another problem or feature request, I think it would be better to create a separate issue, to keep this focused on a single request.

Assuming the original issue was solved, it will be automatically closed now. But feel free to add more comments or create new issues.

In case somebody is still struggling with this, the idiomatic soution would be to use a callable instance, which is basically an object with __call__ method that stores some state (e.g. a database connection).

class AsyncpgPoolDep:
    def __init__(self):
        self._pool: Optional[asyncpg.pool.Pool] = None
        self._lock = asyncio.Lock()

    async def __call__(self):
        if self._pool is not None:
            return self._pool

        async with self._lock:
            if self._pool is not None:
                return self._pool
            self._pool = await asyncpg.create_pool(
                conf.DATABASE_URL,
                min_size=conf.DATABASE_POOL_SIZE,
                max_size=conf.DATABASE_POOL_SIZE,
            )

        return self._pool

asyncpg_pool = AsyncpgPoolDep()
@app.get("/hello")
async def hello(db: Pool = Depends(asyncpg_pool)):
    pass

@app.get("/world")
async def world(db: Pool = Depends(asyncpg_pool)):
    pass

@zenwalker The problem with __call__ is there's no teardown :(

@selimb just add a teardown that is called on app shutdown.

class AsyncpgPoolDep:
    def __init__(self):
        ...

    async def __call__(self):
        ...

    async def close(self):
        if self._pool is not None:
            await self._pool.close()
@app.on_event("shutdown")
async def on_shutdown():
    await asyncpg_pool.close()

@acnebs That's starting to feel a tad clunky (IMO), especially compared to the solution I presented above, in which case it would look like:

@singleton(app)
async def asyncpg_pool():
    pool = await asyncpg.create_pool(
        conf.DATABASE_URL,
        min_size=conf.DATABASE_POOL_SIZE,
        max_size=conf.DATABASE_POOL_SIZE,
    )
    yield pool
    await pool.close()

@app.get("/hello")
async def hello(db: Pool = asyncpg_pool):
    pass

No need for a lock, and everything is in a single cohesive function (instead of a 3-method class + one free function).

That's also pretty close to the API I would like from fastapi itself. The only missing piece is: it's not possible for asyncpg_pool to require other dependencies, although what I've ended up doing is use app.state for that.

@selimb: We should probably continue this in #617, which is specifically about lifetime dependencies, as Tiangolo suggested.

@sm-Fifteen @zenwalker just wanted to point out this related issue as well - https://github.com/tiangolo/full-stack-fastapi-postgresql/issues/104

a bunch of us are seeing similar issues in production. The "Depends" based injection of a database session is slower (and locks up) compared to a contextmanager solution. We think (but are not sure) that sqlalchemy sessions dont get released when function scope ends...while the with block in contextmanager forces this to happen. I could be wrong here.

I would prefer if we have the session injected in function parameters rather than a global contextmanager, since it allows for mocking and testing quite easily. I havent been able to do this with contextmanagers since it needs a with loop.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

siedi picture siedi  路  35Comments

tchiotludo picture tchiotludo  路  21Comments

sandys picture sandys  路  23Comments

euri10 picture euri10  路  28Comments

prav2019 picture prav2019  路  35Comments