starlette/docs/databases.md

158 lines
4.5 KiB
Markdown
Raw Normal View History

Starlette includes optional database support. There is currently only a driver
for Postgres databases, but MySQL and SQLite support is planned.
```python
import os
import sqlalchemy
from starlette.applications import Starlette
from starlette.middleware.database import DatabaseMiddleware
from starlette.responses import JSONResponse
metadata = sqlalchemy.MetaData()
notes = sqlalchemy.Table(
"notes",
metadata,
sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
sqlalchemy.Column("text", sqlalchemy.String),
sqlalchemy.Column("completed", sqlalchemy.Boolean),
)
app = Starlette()
app.add_middleware(DatabaseMiddleware, database_url=os.environ['DATABASE_URL'])
@app.route("/notes", methods=["GET"])
async def list_notes(request):
query = notes.select()
results = await request.database.fetchall(query)
content = [
{
"text": result["text"],
"completed": result["completed"]
}
for result in results
]
return JSONResponse(content)
@app.route("/notes", methods=["POST"])
async def add_note(request):
data = await request.json()
query = notes.insert().values(
text=data["text"],
completed=data["completed"]
)
await request.database.execute(query)
return JSONResponse({
"text": data["text"],
"completed": data["completed"]
})
```
## Queries
Queries may be made with as [SQLAlchemy Core queries][sqlalchemy-core], or as raw SQL.
The following are supported:
* `request.database.fetchall(query)`
* `request.database.fetchone(query)`
* `request.database.fetchval(query)`
* `request.database.execute(query)`
## Transactions
Database transactions are available either as an endpoint decorator, as a
context manager, or as a low-level API.
Using a decorator on an endpoint:
```python
from starlette.databases import transaction
@transaction
async def populate_notes(request):
# These database inserts occur within a transaction.
# If an error occurs they will be rolled back as a group.
query = notes.insert().values(text="buy the groceries", completed=True)
await request.database.execute(query)
query = notes.insert().values(text="take the dog for a walk", completed=False)
await request.database.execute(query)
query = notes.insert().values(text="interview preparation", completed=True)
await request.database.execute(query)
```
Using a context manager:
```python
async def populate_notes(request):
async with request.database.transaction():
# These database inserts occur within a transaction.
# If an error occurs they will be rolled back as a group.
query = notes.insert().values(text="buy the groceries", completed=True)
await request.database.execute(query)
query = notes.insert().values(text="take the dog for a walk", completed=False)
await request.database.execute(query)
query = notes.insert().values(text="interview preparation", completed=True)
await request.database.execute(query)
```
Using the low-level API:
```python
async def populate_notes(request):
transaction = request.database.transaction()
transaction.start()
try:
# These database inserts occur within a transaction.
# If an error occurs they will be rolled back as a group.
query = notes.insert().values(text="buy the groceries", completed=True)
await request.database.execute(query)
query = notes.insert().values(text="take the dog for a walk", completed=False)
await request.database.execute(query)
query = notes.insert().values(text="interview preparation", completed=True)
await request.database.execute(query)
except:
transaction.rollback()
raise
else:
transaction.commit()
```
## Test isolation:
Use rollback_on_shutdown when instantiating DatabaseMiddleware to support test-isolated sessions.
```python
app.add_middleware(
DatabaseMiddleware,
database_url=os.environ['DATABASE_URL'],
rollback_on_shutdown=os.environ['TESTING']
)
```
You'll need to use TestClient as a context manager, in order to perform application startup/shutdown.
```python
with TestClient(app) as client:
# Entering the block performs application startup.
...
# Exiting the block performs application shutdown.
```
If you're using `py.test` you can create a fixture for the test client, like so:
**conftest.py**:
```python
@pytest.fixture()
def client():
with TestClient(app) as client:
yield client
```
[sqlalchemy-core]: https://docs.sqlalchemy.org/en/latest/core/