Skip to content

Commit

Permalink
Changes:
Browse files Browse the repository at this point in the history
- splitout examples in docs_src
  • Loading branch information
devkral committed Dec 2, 2024
1 parent e79d0bc commit c742de3
Show file tree
Hide file tree
Showing 10 changed files with 117 additions and 58 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ venv/
.idea/
testsuite.sqlite3
test_testsuite.sqlite3
site
75 changes: 19 additions & 56 deletions docs/connections-and-transactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ This way all connections are running in a per-thread pool. The global connection
Whenever it is accessed by an other thread, asyncio.run_coroutine_threadsafe is used.
In the full-isolation mode (default) the global connection is even moved to an own thread.

### Self-resetting global connection
### Global connection with auto-rollback

Sometimes, especially for tests, you want a connection in which all changes are resetted when the database is closed.
For having this reliable, a global connection is lazily initialized when requested on the database object via the
Expand All @@ -49,47 +49,45 @@ Whenever the transaction is activated, a connected database is required.
A second way to get a transaction is via the `Connection`. It has also a `transaction()` method.


### Auto-rollback (`force_rollback`)

Transactions support an keyword parameter named `force_rollback` which default to `False`.
When set to `True` at the end of the transaction a rollback is tried.
This means all changes are undone.

This is a simpler variant of `force_rollback` of the database object.


### The three ways to use a transaction

#### Via the async context manager protocol

```python
async with database.transaction():
...
{!> ../docs_src/transactions/async_context_database.py !}
```
It can also be acquired from a specific database connection:

```python
async with database.connection() as connection:
async with connection.transaction():
...
{!> ../docs_src/transactions/async_context_connection.py !}
```

#### Via decorating

You can also use `.transaction()` as a function decorator on any async function:

```python
@database.transaction()
async def create_users(request):
...
{!> ../docs_src/transactions/decorating.py !}
```

#### Manually

For a lower-level transaction API:

```python
transaction = await database.transaction()
try:
...
except:
await transaction.rollback()
else:
await transaction.commit()
{!> ../docs_src/transactions/manually.py !}
```

### Advanced operations
### Isolation level

The state of a transaction is liked to the connection used in the currently executing async task.
If you would like to influence an active transaction from another task, the connection must be
Expand All @@ -98,48 +96,13 @@ shared:
Transaction isolation-level can be specified if the driver backend supports that:

```python
async def add_excitement(connnection: databases.core.Connection, id: int):
await connection.execute(
"UPDATE notes SET text = CONCAT(text, '!!!') WHERE id = :id",
{"id": id}
)


async with Database(database_url) as database:
async with database.transaction():
# This note won't exist until the transaction closes...
await database.execute(
"INSERT INTO notes(id, text) values (1, 'databases is cool')"
)
# ...but child tasks can use this connection now!
await asyncio.create_task(add_excitement(database.connection(), id=1))

await database.fetch_val("SELECT text FROM notes WHERE id=1")
# ^ returns: "databases is cool!!!"
{!> ../docs_src/transactions/isolation_level.py !}
```

Nested transactions are fully supported, and are implemented using database savepoints:
### Nested transactions

```python
async with databases.Database(database_url) as db:
async with db.transaction() as outer:
# Do something in the outer transaction
...

# Suppress to prevent influence on the outer transaction
with contextlib.suppress(ValueError):
async with db.transaction():
# Do something in the inner transaction
...

raise ValueError('Abort the inner transaction')

# Observe the results of the outer transaction,
# without effects from the inner transaction.
await db.fetch_all('SELECT * FROM ...')
```
Nested transactions are fully supported, and are implemented using database savepoints:

```python
async with database.transaction(isolation_level="serializable"):
...
{!> ../docs_src/transactions/nested_transactions.py !}
```
2 changes: 1 addition & 1 deletion docs/database.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Connection and Database
# Connecting to a database

Databasez handles database connection pooling and transaction management
with minimal fuss. It'll automatically deal with acquiring and releasing
Expand Down
2 changes: 1 addition & 1 deletion docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,5 +319,5 @@ forked from [Encode](https://github.com/encode/databases) with additional featur

* SQLAlchemy 2+ integration.
* Additonal support for `mssql`.
* [Connection as dict](./connections-and-transactions.md#connection-options-as-a-dictionary).
* [Connection as dict](./database.md#connection-options-as-a-dictionary).
* Brings a native test client.
12 changes: 12 additions & 0 deletions docs_src/transactions/async_context_connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from databasez import Database


async def main():
async with Database("<URL>") as database, database.connection() as connection:
# do something
async with connection.transaction():
...

# check something and then reset
async with connection.transaction(force_rollback=True):
...
12 changes: 12 additions & 0 deletions docs_src/transactions/async_context_database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from databasez import Database


async def main():
async with Database("<URL>") as database:
# do something
async with database.transaction():
...

# check something and then reset
async with database.transaction(force_rollback=True):
...
15 changes: 15 additions & 0 deletions docs_src/transactions/decorating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from databasez import Database

database = Database("<URL>")


@database.transaction()
async def create_users(request):
...
# do something


async def main():
async with database:
# now the transaction is activated
await create_users()
21 changes: 21 additions & 0 deletions docs_src/transactions/isolation_level.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import asyncio

from databasez import Database, core


async def add_excitement(connection: core.Connection, id: int):
await connection.execute(
"UPDATE notes SET text = CONCAT(text, '!!!') WHERE id = :id", {"id": id}
)


async with Database("database_url") as database:
async with database.transaction():
# This note won't exist until the transaction closes...
await database.execute("INSERT INTO notes(id, text) values (1, 'databases is cool')")
# ...but child tasks can use this connection now!
await asyncio.create_task(add_excitement(database.connection(), id=1))

async with database.transaction(isolation_level="serializable"):
await database.fetch_val("SELECT text FROM notes WHERE id=1")
# ^ returns: "databases is cool!!!"
15 changes: 15 additions & 0 deletions docs_src/transactions/manually.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from databasez import Database


async def main():
async with Database("<URL>") as database, database.connection() as connection:
# get the activated transaction
transaction = await connection.transaction()

try:
# do something
...
except Exception:
await transaction.rollback()
else:
await transaction.commit()
20 changes: 20 additions & 0 deletions docs_src/transactions/nested_transactions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import contextlib

import databasez

async with databasez.Database("database_url") as db:
async with db.transaction() as outer:
# Do something in the outer transaction
...

# Suppress to prevent influence on the outer transaction
with contextlib.suppress(ValueError):
async with db.transaction():
# Do something in the inner transaction
...

raise ValueError("Abort the inner transaction")

# Observe the results of the outer transaction,
# without effects from the inner transaction.
await db.fetch_all("SELECT * FROM ...")

0 comments on commit c742de3

Please sign in to comment.