Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Dependency Injection - Singleton? #504

Closed
Gui-greg opened this issue Sep 4, 2019 · 44 comments
Closed

Dependency Injection - Singleton? #504

Gui-greg opened this issue Sep 4, 2019 · 44 comments
Labels
question Question or problem question-migrate

Comments

@Gui-greg
Copy link

Gui-greg commented Sep 4, 2019

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.

@Gui-greg Gui-greg added the question Question or problem label Sep 4, 2019
@dmontagu
Copy link
Collaborator

dmontagu commented Sep 4, 2019

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’t work for you there may be another (related) approach that does (likely involving the metaclass), but I don’t see why this should be a fastapi feature if you can just make the class itself a singleton.

@euri10
Copy link
Contributor

euri10 commented Sep 4, 2019

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 ?

@euri10
Copy link
Contributor

euri10 commented Sep 4, 2019

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

@dmontagu
Copy link
Collaborator

dmontagu commented Sep 4, 2019

@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.

@euri10
Copy link
Contributor

euri10 commented Sep 4, 2019

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

@Gui-greg
Copy link
Author

Gui-greg commented Sep 9, 2019

@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.

@DrPyser
Copy link

DrPyser commented Sep 17, 2019

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.

@dmontagu
Copy link
Collaborator

dmontagu commented Sep 17, 2019

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.

@skewty
Copy link

skewty commented Oct 26, 2019

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.

@MateuszCzubak
Copy link

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.

@dmontagu
Copy link
Collaborator

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

@MateuszCzubak
Copy link

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

@Gui-greg
Copy link
Author

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

@tiangolo
Copy link
Owner

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.

@jay-aplan
Copy link

jay-aplan commented Feb 12, 2020

@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!

@Gui-greg
Copy link
Author

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?

@peterdeme
Copy link

@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 ??????

@skewty
Copy link

skewty commented Feb 12, 2020

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.

@selimb
Copy link

selimb commented Feb 13, 2020

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"?

@sm-Fifteen
Copy link
Contributor

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.

@selimb
Copy link

selimb commented Feb 13, 2020

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.

@dmontagu
Copy link
Collaborator

@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.

@sm-Fifteen
Copy link
Contributor

@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.

@K900
Copy link

K900 commented Mar 10, 2020

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()

@euri10
Copy link
Contributor

euri10 commented Mar 10, 2020

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

@tiangolo
Copy link
Owner

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

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

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/ ✔️

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.

@github-actions
Copy link
Contributor

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

@maxpoletaev
Copy link

maxpoletaev commented May 21, 2020

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

@selimb
Copy link

selimb commented May 21, 2020

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

@acnebs
Copy link

acnebs commented May 21, 2020

@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()

@selimb
Copy link

selimb commented May 21, 2020

@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.

@sm-Fifteen
Copy link
Contributor

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

@sandys
Copy link

sandys commented Jun 5, 2020

@sm-Fifteen @ZenWalker just wanted to point out this related issue as well - tiangolo/full-stack-fastapi-template#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.

@DachuanZhao
Copy link

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

This is what I'm looking for . I load a machine learning model like this .

@sm-Fifteen
Copy link
Contributor

sm-Fifteen commented Mar 1, 2022

This is what I'm looking for . I load a machine learning model like this .

@DachuanZhao: Assuming you're asking this here because you only want to load the model once globally (but to not have it live in the global namespace) and not once per call (like in a regular dependency or in the route directly), app.state is a generic dict you can store arbitrary shared state on, and request.app.state allows you to access it from within any route. Ideally, you would wrap it in a dependency and get something like this:

from fastapi import FastAPI

app = FastAPI()

@app.on_event("startup")
def load_ml_model():
	# EDIT: My bad, the state dict uses attribute access, not key access
	#app.state['machine_learning_model'] = joblib.load(some_path)
	app.state.machine_learning_model = joblib.load(some_path)
from fastapi import Router

my_router = Router()

async def get_ml_model(req: Request):
	#return request.app.state['machine_learning_model']
	return req.app.state.machine_learning_model

@my_router.get('/use_ml_model')
async def use_ml_model(ml_model = Depends(get_ml_model)):
	pass

(Disclaimer: Untested code, written from memory, just to illustrate the general idea)

@tafaust
Copy link

tafaust commented Apr 5, 2022

@sm-Fifteen app.state cannot be set. Simply use app. This worked for me.

@sm-Fifteen
Copy link
Contributor

@sm-Fifteen app.state cannot be set. Simply use app. This worked for me.

You're not meant to set app.state, it's pre-initialized. Just add new keys to it.

@tafaust
Copy link

tafaust commented Apr 6, 2022

For completeness, I wanted to state that

app.state['machine_learning_model'] = joblib.load(some_path)

does not work. I had to do

app.machine_learning_model = joblib.load(some_path)

to avoid TypeError: 'State' object does not support item assignment

@sm-Fifteen
Copy link
Contributor

For completeness, I wanted to state that

app.state['machine_learning_model'] = joblib.load(some_path)

does not work. I had to do

app.machine_learning_model = joblib.load(some_path)

to avoid TypeError: 'State' object does not support item assignment

Oh, right, my bad, Starlette's State object uses attribute storage, not key storage, so it's app.state.machine_learning_model = joblib.load(some_path), not app.state['machine_learning_model'] = joblib.load(some_path).

I still get it wrong every single time.

@haydenzhourepo
Copy link

@tafaust
Copy link

tafaust commented May 3, 2022

I have written a gist to summarize the above mentioned into one fully working FastAPI app:
https://gist.github.com/tahesse/ee9e09b7d68f702ad8f7fb1177e20c93

@AIGeneratedUsername
Copy link

AIGeneratedUsername commented May 22, 2022

i think you need this : https://python-dependency-injector.ets-labs.org/examples/fastapi.html

I have written a gist to summarize the above mentioned into one fully working FastAPI app:
https://gist.github.com/tahesse/ee9e09b7d68f702ad8f7fb1177e20c93

All these "singletons" are useless for FastAPI as long as there is no session startup / shutdown support.

The mandatory requirement for FastAPI singletons is to open singleton context manager (__aetner__) on application startup and close the context manager (call __aexit__) on application shutdown. Any code snippets without the mentioned feature are just reinventing the wheel.

@darioWorknet
Copy link

All this problem comes from constructor arguments. Since we are implementing Singleton, we can think as the class constructor as the __call__() method from it's superclass, in this case SingletonMetaNoArgs.
Callables used with Depends() must have no arguments, so you must simple remove extra *args and **kwargs from default SingletonMeta as specified in this comment #504 (comment).

class SingletonMetaNoArgs(type):
    """
    Singleton metaclass for classes without parameters on constructor,
    for compatibility with FastApi Depends() function.
    """
    _instances = {}

    def __call__(cls):
        if cls not in cls._instances:
            instance = super().__call__()
            cls._instances[cls] = instance
        return cls._instances[cls]


class TestClass(metaclass=SingletonMetaNoArgs):
    def __init__(self):
        self.name = "initial name"

    def change_name(self, name:str):
        self.name = name

You can try this code and see how self.name value is preserved between calls.

@jacksbox
Copy link

jacksbox commented Dec 15, 2022

to be not dependent on the request object, wouldn't something like this be a suitable solution (not tested yet):

class Dependency01:
  pass


class Dependency02:
  pass

The ServiceProvider is just a wrapper class to provide access to all dependencies.
They are stored in class variables to eliminate the need for instantiating the Provider more than once.

class ServiceProvider:
    dependency_01: Dependency01
    dependency_02: Dependency02

    def __init__(self):
        cls = self.__class__

        cls.dependency_01 = Dependency01()
        cls.dependency_02 = Dependency02()

    @classmethod
    def get_dependency_01(cls):
        return cls.dependency_01

    def shutdown(self):
        pass

Now we instantiate the Provider once on app startup. After this, our dependencies are available via the class methods of the ServiceProvider Class

# start dependencies in the startup event
@app.on_event("startup")
def startup():
    app.state.service_provider = ServiceProvider()

# cleanup when shutting down
@app.on_event("shutdown")
def shutdown():
    app.state.service_provider.shutdown()

@app.get("/")
async def hello(dependecy_01: Dependency01 = Depends(ServiceProvider.get_dependency_01)):
    pass

The nice thing about this would be, after the app started, the dependency injection via Depends would also work outside of route methods as it is not dependent on the request object.

@tiangolo tiangolo changed the title [QUESTION] Dependency Injection - Singleton? Dependency Injection - Singleton? Feb 24, 2023
@tiangolo tiangolo reopened this Feb 28, 2023
Repository owner locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #8054 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
question Question or problem question-migrate
Projects
None yet
Development

No branches or pull requests