Repository: fastapi
Issue: Further develop startup and shutdown events
Labels: feature,reviewed

While the documentationn for FastAPI is in general extremely solid, there's a weakpoint that I feel hints at some underdevelopped feature within the framework, and that's [startup and shutdown events][1]. They are briefly mentionned (separately) with the startup event in particular being demonstrated like this :

```py
items = {}


@app.on_event("startup")
async def startup_event():
    items["foo"] = {"name": "Fighters"}
    items["bar"] = {"name": "Tenders"}


@app.get("/items/{item_id}")
async def read_items(item_id: str):
    return items[item_id]
```

...which could very well be written like this:

```py
items = {
    "foo": {"name": "Fighters"},
    "bar": {"name": "Tenders"}
}

@app.get("/items/{item_id}")
async def read_items(item_id: str):
    return items[item_id]
```

...and therefore makes the feature look useless. The example for `shutdown` instead uses logging as an example, which makes it look like this would be the primary purposes for those events, while in reality, it's not.

**Is your feature request related to a problem? Please describe.**
The problem is that, throughout the entire documentation, things like database connections are created in the global scope, at module import. While this would be fine in a regular Python application, this has a number of problems, especially with objects that have a side-effect outside the code itself, like database connections. To demonstrate this, I've made [a test structure that creates a lock file when initialized and deletes it when garbage collected][2].

Using it like this:
```py
from fastapi import FastAPI
from lock import FileLock

app = FastAPI()
lock = FileLock("fastapi")

@app.get("/")
async def root():
    return {"message": "Hello World"}
```

...does not work and the lock is not deleted before shutdown (I was actually expecting it to be closed properly, like SQLAlchemy does with its connections, but clearly there's a lot of extra magic going on with SQLAlchemy that I don't even come close to understanding). This is also extremely apparent when using the `--reload` option on Uvicorn, bcause the lock is *also* not released when the modules are reloaded, causing the import to fail and the server to crash. This would be one thing, but I've had a similar incident occur some time ago when, while developping in reload mode, I've actually managed to take up every connection on my PostgreSQL server because of that problem, since while SQLAlchemy is smart enough to cleanup on exit where my `FileLock` cannot, the same does not happen when hot-reloading code.

So that would be one thing; the documentation should probably go into more details about what those startup and shutdown events are for ([the Starlette documentation is a little more concrete about this][3], but no working code is given to illustrate this) and that should also be woven with the chapters about databases and such to make sure people don't miss it.

Except... That's not super ergonomic, now, is it?

```py
from fastapi import FastAPI, Depends
from some_db_module import Connection

app = FastAPI()
_db_conn: Connection

@app.on_event("startup")
def take_lock():
	global _db_conn
	_db_conn = Connection("mydb:///")

@app.on_event("shutdown")
def release_lock():
	global _db_conn
	_db_conn.close()

def get_db_conn():
	return _db_conn

@app.get("/")
async def root(conn: Connection = Depends(get_db_conn)):
	pass
```

This is basically just a context manager split into two halves, linked together by a global variable. A context manager that will be entered and exited when the ASGI lifetime protocol notifies that the application has started and stopped. A context manager whose only job will be to initialize a resource to be either held while the application is running or used as an injectable dependency. *Surely* there's a cleaner way to do this.


**Describe the solution you'd like**
I've been meaning to file this bug for a few weeks now, but what finally got me to do it is the release of FastAPI 0.42 (Good job, everyone!), which has [context managers as dependencies][4] as one of its main new features. Not only that, but the examples being given are pretty much all database-related, except connections are opened and closed for each call of each route instead of being polled like SQLAlchemy (and I assume encode's async database module too). Ideally, events should be replaced with something like this, but where the dependencies are pre-initialized instead of being created on the spot. Maybe by having context managers that are started and stopped based on `startup` and `shutdown` events and yield "factory functions" that could in turn be called during dependency injection to get the object that needs to be passed.

Something along those lines:

```py
from fastapi import FastAPI, Depends
from sqlalchemy import create_engine

app = FastAPI()

@app.lifetime_dependency
def get_db_conn():
	conn_pool = create_engine("mydb:///")

	# yield a function that closes around db_conn
	# and returns it as a dependency when called
	yield lambda: conn_pool

	conn_pool.close()

@app.get("/")
async def root(conn: Connection = Depends(get_db_conn)):
	pass
```

**Additional context**
Not sure where else to mention it, but I've ran into cases where the shutdown event does not get called before exiting, namely when using the VSCode debugger on Windows and stopping or restarting the application via the debugger's controls (haven't tried this on Linux yet). This apparently kills the thread without any sort of cleanup being performed and leaves all database connections open (and possibly unable to timeout, since [DBAPI appears to suggest that all queries to be executed as part of a transaction][5], which most drivers do, and mid-transaction timeout is disabled by default on at least PostgreSQL). I don't think there is any way that could be fixed, though it should probably be mentionned somewhere, either there or in [debugging part of the documentation][6].

[1]: https://fastapi.tiangolo.com/tutorial/events/
[2]: https://gist.github.com/sm-Fifteen/2ceb7b453463b828dc1bb42077fdce63#file-lock-py
[3]: https://www.starlette.io/events/
[4]: https://fastapi.tiangolo.com/tutorial/dependencies/dependencies-with-yield/
[5]: https://www.python.org/dev/peps/pep-0249/#commit
[6]: https://fastapi.tiangolo.com/tutorial/debugging/