-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
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
Comments
I think the easiest way to achieve this would be to put a 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. |
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 |
agreed @dmontagu , I read it too fast. |
@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". |
Yes, I also think this would be a nice extension of the dependency system. 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 The example contains a lot of duplicated code for injecting the |
@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? |
@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:
|
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. 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 """
``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 |
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.
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.
@Gui-greg: That's been available in Starlette and FastAPI for a few months as |
@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.
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 |
@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:
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. 😱 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. |
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
|
@ZenWalker The problem with |
@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 |
@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: 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), 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) |
@sm-Fifteen |
You're not meant to set app.state, it's pre-initialized. Just add new keys to it. |
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 |
Oh, right, my bad, Starlette's I still get it wrong every single time. |
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: |
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 ( |
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. 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. |
to be not dependent on the request object, wouldn't something like this be a suitable solution (not tested yet):
The ServiceProvider is just a wrapper class to provide access to all dependencies.
Now we instantiate the Provider once on app startup. After this, our dependencies are available via the class methods of the ServiceProvider Class
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. |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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.
The text was updated successfully, but these errors were encountered: