from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import Session, sessionmaker
from starlette.requests import Request
+from starlette.responses import Response
# SQLAlchemy specific code, as with any other app
SQLALCHEMY_DATABASE_URI = "sqlite:///./test.db"
@app.middleware("http")
async def db_session_middleware(request: Request, call_next):
- request.state.db = SessionLocal()
- response = await call_next(request)
- request.state.db.close()
+ response = Response('', status_code=500)
+ try:
+ request.state.db = SessionLocal()
+ response = await call_next(request)
+ finally:
+ request.state.db.close()
return response
Define the database that SQLAlchemy should "connect" to:
-```Python hl_lines="8"
+```Python hl_lines="9"
{!./src/sql_databases/tutorial001.py!}
```
## Create the SQLAlchemy `engine`
-```Python hl_lines="11 12 13"
+```Python hl_lines="12 13 14"
{!./src/sql_databases/tutorial001.py!}
```
For now, create the `SessionLocal`:
-```Python hl_lines="14"
+```Python hl_lines="15"
{!./src/sql_databases/tutorial001.py!}
```
This middleware (just a function) will create a new SQLAlchemy `SessionLocal` for each request, add it to the request and then close it once the request is finished.
-```Python hl_lines="67 68 69 70 71 72"
+```Python hl_lines="68 69 70 71 72 73 74 75 76"
{!./src/sql_databases/tutorial001.py!}
```
+!!! info
+ We put the creation of the `SessionLocal()` and handling of the requests in a `try` block.
+
+ And then we close it in the `finally` block.
+
+ This way we make sure the database session is always closed after the request. Even if there was an exception in the middle.
+
### About `request.state`
<a href="https://www.starlette.io/requests/#other-state" target="_blank">`request.state` is a property of each Starlette `Request` object</a>, it is there to store arbitrary objects attached to the request itself, like the database session in this case.
This will then give us better editor support inside the path operation function, because the editor will know that the `db` parameter is of type `Session`.
-```Python hl_lines="53 54 68"
+```Python hl_lines="54 55 69"
{!./src/sql_databases/tutorial001.py!}
```
So, your models will behave very similarly to, for example, Flask-SQLAlchemy.
-```Python hl_lines="17 18 19 20 21"
+```Python hl_lines="18 19 20 21 22"
{!./src/sql_databases/tutorial001.py!}
```
## Create the SQLAlchemy `Base` model
-```Python hl_lines="24"
+```Python hl_lines="25"
{!./src/sql_databases/tutorial001.py!}
```
Here's a user model that will be a table in the database:
-```Python hl_lines="27 28 29 30 31"
+```Python hl_lines="28 29 30 31 32"
{!./src/sql_databases/tutorial001.py!}
```
In a very simplistic way, initialize your database (create the tables, etc) and make sure you have a first user:
-```Python hl_lines="34 36 38 39 40 41 42 44"
+```Python hl_lines="35 37 39 40 41 42 43 45"
{!./src/sql_databases/tutorial001.py!}
```
By creating a function that is only dedicated to getting your user from a `user_id` (or any other parameter) independent of your path operation function, you can more easily re-use it in multiple parts and also add <abbr title="Automated tests, written in code, that check if another piece of code is working correctly.">unit tests</abbr> for it:
-```Python hl_lines="48 49"
+```Python hl_lines="49 50"
{!./src/sql_databases/tutorial001.py!}
```
Create your app and path operation function:
-```Python hl_lines="58 61 62 63 64"
+```Python hl_lines="59 62 63 64 65"
{!./src/sql_databases/tutorial001.py!}
```
Then we should declare the path operation without `async def`, just with a normal `def`:
-```Python hl_lines="62"
+```Python hl_lines="63"
{!./src/sql_databases/tutorial001.py!}
```