Skip to content

Commit e870c3a

Browse files
committed
New version of docs
1 parent e838e84 commit e870c3a

40 files changed

+1328
-934
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@ testsuite.sqlite3
1414
test_testsuite.sqlite3
1515
/site
1616
/dist
17+
/docs/generated
18+
/docs/.generated.tmp
19+
/.cache
20+
.DS_Store

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ Databasez is suitable for integrating against any async Web framework, such as [
5656
[Starlette][starlette], [Sanic][sanic], [Responder][responder], [Quart][quart], [aiohttp][aiohttp],
5757
[Tornado][tornado], or [FastAPI][fastapi].
5858

59-
Databasez was built for Python 3.9+ and on the top of the newest **SQLAlchemy 2** and gives you
59+
Databasez was built for Python 3.10+ and on the top of the newest **SQLAlchemy 2** and gives you
6060
simple asyncio support for a range of databases.
6161

6262
### Special notes
6363

64-
This package couldn't exist without [Databases](https://www.encode.io/databasex/) and the continuous work
64+
This package couldn't exist without [Databases](https://www.encode.io/databases/) and the continuous work
6565
done by the amazing team behind it. For that reason, thank you!
6666

6767
## Installation
@@ -70,7 +70,7 @@ done by the amazing team behind it. For that reason, thank you!
7070
$ pip install databasez
7171
```
7272

73-
If you are interested in using the [test client](./test-client.md), you can also install:
73+
If you are interested in using the [test client](https://databasez.dymmond.com/test-client/), you can also install:
7474

7575
```shell
7676
$ pip install databasez[testing]

Taskfile.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
version: 3
2+
3+
tasks:
4+
docs_prepare:
5+
desc: Prepares documentation sources for Zensical
6+
cmds:
7+
- hatch run docs:prepare
8+
9+
docs_clean:
10+
desc: Cleans generated docs artifacts
11+
cmds:
12+
- hatch run docs:clean
13+
14+
build:
15+
desc: Builds the documentation
16+
cmds:
17+
- hatch run docs:build
18+
19+
serve:
20+
desc: Runs the documentation in live mode
21+
cmds:
22+
- hatch run docs:serve
23+
24+
serve_dev:
25+
desc: Runs the documentation in dev mode
26+
cmds:
27+
- hatch run docs:dev

docs/architecture-overview.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Architecture Overview
2+
3+
Databasez is built around a small set of objects:
4+
5+
- `Database` for pool lifecycle and task-local connection routing.
6+
- `Connection` for query execution and transaction orchestration.
7+
- `Transaction` for context/decorator/manual transaction control.
8+
- Backend classes (`DatabaseBackend`, `ConnectionBackend`, `TransactionBackend`) for dialect-specific behavior.
9+
10+
## Component relationships
11+
12+
```mermaid
13+
flowchart TD
14+
A["Database"] --> B["Connection"]
15+
B --> C["Transaction"]
16+
A --> D["DatabaseBackend"]
17+
D --> E["ConnectionBackend"]
18+
E --> F["TransactionBackend"]
19+
D --> G["SQLAlchemy AsyncEngine"]
20+
```
21+
22+
## Query lifecycle
23+
24+
```mermaid
25+
sequenceDiagram
26+
participant App as Application code
27+
participant DB as Database
28+
participant Conn as Connection
29+
participant Backend as ConnectionBackend
30+
participant Engine as AsyncEngine
31+
32+
App->>DB: fetch_all/execute/iterate
33+
DB->>DB: resolve task-local or global connection
34+
DB->>Conn: open context
35+
Conn->>Backend: acquire()
36+
Backend->>Engine: connect + execute
37+
Engine-->>Backend: result
38+
Backend-->>Conn: parsed rows/rowcount
39+
Conn-->>DB: result
40+
DB-->>App: return value
41+
Conn->>Backend: release()
42+
```
43+
44+
## Multiloop model
45+
46+
Databasez is loop-aware. If the same `Database` is used from a different event loop, it can create a sub-database for that loop and proxy operations safely.
47+
48+
```mermaid
49+
flowchart LR
50+
L1["Loop A"] --> DB["Database (root)"]
51+
L2["Loop B"] --> SUB["Sub-database (auto-created)"]
52+
DB --> SUB
53+
DB --> G["Global force_rollback connection"]
54+
SUB --> G
55+
```
56+
57+
For deeper details:
58+
59+
- [Core Concepts](./core-concepts.md)
60+
- [Connections & Transactions](./connections-and-transactions.md)
61+
- [Extra drivers and overwrites](./extra-drivers-and-overwrites.md)

docs/connections-and-transactions.md

Lines changed: 70 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,116 +2,131 @@
22

33
## Connections
44

5-
Connections are the heart-piece of databasez. They are enabling rollback behavior, via transactions
6-
and provide all manipulation functions.
7-
Despite there are shortcut methods on the [Database object](./database.md), they are all using
8-
connections internally.
9-
When wanting for performance reasons one connection doing multiple jobs, retrieving a connection via
10-
`connection()` and perform all tasks within the connection context (connections are async contextmanagers) are the way to go.
5+
Connections are the core execution unit in Databasez. Even when you call high-level methods on `Database`, those calls are routed through a `Connection`.
116

12-
### Multithreading
7+
If you want multiple operations in one scoped connection, use:
138

14-
`sqlalchemy`'s async addon is also multi-threading capable but we cannot use this for two reasons:
9+
```python
10+
async with database.connection() as connection:
11+
await connection.execute(
12+
"INSERT INTO notes(text) VALUES (:text)",
13+
{"text": "example"},
14+
)
15+
rows = await connection.fetch_all("SELECT * FROM notes")
16+
```
17+
18+
This is the recommended style for explicit control.
1519

16-
- Only the `NullPool` is supported which is afaik just no pooling.
17-
- No global connection which is reliable resetted would be possible.
20+
## Task-local behavior
1821

19-
Therefor `databasez` uses an other approach: `isolation`:
22+
Connections are task-local. Inside one task, repeated `database.connection()` calls reuse the same object. Different tasks receive different connections.
2023

21-
Event-loops are per thread. So in this approach for every new event-loop detected the database object is copied and assigned to it.
22-
This way all connections are running in a per-thread pool. The global connection is a bit different from the rest.
23-
Whenever it is accessed by an other thread, asyncio.run_coroutine_threadsafe is used.
24-
In the full-isolation mode (default) the global connection is even moved to an own thread.
24+
## Global connection in rollback mode
2525

26-
### Global connection with auto-rollback
26+
When `force_rollback=True`, `database.connection()` returns a global connection that is wrapped in rollback behavior.
2727

28-
Sometimes, especially for tests, you want a connection in which all changes are resetted when the database is closed.
29-
For having this reliable, a global connection is lazily initialized when requested on the database object via the
30-
`force_rollback` contextmanager/parameter/pseudo-attribute.
28+
This is useful for tests, but remember:
3129

32-
Whenever `connection()` is called, the same global connection is returned. It behaves nearly normally except you have just one connection
33-
available.
34-
This means you have to be careful when using `iterate` to not open another connection via `connection` (except you set `force_rollback` to False before opening the connection).
35-
Otherwise it will deadlock.
30+
- you are effectively sharing one connection
31+
- nested/parallel usage on the same connection must be planned carefully
3632

37-
Example for `force_rollback`:
33+
Runnable example:
3834

3935
```python
4036
{!> ../docs_src/connections/force_rollback.py !}
4137
```
4238

43-
## Transactions
44-
45-
Transactions are lazily initialized. You dont't need a connected database to create them via `database.transaction()`.
39+
## Multiloop and multithreading
4640

47-
When created, there are three ways to activate the Transaction
41+
Databasez supports loop-aware routing:
4842

49-
1. The async context manager way via `async with transaction: ...`
50-
2. Entering a method decorated with a transaction.
51-
3. The manual way via `await transaction`
43+
- same loop: direct call
44+
- different loop: operation is proxied using cross-loop helpers
45+
- optional full isolation: global rollback connection in a dedicated thread
5246

53-
Whenever the transaction is activated, a connected database is required.
54-
55-
A second way to get a transaction is via the `Connection`. It has also a `transaction()` method.
47+
```mermaid
48+
flowchart TD
49+
A["Call from current loop"] --> B{"Loop matches database loop?"}
50+
B -->|Yes| C["Execute directly"]
51+
B -->|No| D{"Known sub-database for loop?"}
52+
D -->|Yes| E["Forward to sub-database"]
53+
D -->|No| F["Proxy call with async helper"]
54+
```
5655

56+
## Transactions
5757

58-
### The three ways to use a transaction
58+
Transactions are lazily initialized and can be used in three ways.
5959

60-
#### Via the async context manager protocol
60+
### 1. Async context manager
6161

6262
```python
6363
{!> ../docs_src/transactions/async_context_database.py !}
6464
```
65-
It can also be acquired from a specific database connection:
65+
66+
Or from a specific connection:
6667

6768
```python
6869
{!> ../docs_src/transactions/async_context_connection.py !}
6970
```
7071

71-
#### Via decorating
72-
73-
You can also use `.transaction()` as a function decorator on any async function:
72+
### 2. Decorator
7473

7574
```python
7675
{!> ../docs_src/transactions/decorating.py !}
7776
```
7877

79-
#### Manually
80-
81-
For a lower-level transaction API:
78+
### 3. Manual control
8279

8380
```python
8481
{!> ../docs_src/transactions/manually.py !}
8582
```
8683

87-
### Auto-rollback (`force_rollback`)
84+
## Transaction options
85+
86+
### `force_rollback`
8887

89-
Transactions support an keyword parameter named `force_rollback` which default to `False`.
90-
When set to `True` at the end of the transaction a rollback is tried.
91-
This means all changes are undone.
88+
`transaction(force_rollback=True)` always rolls back on exit.
9289

93-
This is a simpler variant of `force_rollback` of the database object.
9490
```python
9591
{!> ../docs_src/transactions/force_rollback_transaction.py !}
9692
```
9793

98-
9994
### Isolation level
10095

101-
The state of a transaction is liked to the connection used in the currently executing async task.
102-
If you would like to influence an active transaction from another task, the connection must be
103-
shared:
104-
105-
Transaction isolation-level can be specified if the driver backend supports that:
96+
You can pass backend-supported isolation levels:
10697

10798
```python
10899
{!> ../docs_src/transactions/isolation_level.py !}
109100
```
110101

111102
### Nested transactions
112103

113-
Nested transactions are fully supported, and are implemented using database savepoints:
104+
Nested transactions are supported through savepoints:
114105

115106
```python
116107
{!> ../docs_src/transactions/nested_transactions.py !}
117108
```
109+
110+
## Transaction stack model
111+
112+
```mermaid
113+
sequenceDiagram
114+
participant App
115+
participant Conn as Connection
116+
participant Stack as Transaction Stack
117+
118+
App->>Conn: begin outer transaction
119+
Conn->>Stack: push outer
120+
App->>Conn: begin nested transaction
121+
Conn->>Stack: push savepoint
122+
App->>Conn: rollback nested
123+
Conn->>Stack: pop savepoint
124+
App->>Conn: commit outer
125+
Conn->>Stack: pop outer
126+
```
127+
128+
## See also
129+
130+
- [Database](./database.md)
131+
- [Queries](./queries.md)
132+
- [Troubleshooting](./troubleshooting.md)

0 commit comments

Comments
 (0)