mirror of https://github.com/encode/starlette.git
158 lines
4.5 KiB
Markdown
158 lines
4.5 KiB
Markdown
|
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/
|