forked from fastapi/fastapi
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ Add docs and tests for encode/databases (fastapi#107)
* ✨ Add docs and tests for encode/databases * ➕ Add testing-only dependency, databases
- Loading branch information
Showing
9 changed files
with
431 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from typing import List | ||
|
||
import databases | ||
import sqlalchemy | ||
from fastapi import FastAPI | ||
from pydantic import BaseModel | ||
|
||
# SQLAlchemy specific code, as with any other app | ||
DATABASE_URL = "sqlite:///./test.db" | ||
# DATABASE_URL = "postgresql://user:password@postgresserver/db" | ||
|
||
database = databases.Database(DATABASE_URL) | ||
|
||
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), | ||
) | ||
|
||
|
||
engine = sqlalchemy.create_engine( | ||
DATABASE_URL, connect_args={"check_same_thread": False} | ||
) | ||
metadata.create_all(engine) | ||
|
||
|
||
class NoteIn(BaseModel): | ||
text: str | ||
completed: bool | ||
|
||
|
||
class Note(BaseModel): | ||
id: int | ||
text: str | ||
completed: bool | ||
|
||
|
||
app = FastAPI() | ||
|
||
|
||
@app.on_event("startup") | ||
async def startup(): | ||
await database.connect() | ||
|
||
|
||
@app.on_event("shutdown") | ||
async def shutdown(): | ||
await database.disconnect() | ||
|
||
|
||
@app.get("/notes/", response_model=List[Note]) | ||
async def read_notes(): | ||
query = notes.select() | ||
return await database.fetch_all(query) | ||
|
||
|
||
@app.post("/notes/", response_model=Note) | ||
async def create_note(note: NoteIn): | ||
query = notes.insert().values(text=note.text, completed=note.completed) | ||
last_record_id = await database.execute(query) | ||
return {**note.dict(), "id": last_record_id} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
You can also use <a href="https://github.com/encode/databases" target="_blank">`encode/databases`</a> with **FastAPI** to connect to databases using `async` and `await`. | ||
|
||
It is compatible with: | ||
|
||
* PostgreSQL | ||
* MySQL | ||
* SQLite | ||
|
||
In this example, we'll use **SQLite**, because it uses a single file and Python has integrated support. So, you can copy this example and run it as is. | ||
|
||
Later, for your production application, you might want to use a database server like **PostgreSQL**. | ||
|
||
!!! tip | ||
You could adopt ideas from the previous section about <a href="/tutorial/sql-databases/" target="_blank">SQLAlchemy ORM</a>, like using utility functions to perform operations in the database, independent of your **FastAPI** code. | ||
|
||
This section doesn't apply those ideas, to be equivalent to the counterpart in <a href="https://www.starlette.io/database/" target="_blank">Starlette</a>. | ||
|
||
## Import and set up `SQLAlchemy` | ||
|
||
* Import `SQLAlchemy`. | ||
* Create a `metadata` object. | ||
* Create a table `notes` using the `metadata` object. | ||
|
||
```Python hl_lines="4 14 16 17 18 19 20 21 22" | ||
{!./src/async_sql_databases/tutorial001.py!} | ||
``` | ||
|
||
!!! tip | ||
Notice that all this code is pure SQLAlchemy Core. | ||
|
||
`databases` is not doing anything here yet. | ||
|
||
## Import and set up `databases` | ||
|
||
* Import `databases`. | ||
* Create a `DATABASE_URL`. | ||
* Create a `database` object. | ||
|
||
```Python hl_lines="3 9 12" | ||
{!./src/async_sql_databases/tutorial001.py!} | ||
``` | ||
|
||
!!! tip | ||
If you where connecting to a different database (e.g. PostgreSQL), you would need to change the `DATABASE_URL`. | ||
|
||
## Create the tables | ||
|
||
In this case, we are creating the tables in the same Python file, but in production, you would probably want to create them with Alembic, integrated with migrations, etc. | ||
|
||
Here, this section would run directly, right before starting your **FastAPI** application. | ||
|
||
* Create an `engine`. | ||
* Create all the tables from the `metadata` object. | ||
|
||
```Python hl_lines="25 26 27 28" | ||
{!./src/async_sql_databases/tutorial001.py!} | ||
``` | ||
|
||
## Create models | ||
|
||
Create Pydantic models for: | ||
|
||
* Notes to be created (`NoteIn`). | ||
* Notes to be returned (`Note`). | ||
|
||
```Python hl_lines="31 32 33 36 37 38 39" | ||
{!./src/async_sql_databases/tutorial001.py!} | ||
``` | ||
|
||
By creating these Pydantic models, the input data will be validated, serialized (converted), and annotated (documented). | ||
|
||
So, you will be able to see it all in the interactive API docs. | ||
|
||
## Connect and disconnect | ||
|
||
* Create your `FastAPI` application. | ||
* Create event handlers to connect and disconnect from the database. | ||
|
||
```Python hl_lines="42 45 46 47 50 51 52" | ||
{!./src/async_sql_databases/tutorial001.py!} | ||
``` | ||
|
||
## Read notes | ||
|
||
Create the *path operation function* to read notes: | ||
|
||
```Python hl_lines="55 56 57 58" | ||
{!./src/async_sql_databases/tutorial001.py!} | ||
``` | ||
|
||
!!! Note | ||
Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`. | ||
|
||
### Notice the `response_model=List[Note]` | ||
|
||
It uses `typing.List`. | ||
|
||
That documents (and validates, serializes, filters) the output data, as a `list` of `Note`s. | ||
|
||
## Create notes | ||
|
||
Create the *path operation function* to create notes: | ||
|
||
```Python hl_lines="61 62 63 64 65" | ||
{!./src/async_sql_databases/tutorial001.py!} | ||
``` | ||
|
||
!!! Note | ||
Notice that as we communicate with the database using `await`, the *path operation function* is declared with `async`. | ||
|
||
### About `{**note.dict(), "id": last_record_id}` | ||
|
||
`note` is a Pydantic `Note` object. | ||
|
||
`note.dict()` returns a `dict` with its data, something like: | ||
|
||
```Python | ||
{ | ||
"text": "Some note", | ||
"completed": False, | ||
} | ||
``` | ||
|
||
but it doesn't have the `id` field. | ||
|
||
So we create a new `dict`, that contains the key-value pairs from `note.dict()` with: | ||
|
||
```Python | ||
{**note.dict()} | ||
``` | ||
|
||
`**note.dict()` "unpacks" the key value pairs directly, so, `{**note.dict()}` would be, more or less, a copy of `note.dict()`. | ||
|
||
And then, we extend that copy `dict`, adding another key-value pair: `"id": last_record_id`: | ||
|
||
```Python | ||
{**note.dict(), "id": last_record_id} | ||
``` | ||
|
||
So, the final result returned would be something like: | ||
|
||
```Python | ||
{ | ||
"id": 1, | ||
"text": "Some note", | ||
"completed": False, | ||
} | ||
``` | ||
|
||
## Check it | ||
|
||
You can copy this code as is, and see the docs at <a href="http://127.0.0.1:8000/docs" target="_blank">http://127.0.0.1:8000/docs</a>. | ||
|
||
There you can see all your API documented and interact with it: | ||
|
||
<img src="/img/tutorial/async-sql-databases/image01.png"> | ||
|
||
## More info | ||
|
||
You can read more about <a href="https://github.com/encode/databases" target="_blank">`encode/databases` at its GitHub page</a>. |
Oops, something went wrong.