Skip to content

Commit 622318a

Browse files
authored
Merge pull request #114 from encode/docs
Documentation
2 parents 5688129 + 84da9bf commit 622318a

File tree

6 files changed

+450
-255
lines changed

6 files changed

+450
-255
lines changed

README.md

Lines changed: 29 additions & 255 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ It allows you to make queries using the powerful [SQLAlchemy Core][sqlalchemy-co
1818
expression language, and provides support for PostgreSQL, MySQL, and SQLite.
1919

2020
Databases is suitable for integrating against any async Web framework, such as [Starlette][starlette],
21-
[Sanic][sanic], [Responder][responder], [Quart][quart], [aiohttp][aiohttp], [Tornado][tornado], [FastAPI][fastapi],
22-
or [Bocadillo][bocadillo].
21+
[Sanic][sanic], [Responder][responder], [Quart][quart], [aiohttp][aiohttp], [Tornado][tornado], [FastAPI][fastapi], or [Bocadillo][bocadillo].
2322

24-
**Community**: https://discuss.encode.io/c/databases
23+
**Documentation**: [https://www.encode.io/databases/](https://www.encode.io/databases/)
24+
25+
**Community**: [https://discuss.encode.io/c/databases](https://discuss.encode.io/c/databases)
2526

2627
**Requirements**: Python 3.6+
2728

@@ -43,278 +44,51 @@ $ pip install databases[sqlite]
4344

4445
Driver support is providing using one of [asyncpg][asyncpg], [aiomysql][aiomysql], or [aiosqlite][aiosqlite].
4546

46-
## Getting started
47-
48-
**Note**: Use `ipython` to try these example from the console, since it supports `await`.
49-
50-
Declare your tables using SQLAlchemy:
51-
52-
```python
53-
import sqlalchemy
47+
---
5448

49+
## Quickstart
5550

56-
metadata = sqlalchemy.MetaData()
51+
For this example we'll create a very simple SQLite database to run some
52+
queries against.
5753

58-
notes = sqlalchemy.Table(
59-
"notes",
60-
metadata,
61-
sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True),
62-
sqlalchemy.Column("text", sqlalchemy.String(length=100)),
63-
sqlalchemy.Column("completed", sqlalchemy.Boolean),
64-
)
54+
```shell
55+
$ pip install databases[sqlite]
56+
$ pip install ipython
6557
```
6658

59+
We can now run a simple example from the console.
6760

68-
You can use any of the sqlalchemy column types such as `sqlalchemy.JSON`, or
69-
custom column types.
70-
71-
## Queries
72-
73-
You can now use any [SQLAlchemy core][sqlalchemy-core] queries ([official tutorial][sqlalchemy-core-tutorial]).
61+
Note that we want to use `ipython` here, because it supports using `await`
62+
expressions directly from the console.
7463

7564
```python
65+
# Create a database instance, and connect to it.
7666
from databases import Database
77-
78-
database = Database('postgresql://localhost/example')
79-
80-
81-
# Establish the connection pool
67+
database = Database('sqlite:///example.db')
8268
await database.connect()
8369

84-
# Execute
85-
query = notes.insert()
86-
values = {"text": "example1", "completed": True}
87-
await database.execute(query=query, values=values)
70+
# Create a table.
71+
query = """CREATE TABLE HighScores (id INTEGER PRIMARY KEY, name VARCHAR(100), score INTEGER)"""
72+
await database.execute(query=query)
8873

89-
# Execute many
90-
query = notes.insert()
74+
# Insert some data.
75+
query = "INSERT INTO HighScores(name, score) VALUES (:name, :score)"
9176
values = [
92-
{"text": "example2", "completed": False},
93-
{"text": "example3", "completed": True},
77+
{"name": "Daisy", "score": 92},
78+
{"name": "Neil", "score": 87},
79+
{"name": "Carol", "score": 43},
9480
]
9581
await database.execute_many(query=query, values=values)
9682

97-
# Fetch multiple rows
98-
query = notes.select()
83+
# Run a database query.
84+
query = "SELECT * FROM HighScores"
9985
rows = await database.fetch_all(query=query)
100-
101-
# Fetch single row
102-
query = notes.select()
103-
row = await database.fetch_one(query=query)
104-
105-
# Fetch single value, defaults to `column=0`.
106-
query = notes.select()
107-
value = await database.fetch_val(query=query)
108-
109-
# Fetch multiple rows without loading them all into memory at once
110-
query = notes.select()
111-
async for row in database.iterate(query=query):
112-
...
113-
114-
# Close all connection in the connection pool
115-
await database.disconnect()
116-
```
117-
118-
Connections are managed as task-local state, with driver implementations
119-
transparently using connection pooling behind the scenes.
120-
121-
## Raw queries
122-
123-
In addition to SQLAlchemy core queries, you can also perform raw SQL queries:
124-
125-
```python
126-
# Execute
127-
query = "INSERT INTO notes(text, completed) VALUES (:text, :completed)"
128-
values = {"text": "example1", "completed": True}
129-
await database.execute(query=query, values=values)
130-
131-
# Execute many
132-
query = "INSERT INTO notes(text, completed) VALUES (:text, :completed)"
133-
values = [
134-
{"text": "example2", "completed": False},
135-
{"text": "example3", "completed": True},
136-
]
137-
await database.execute_many(query=query, values=values)
138-
139-
# Fetch multiple rows
140-
query = "SELECT * FROM notes WHERE completed = :completed"
141-
rows = await database.fetch_all(query=query, values={"completed": True})
142-
143-
# Fetch single row
144-
query = "SELECT * FROM notes WHERE id = :id"
145-
result = await database.fetch_one(query=query, values={"id": 1})
146-
```
147-
148-
Note that query arguments should follow the `:query_arg` style.
149-
150-
## Transactions
151-
152-
Transactions are managed by async context blocks:
153-
154-
```python
155-
async with database.transaction():
156-
...
157-
```
158-
159-
For a lower-level transaction API:
160-
161-
```python
162-
transaction = await database.transaction()
163-
try:
164-
...
165-
except:
166-
transaction.rollback()
167-
else:
168-
transaction.commit()
169-
```
170-
171-
You can also use `.transaction()` as a function decorator on any async function:
172-
173-
```python
174-
@database.transaction()
175-
async def create_users(request):
176-
...
177-
```
178-
179-
Transaction blocks are managed as task-local state. Nested transactions
180-
are fully supported, and are implemented using database savepoints.
181-
182-
## Connecting and disconnecting
183-
184-
You can control the database connect/disconnect, by using it as a async context manager.
185-
186-
```python
187-
async with Database(DATABASE_URL) as database:
188-
...
189-
```
190-
191-
Or by using explicit connection and disconnection:
192-
193-
```python
194-
database = Database(DATABASE_URL)
195-
await database.connect()
196-
...
197-
await database.disconnect()
198-
```
199-
200-
If you're integrating against a web framework, then you'll probably want
201-
to hook into framework startup or shutdown events. For example, with
202-
[Starlette][starlette] you would use the following:
203-
204-
```python
205-
@app.on_event("startup")
206-
async def startup():
207-
await database.connect()
208-
209-
@app.on_event("shutdown")
210-
async def shutdown():
211-
await database.disconnect()
212-
```
213-
214-
## Connection options
215-
216-
The PostgreSQL and MySQL backends provide a few connection options for SSL
217-
and for configuring the connection pool.
218-
219-
```python
220-
# Use an SSL connection.
221-
database = Database('postgresql://localhost/example?ssl=true')
222-
223-
# Use a connection pool of between 5-20 connections.
224-
database = Database('mysql://localhost/example?min_size=5&max_size=20')
225-
```
226-
227-
You can also use keyword arguments to pass in any connection options.
228-
Available keyword arguments may differ between database backends.
229-
230-
```python
231-
database = Database('postgresql://localhost/example', ssl=True, min_size=5, max_size=20)
232-
```
233-
234-
## Test isolation
235-
236-
For strict test isolation you will always want to rollback the test database
237-
to a clean state between each test case:
238-
239-
```python
240-
database = Database(DATABASE_URL, force_rollback=True)
241-
```
242-
243-
This will ensure that all database connections are run within a transaction
244-
that rollbacks once the database is disconnected.
245-
246-
If you're integrating against a web framework you'll typically want to
247-
use something like the following pattern:
248-
249-
```python
250-
if TESTING:
251-
database = Database(TEST_DATABASE_URL, force_rollback=True)
252-
else:
253-
database = Database(DATABASE_URL)
254-
```
255-
256-
This will give you test cases that run against a different database to
257-
the development database, with strict test isolation so long as you make sure
258-
to connect and disconnect to the database between test cases.
259-
260-
For a lower level API you can explicitly create force-rollback transactions:
261-
262-
```python
263-
async with database.transaction(force_rollback=True):
264-
...
265-
```
266-
267-
## Migrations
268-
269-
Because `databases` uses SQLAlchemy core, you can integrate with [Alembic][alembic]
270-
for database migration support.
271-
272-
```shell
273-
$ pip install alembic
274-
$ alembic init migrations
275-
```
276-
277-
You'll want to set things up so that Alembic references the configured
278-
`DATABASE_URL`, and uses your table metadata.
279-
280-
In `alembic.ini` remove the following line:
281-
282-
```shell
283-
sqlalchemy.url = driver://user:pass@localhost/dbname
86+
print('High Scores:', rows)
28487
```
28588

286-
In `migrations/env.py`, you need to set the ``'sqlalchemy.url'`` configuration key,
287-
and the `target_metadata` variable. You'll want something like this:
288-
289-
```python
290-
# The Alembic Config object.
291-
config = context.config
292-
293-
# Configure Alembic to use our DATABASE_URL and our table definitions.
294-
# These are just examples - the exact setup will depend on whatever
295-
# framework you're integrating against.
296-
from myapp.settings import DATABASE_URL
297-
from myapp.tables import metadata
298-
299-
config.set_main_option('sqlalchemy.url', str(DATABASE_URL))
300-
target_metadata = metadata
301-
302-
...
303-
```
304-
305-
Note that migrations will use a standard synchronous database driver,
306-
rather than using the async drivers that `databases` provides support for.
307-
308-
This will also be the case if you're using SQLAlchemy's standard tooling, such
309-
as using `metadata.create_all(engine)` to setup the database tables.
310-
311-
**Note for MySQL**:
312-
313-
For MySQL you'll probably need to explicitly specify the `pymysql` dialect when
314-
using Alembic since the default MySQL dialect does not support Python 3.
89+
Check out the documentation on [making database queries](database_queries.md)
90+
for examples of how to start using databases together with SQLAlchemy core expressions.
31591

316-
If you're using the `databases.DatabaseURL` datatype, you can obtain this using
317-
`DATABASE_URL.replace(dialect="pymysql")`
31892

31993
<p align="center">&mdash; ⭐️ &mdash;</p>
32094
<p align="center"><i>Databases is <a href="https://github.com/encode/databases/blob/master/LICENSE.md">BSD licensed</a> code. Designed & built in Brighton, England.</i></p>

0 commit comments

Comments
 (0)