Skip to content

Commit 40fe808

Browse files
committed
before_commit callback in middleware
1 parent 4fd3b9f commit 40fe808

4 files changed

Lines changed: 147 additions & 152 deletions

File tree

docs/examples/index.html

Lines changed: 70 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,10 @@
6161
<li class="toctree-l2"><a class="reference internal" href="#manually-close-the-transaction-and-session">Manually close the transaction and session</a>
6262
</li>
6363
<li class="toctree-l2"><a class="reference internal" href="#multiple-sessions-and-concurrent-execution">Multiple sessions and concurrent execution</a>
64-
<ul>
65-
<li class="toctree-l3"><a class="reference internal" href="#read-your-own-writes">Read Your Own Writes</a>
6664
</li>
67-
<li class="toctree-l3"><a class="reference internal" href="#rollback">Rollback</a>
65+
<li class="toctree-l2"><a class="reference internal" href="#rollback">Rollback</a>
6866
</li>
69-
</ul>
67+
<li class="toctree-l2"><a class="reference internal" href="#read-your-own-writes">Read Your Own Writes</a>
7068
</li>
7169
</ul>
7270
</li>
@@ -238,7 +236,7 @@ <h3 id="manually-close-the-transaction-and-session">Manually close the transacti
238236
)
239237
await session.execute(stmt)
240238
</code></pre>
241-
<h2 id="multiple-sessions-and-concurrent-execution">Multiple sessions and concurrent execution</h2>
239+
<h3 id="multiple-sessions-and-concurrent-execution">Multiple sessions and concurrent execution</h3>
242240
<pre><code class="language-python">import asyncio
243241

244242
from context_async_sqlalchemy import (
@@ -309,6 +307,73 @@ <h2 id="multiple-sessions-and-concurrent-execution">Multiple sessions and concur
309307
await session.execute(stmt)
310308
await session.commit()
311309
</code></pre>
310+
<h3 id="rollback">Rollback</h3>
311+
<pre><code class="language-python">from context_async_sqlalchemy import db_session
312+
from sqlalchemy import insert
313+
314+
from ..database import connection
315+
from ..models import ExampleTable
316+
317+
318+
async def handler_with_db_session_and_exception() -&gt; None:
319+
&quot;&quot;&quot;
320+
let's imagine that an exception occurred.
321+
&quot;&quot;&quot;
322+
session = await db_session(connection)
323+
stmt = insert(ExampleTable).values(text=&quot;example_with_db_session&quot;)
324+
await session.execute(stmt)
325+
326+
raise Exception(&quot;Some exception&quot;)
327+
# transaction automatically rolls back
328+
</code></pre>
329+
<pre><code class="language-python">from fastapi import HTTPException
330+
331+
from context_async_sqlalchemy import db_session
332+
from sqlalchemy import insert
333+
334+
from ..database import connection
335+
from ..models import ExampleTable
336+
337+
338+
async def handler_with_db_session_and_http_exception() -&gt; None:
339+
&quot;&quot;&quot;
340+
let's imagine that an http exception occurred.
341+
&quot;&quot;&quot;
342+
session = await db_session(connection)
343+
stmt = insert(ExampleTable).values(text=&quot;example_with_db_session&quot;)
344+
await session.execute(stmt)
345+
346+
raise HTTPException(status_code=500)
347+
# transaction rolls back automatically by status code
348+
</code></pre>
349+
<pre><code class="language-python">from context_async_sqlalchemy import db_session, rollback_db_session
350+
from sqlalchemy import insert
351+
352+
from ..database import connection
353+
from ..models import ExampleTable
354+
355+
356+
async def handler_with_db_session_and_manual_rollback() -&gt; None:
357+
&quot;&quot;&quot;
358+
An example of a handle that uses a rollback
359+
&quot;&quot;&quot;
360+
# it's convenient this way
361+
await _insert()
362+
await rollback_db_session(connection)
363+
364+
# but it's possible this way too
365+
await _insert()
366+
session = await db_session(connection)
367+
await session.rollback()
368+
369+
370+
async def _insert() -&gt; None:
371+
session = await db_session(connection)
372+
stmt = insert(ExampleTable).values(
373+
text=&quot;example_with_db_session_and_manual_close&quot;
374+
)
375+
await session.execute(stmt)
376+
</code></pre>
312377
<h3 id="read-your-own-writes">Read Your Own Writes</h3>
313378
<p>The "Read Your Own Writes" pattern ensures that after a write request, subsequent read requests
314379
always see the data that was just written — even when reads are served by a replica.</p>
@@ -389,74 +454,6 @@ <h3 id="read-your-own-writes">Read Your Own Writes</h3>
389454
<p>The <a href="https://github.com/krylosov-aa/pg-status">pg-status</a> provides a ready-made helper
390455
for exactly this: it lets you poll replicas and pick the first one whose replay LSN is at or
391456
ahead of the required value, falling back to the primary if none qualifies.</p>
392-
<hr />
393-
<h3 id="rollback">Rollback</h3>
394-
<pre><code class="language-python">from context_async_sqlalchemy import db_session
395-
from sqlalchemy import insert
396-
397-
from ..database import connection
398-
from ..models import ExampleTable
399-
400-
401-
async def handler_with_db_session_and_exception() -&gt; None:
402-
&quot;&quot;&quot;
403-
let's imagine that an exception occurred.
404-
&quot;&quot;&quot;
405-
session = await db_session(connection)
406-
stmt = insert(ExampleTable).values(text=&quot;example_with_db_session&quot;)
407-
await session.execute(stmt)
408-
409-
raise Exception(&quot;Some exception&quot;)
410-
# transaction automatically rolls back
411-
</code></pre>
412-
<pre><code class="language-python">from fastapi import HTTPException
413-
414-
from context_async_sqlalchemy import db_session
415-
from sqlalchemy import insert
416-
417-
from ..database import connection
418-
from ..models import ExampleTable
419-
420-
421-
async def handler_with_db_session_and_http_exception() -&gt; None:
422-
&quot;&quot;&quot;
423-
let's imagine that an http exception occurred.
424-
&quot;&quot;&quot;
425-
session = await db_session(connection)
426-
stmt = insert(ExampleTable).values(text=&quot;example_with_db_session&quot;)
427-
await session.execute(stmt)
428-
429-
raise HTTPException(status_code=500)
430-
# transaction rolls back automatically by status code
431-
</code></pre>
432-
<pre><code class="language-python">from context_async_sqlalchemy import db_session, rollback_db_session
433-
from sqlalchemy import insert
434-
435-
from ..database import connection
436-
from ..models import ExampleTable
437-
438-
439-
async def handler_with_db_session_and_manual_rollback() -&gt; None:
440-
&quot;&quot;&quot;
441-
An example of a handle that uses a rollback
442-
&quot;&quot;&quot;
443-
# it's convenient this way
444-
await _insert()
445-
await rollback_db_session(connection)
446-
447-
# but it's possible this way too
448-
await _insert()
449-
session = await db_session(connection)
450-
await session.rollback()
451-
452-
453-
async def _insert() -&gt; None:
454-
session = await db_session(connection)
455-
stmt = insert(ExampleTable).values(
456-
text=&quot;example_with_db_session_and_manual_close&quot;
457-
)
458-
await session.execute(stmt)
459-
</code></pre>
460457

461458
</div>
462459
</div><footer>

docs/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,5 +221,5 @@ <h2 id="how-it-works">How it works</h2>
221221

222222
<!--
223223
MkDocs version : 1.6.1
224-
Build Date UTC : 2026-06-14 09:48:21.276136+00:00
224+
Build Date UTC : 2026-06-14 09:53:08.796219+00:00
225225
-->

docs/search/search_index.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

docs_sources/docs/examples.md

Lines changed: 75 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ async def _insert_3() -> None:
134134
await session.execute(stmt)
135135
```
136136

137-
## Multiple sessions and concurrent execution
137+
### Multiple sessions and concurrent execution
138138

139139
```python
140140
import asyncio
@@ -208,6 +208,80 @@ async def _insert_non_ctx_manual() -> None:
208208
await session.commit()
209209
```
210210

211+
### Rollback
212+
213+
```python
214+
from context_async_sqlalchemy import db_session
215+
from sqlalchemy import insert
216+
217+
from ..database import connection
218+
from ..models import ExampleTable
219+
220+
221+
async def handler_with_db_session_and_exception() -> None:
222+
"""
223+
let's imagine that an exception occurred.
224+
"""
225+
session = await db_session(connection)
226+
stmt = insert(ExampleTable).values(text="example_with_db_session")
227+
await session.execute(stmt)
228+
229+
raise Exception("Some exception")
230+
# transaction automatically rolls back
231+
```
232+
233+
```python
234+
from fastapi import HTTPException
235+
236+
from context_async_sqlalchemy import db_session
237+
from sqlalchemy import insert
238+
239+
from ..database import connection
240+
from ..models import ExampleTable
241+
242+
243+
async def handler_with_db_session_and_http_exception() -> None:
244+
"""
245+
let's imagine that an http exception occurred.
246+
"""
247+
session = await db_session(connection)
248+
stmt = insert(ExampleTable).values(text="example_with_db_session")
249+
await session.execute(stmt)
250+
251+
raise HTTPException(status_code=500)
252+
# transaction rolls back automatically by status code
253+
```
254+
255+
```python
256+
from context_async_sqlalchemy import db_session, rollback_db_session
257+
from sqlalchemy import insert
258+
259+
from ..database import connection
260+
from ..models import ExampleTable
261+
262+
263+
async def handler_with_db_session_and_manual_rollback() -> None:
264+
"""
265+
An example of a handle that uses a rollback
266+
"""
267+
# it's convenient this way
268+
await _insert()
269+
await rollback_db_session(connection)
270+
271+
# but it's possible this way too
272+
await _insert()
273+
session = await db_session(connection)
274+
await session.rollback()
275+
276+
277+
async def _insert() -> None:
278+
session = await db_session(connection)
279+
stmt = insert(ExampleTable).values(
280+
text="example_with_db_session_and_manual_close"
281+
)
282+
await session.execute(stmt)
283+
```
284+
211285
### Read Your Own Writes
212286

213287
The "Read Your Own Writes" pattern ensures that after a write request, subsequent read requests
@@ -303,79 +377,3 @@ it against the client-supplied LSN.
303377
The [pg-status](https://github.com/krylosov-aa/pg-status) provides a ready-made helper
304378
for exactly this: it lets you poll replicas and pick the first one whose replay LSN is at or
305379
ahead of the required value, falling back to the primary if none qualifies.
306-
307-
---
308-
309-
### Rollback
310-
311-
```python
312-
from context_async_sqlalchemy import db_session
313-
from sqlalchemy import insert
314-
315-
from ..database import connection
316-
from ..models import ExampleTable
317-
318-
319-
async def handler_with_db_session_and_exception() -> None:
320-
"""
321-
let's imagine that an exception occurred.
322-
"""
323-
session = await db_session(connection)
324-
stmt = insert(ExampleTable).values(text="example_with_db_session")
325-
await session.execute(stmt)
326-
327-
raise Exception("Some exception")
328-
# transaction automatically rolls back
329-
```
330-
331-
```python
332-
from fastapi import HTTPException
333-
334-
from context_async_sqlalchemy import db_session
335-
from sqlalchemy import insert
336-
337-
from ..database import connection
338-
from ..models import ExampleTable
339-
340-
341-
async def handler_with_db_session_and_http_exception() -> None:
342-
"""
343-
let's imagine that an http exception occurred.
344-
"""
345-
session = await db_session(connection)
346-
stmt = insert(ExampleTable).values(text="example_with_db_session")
347-
await session.execute(stmt)
348-
349-
raise HTTPException(status_code=500)
350-
# transaction rolls back automatically by status code
351-
```
352-
353-
```python
354-
from context_async_sqlalchemy import db_session, rollback_db_session
355-
from sqlalchemy import insert
356-
357-
from ..database import connection
358-
from ..models import ExampleTable
359-
360-
361-
async def handler_with_db_session_and_manual_rollback() -> None:
362-
"""
363-
An example of a handle that uses a rollback
364-
"""
365-
# it's convenient this way
366-
await _insert()
367-
await rollback_db_session(connection)
368-
369-
# but it's possible this way too
370-
await _insert()
371-
session = await db_session(connection)
372-
await session.rollback()
373-
374-
375-
async def _insert() -> None:
376-
session = await db_session(connection)
377-
stmt = insert(ExampleTable).values(
378-
text="example_with_db_session_and_manual_close"
379-
)
380-
await session.execute(stmt)
381-
```

0 commit comments

Comments
 (0)