Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/api/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ <h3 id="connect">connect</h3>
</code></pre>
<p>Establishes a connection to the specified host.
It doesn’t need to be called explicitly.
If you don't use the call, the first session request will automatically
The first session request automatically
establishes the connection.</p>
<hr />
<h3 id="change_host">change_host</h3>
Expand Down Expand Up @@ -391,7 +391,7 @@ <h3 id="set_test_context">set_test_context</h3>
It is intended to test the application when it shares a single transaction.</p>
<p>Use <code>auto_close=False</code> if you’re using a test session and transaction
that you close elsewhere in your code.</p>
<p>Use <code>auto_close=True</code> if you want to call a function
<p>Use <code>auto_close=True</code> to call a function
in a test that uses a context while the middleware is not
active. All sessions close automatically.</p>
<hr />
Expand All @@ -404,6 +404,7 @@ <h3 id="put_savepoint_session_in_ctx">put_savepoint_session_in_ctx</h3>
<p>Sets the context to a session that uses a save point instead of creating
a transaction. You need to pass the session you're using inside
your tests to attach a new session to the same connection.</p>

<pre><code>It is important to use this function inside set_test_context.
</code></pre>
<p>Learn more about <a href="../testing/">testing</a></p>
Expand Down
6 changes: 3 additions & 3 deletions docs/concurrent_queries/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,11 @@
<h1 id="concurrent-sql-queries">Concurrent sql queries</h1>
<p>Concurrent query execution deserves special attention.
In SQLAlchemy, you can’t run multiple queries concurrently within the same
session - you need to create a new one.</p>
session. You need to create a new one.</p>
<p>The library provides two simple ways to execute queries concurrently:</p>
<ul>
<li>Run a function in a new context - <code>run_in_new_ctx</code></li>
<li>Create a new session that is completely independent of the current context -
<li>Run a function in a new context: <code>run_in_new_ctx</code></li>
<li>Create a new session that is independent of the current context:
<code>new_non_ctx_atomic_session</code> or <code>new_non_ctx_session</code></li>
</ul>
<pre><code class="language-python">import asyncio
Expand Down
5 changes: 2 additions & 3 deletions docs/examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ <h3 id="manually-close-the-transaction-and-session">Manually close the transacti

# We closed the transaction
await commit_db_session(connection)

# We closed the session and returned the connection to the pool.
# We closed the session, which returned the connection to the pool automatically.
# Use if you have more work you need to complete without keeping the connection open.
await close_db_session(connection)

Expand Down Expand Up @@ -324,7 +323,7 @@ <h3 id="rollback">Rollback</h3>
await session.execute(stmt)

raise Exception(&quot;Some exception&quot;)
# transaction rolls back automatically
# transaction automatically rolls back
</code></pre>
<pre><code class="language-python">from fastapi import HTTPException

Expand Down
30 changes: 16 additions & 14 deletions docs/master_replica/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,22 +103,24 @@
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div class="section" itemprop="articleBody">

<h1 id="masterreplica-or-several-databases-at-the-same-time">Master/Replica or several databases at the same time</h1>
<p>This is why <code>db_session</code> and other functions accept a <code>DBConnect</code> instance as
input.
This approach allows you to work with multiple hosts simultaneously -
for example, with both a master and a replica.</p>
<p><code>DBConnect</code> can also accept factory functions instead of ready-made objects,
making it easy to switch hosts when needed.</p>
<p>For example, <code>libpq</code> can detect the master and replica when creating an engine,
<h1 id="masterreplica-or-several-databases-at-the-same-time">Using multiple databases at the same time (master/replica)</h1>
<p><code>db_session</code> and other functions accept a <code>DBConnect</code> instance as input. This approach allows you to work with multiple hosts simultaneously. This system allows you to manage scalability and version control.</p>
<ul>
<li><b>Master:</b> The primary database which runs the "live" version of your information, the location of your primary data set. The master database
handles INSERT, DELETE, and UPDATE API calls.
</li>
<li><b>Replicas:</b> Secondary database(s) which run READ-ONLY versions of your information and receive updates from the master database.</li>
</ul>
<p><code>DBConnect</code> accepts factory functions instead of ready-made objects, making it easy to switch hosts when needed.</p>

<p><code>libpq</code> can detect the master and replica when creating an engine,
but it only does this once - at creation time.
The before_create_session_handler hook allows you to change the host at
The <code>before_create_session_handler</code> hook allows you to change the host at
runtime if the master or replica changes.
You’ll need third-party functionality to determine which host is the master
or the replica.</p>
<h4 id="i-have-an-extremely-lightweight-microservice-pg-status-that-fits-perfectly-here">I have an extremely lightweight microservice <a href="https://github.com/krylosov-aa/pg-status">pg-status</a> that fits perfectly here.</h4>
<p>The engine is not created immediately when <code>DBConnect</code> is initialized -
it is created only on the first request.

<p><b>You need third-party functionality to determine which host is the master or the replica.</b></p>
<h4 id="change_host">I have an extremely lightweight microservice <a href="https://github.com/krylosov-aa/pg-status">pg-status</a> that fits perfectly here</h4>
<p>The engine is not created when <code>DBConnect</code> initializes, also known as lazy initialization. It is created only on the first request.
The library uses lazy initialization in many places.</p>
<pre><code class="language-python">from context_async_sqlalchemy import DBConnect

Expand Down
2 changes: 1 addition & 1 deletion docs/search/search_index.json

Large diffs are not rendered by default.

59 changes: 21 additions & 38 deletions docs/testing/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,47 +106,30 @@
<div class="section" itemprop="articleBody">

<h1 id="testing">Testing</h1>
<p>When testing with a real database, one important problem needs to be
solved - ensuring data isolation between tests.</p>
<p>There are basically two approaches:</p>
<ol>
<li>Separate sessions</li>
</ol>
<p>The test has its own session that it uses to prepare data and verify
results after execution.
The application also has its own session.
Data isolation is achieved by clearing all tables at the end of each test
(and once before running all tests).</p>
<ol>
<li>Shared session and transaction
The test and the application share the same session and transaction.
Data isolation is achieved by rolling back the transaction at
the end of the test.</li>
</ol>
<p>Personally, I prefer the first option, because it is a more "honest" way
to test the application.
We can verify how it handles sessions and transactions on its own.
It’s also convenient to inspect the database state when a test is paused.</p>
<p>Sometimes, there are complex session management scenarios (for example,
concurrent query execution) where other types of testing are either
impossible or very difficult.</p>
<p>The main disadvantage of this approach is the slower execution speed.
Since we clear all tables after each test, this process takes additional time.</p>
<p>This is where the second approach comes in - its main advantage is speed,
as rolling back a transaction is very fast.</p>
<p>When testing with a real database, one important problem needs to be solved: ensuring data isolation between tests. There are two approaches:
<ol>
<li><b>Separate sessions</b>: The test uses its own session that prepares data and verifies results after execution. The application also has its own session.
<ul>
<li>It is a more "honest" way to test the application.</li>
<li>Verifies how it handles sessions and transactions automatically.</li>
<li>The ability to inspect the database state when the test is paused.</li>
<li>Complex session management scenarios make other test methods difficult or impossible (e.g., concurrent query execution).</li>
</ul>
<li><b>Shared session and transaction:</b> The test and the application share the same session and transaction.
<ul>
<li>Rolling back transactions is faster and takes less time than starting a new session.</li>
</ul>
</ol>
</p>

<p>In my projects, I use both approaches at the same time:</p>
<ul>
<li>For most tests with simple or common logic, I use a shared transaction
for the test and the application</li>
<li>For more complex cases, or ones that cannot be tested this way,
I use separate transactions.</li>
<li>For most tests with simple or common logic, I use a shared transaction for the test and the application</li>
<li>For more complex cases, or ones that cannot be tested with separate sessions, I use separate transactions.</li>
</ul>
<p>This combination allows for both good performance and convenient testing.</p>
<p>The library provides several utilities that can be used in tests - for
example, in fixtures.
They help create tests that share a common transaction between the test
and the application, so data isolation between tests is achieved through
fast transaction rollback.</p>
<p>The library provides several utilities that can be used in tests (e.g., fixtures). They create tests that share a common transaction between the test
and the application. You achieve data isolation by rolling back transactions.

<p>You can see these capabilities in the examples:</p>
<p><a href="https://github.com/krylosov-aa/context-async-sqlalchemy/blob/main/examples/fastapi_example/tests/transactional">Here are tests with a common transaction between the
application and the tests.</a></p>
Expand Down
15 changes: 7 additions & 8 deletions docs_sources/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,7 @@ from the target host.
```python
async def create_session(self: DBConnect) -> AsyncSession:
```
Creates a new session the library uses internally.
You never need to call it directly. (Only maybe in some special cases.)
Creates a new session. Used internally by the library. You may never need to call it directly.
---

### session_maker
Expand Down Expand Up @@ -196,7 +195,8 @@ async def atomic_db_session(
A context manager you can use to wrap another function which
uses a context session, making that call isolated within its own transaction.

Several options define how a function handles an open transaction.
There are several options that define how the function will handle
an open transaction.

current_transaction:

Expand All @@ -219,19 +219,18 @@ Commits the active session.
```python
async def rollback_db_session(connect: DBConnect) -> None:
```
Rolls back an active session.
Rollbacks the active session.

---

### close_db_session
```python
async def close_db_session(connect: DBConnect) -> None:
```
Closes the current context session and returns the connection to the pool.
If you close an uncommitted transaction, the connection rolls back
Closes the current context session. The connection is returned to the pool.
If you close an uncommitted transaction, the connection rolls back.

This is useful when you need to run a database query at the start of the
handler, then continue working over time without keeping the connection open.
Use if you have more work you need to complete without keeping the connection open.

---

Expand Down