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
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ transactions
- Not a wrapper around SQLAlchemy
- Convenient for testing
- Runtime host switching
- Supports multiple databases and multiple sessions per database
- Supports multiple databases and sessions per database
- Provides tools for running concurrent SQL queries
- Fully lazy initialization

Expand All @@ -37,25 +37,25 @@ from context_async_sqlalchemy import db_session
from sqlalchemy import insert

from database import connection # your configured connection to the database
from models import ExampleTable # just some model for example
from models import ExampleTable # a model for example

async def some_func() -> None:
# Created a session (no connection to the database yet)
# Creates a session with no connection to the database yet
session = await db_session(connection)

stmt = insert(ExampleTable).values(text="example_with_db_session")

# On the first request, a connection and transaction were opened
# A connection and transaction open in the first request.
await session.execute(stmt)

# If you call db_session again, it will return the same session
# If you call db_session again, it returns the same session
# even in child coroutines.
session = await db_session(connection)

# The second request will use the same connection and the same transaction
# The second request uses the same connection and the same transaction
await session.execute(stmt)

# The commit and closing of the session will occur automatically
# The commit and closing of the session occurs automatically
```

## How it works
Expand All @@ -70,5 +70,4 @@ existing ones.
3. The middleware automatically commits or rolls back open
transactions. It also closes open sessions and clears the context.

The library also provides the ability to commit, roll back, and close at any
time without waiting for the end of the request.
The library provides the ability to commit, roll back, and close at any
16 changes: 7 additions & 9 deletions context_async_sqlalchemy/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ async def atomic_db_session(
current_transaction: _current_transaction_choices = "commit",
) -> AsyncGenerator[AsyncSession, None]:
"""
A context manager that can be used to wrap another function which
uses a context session, making that call isolated within its
A context manager that you can use to wrap another function which
uses a context session, isolating the call within its
own transaction.

There are several options that define how the function will handle
an already open transaction.
There are several options that define how the function handles
an open transaction.
current_transaction:
"commit" - commits the open transaction and starts a new one
"rollback" - rolls back the open transaction and starts a new one
Expand Down Expand Up @@ -80,7 +80,7 @@ async def atomic_db_session(

async def commit_db_session(connect: DBConnect) -> None:
"""
Commits the active session, if there is one.
Commits the active session.

example of use:
await your_function_with_db_session()
Expand All @@ -93,7 +93,7 @@ async def commit_db_session(connect: DBConnect) -> None:

async def rollback_db_session(connect: DBConnect) -> None:
"""
Rollbacks the active session, if there is one.
Rolls back the active session.

example of use:
await your_function_with_db_session()
Expand All @@ -108,9 +108,7 @@ async def close_db_session(connect: DBConnect) -> None:
"""
Closes the active session (and connection), if there is one.

This is useful if, for example, at the beginning of the handle a
database query is needed, and then there is some other long-term work
and you don't want to keep the connection opened.
Use if you have more work you need to complete without keeping the connection open.

example of use:
await your_function_with_db_session()
Expand Down
2 changes: 1 addition & 1 deletion context_async_sqlalchemy/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async def put_savepoint_session_in_ctx(
a transaction. You need to pass the session you're using inside
your tests to attach a new session to the same connection.

It is important to use this function inside set_test_context.
Use this function inside set_test_context.
"""
session_maker = await connection.session_maker()
async with session_maker(
Expand Down
90 changes: 43 additions & 47 deletions docs/api/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
</li>
<li class="toctree-l2"><a class="reference internal" href="#middlewares">Middlewares</a>
<ul>
<li class="toctree-l3"><a class="reference internal" href="#fastapi">Fastapi</a>
<li class="toctree-l3"><a class="reference internal" href="#fastapi">FastAPI</a>
</li>
<li class="toctree-l3"><a class="reference internal" href="#starlette">Starlette</a>
</li>
Expand Down Expand Up @@ -160,12 +160,14 @@

<h1 id="api-reference">API Reference</h1>
<h2 id="dbconnect">DBConnect</h2>
<p>DBConnect is responsible for managing the engine and the session_maker.
You need to define two factories.
Optionally, you can specify a host to connect to.
You can also specify a handler that runs before a session is created -
this handler can be used to connect to the host for the first time or
to reconnect to a new one.</p>
<p>DBConnect is responsible for managing the engine and <code>session_maker</code>.
You need to define two factories:
<ul>
<li>Specify host to which you want to connect (optional).</li>
<li>Specify a handler that runs before a session is created. It be used to connect to the host for the first time or
to reconnect to a new one.</li>
</ul>
</p>
<h3 id="init">init</h3>
<pre><code class="language-python">def __init__(
self: DBConnect,
Expand Down Expand Up @@ -208,39 +210,36 @@ <h3 id="connect">connect</h3>
<pre><code class="language-python">async def connect(self: DBConnect, host: str) -&gt; None:
</code></pre>
<p>Establishes a connection to the specified host.
This method doesn’t need to be called explicitly.
If it isn’t called, the first session request will automatically
establish the connection.</p>
It doesn’t need to be called explicitly.
The first session request automatically
establishes the connection.</p>
<hr />
<h3 id="change_host">change_host</h3>
<pre><code class="language-python">async def change_host(self: DBConnect, host: str) -&gt; None:
</code></pre>
<p>Establishes a connection to the specified host, but first
checks under a lock that the currently connected host is different
Establishes a connection to the specified host. It validates that the currently connected host is different
from the target host.</p>
<hr />
<h3 id="create_session">create_session</h3>
<pre><code class="language-python">async def create_session(self: DBConnect) -&gt; AsyncSession:
</code></pre>
<p>Creates a new session. Used internally by the library -
you’ll probably never need to call it directly, but it’s
good to know it exists.</p>
<p>Creates a new session the library uses internally.
You never need to call it directly.</p>
<hr />
<h3 id="session_maker">session_maker</h3>
<pre><code class="language-python">async def session_maker(self: DBConnect) -&gt; async_sessionmaker[AsyncSession]:
</code></pre>
<p>Provides access to the session_maker currently used to create sessions.</p>
<p>Provides access to the <code>session_maker</code> currently used to create sessions.</p>
<hr />
<h3 id="close">close</h3>
<pre><code class="language-python">async def close(self: DBConnect) -&gt; None:
</code></pre>
<p>Completely closes and cleans up all resources, freeing the connection pool.
This should be called at the end of your application’s lifecycle.</p>
<p>Closes and cleans up all resources, freeing the connection pool.
Use this call at the end of your application’s life cycle.</p>
<h2 id="middlewares">Middlewares</h2>
<p>Most of the work - and the “magic” - happens inside the middleware.</p>
<p>You can check out <a href="../how_middleware_works">how it works</a> and implement your
<p>Most of the work the “magic” happens inside the middleware. Check out <a href="../how_middleware_works">how it works</a> and implement your
own.</p>
<h3 id="fastapi">Fastapi</h3>
<h3 id="fastapi">FastAPI</h3>
<pre><code class="language-python">from context_async_sqlalchemy.fastapi_utils import (
fastapi_http_db_session_middleware,
add_fastapi_http_db_session_middleware,
Expand Down Expand Up @@ -285,7 +284,7 @@ <h3 id="pure-asgi">Pure ASGI</h3>
app.add_middleware(ASGIHTTPDBSessionMiddleware)
</code></pre>
<h2 id="sessions">Sessions</h2>
<p>Here are the functions you’ll use most often from the library.
<p>Here are the library functions you will use most often.
They allow you to work with sessions directly from your asynchronous code.</p>
<h3 id="db_session">db_session</h3>
<pre><code class="language-python">async def db_session(connect: DBConnect) -&gt; AsyncSession:
Expand All @@ -303,8 +302,7 @@ <h3 id="atomic_db_session">atomic_db_session</h3>
</code></pre>
<p>A context manager that can be used to wrap another function which
uses a context session, making that call isolated within its own transaction.</p>
<p>There are several options that define how the function will handle
an already open transaction.</p>
<p>Several options define how a function handles an open transaction.</p>
<p>current_transaction:</p>
<ul>
<li><code>commit</code> - commits the open transaction and starts a new one</li>
Expand All @@ -316,31 +314,28 @@ <h3 id="atomic_db_session">atomic_db_session</h3>
<h3 id="commit_db_session">commit_db_session</h3>
<pre><code class="language-python">async def commit_db_session(connect: DBConnect) -&gt; None:
</code></pre>
<p>Commits the active session, if there is one.</p>
<p>Commits the active session.</p>
<hr />
<h3 id="rollback_db_session">rollback_db_session</h3>
<pre><code class="language-python">async def rollback_db_session(connect: DBConnect) -&gt; None:
</code></pre>
<p>Rollbacks the active session, if there is one.</p>
<p>Rolls back an active session.</p>
<hr />
<h3 id="close_db_session">close_db_session</h3>
<pre><code class="language-python">async def close_db_session(connect: DBConnect) -&gt; None:
</code></pre>
<p>Closes the current context session. The connection is returned to the pool.
If you close an uncommitted transaction, the connection will be rolled back.</p>
<p>This is useful if, for example, at the beginning of the handle a
database query is needed, and then there is some other long-term work
and you don't want to keep the connection opened.</p>
<p>Closes the current context session and returns the connection to the pool.
If you close an uncommitted transaction, the connection rolls back.</p>
<p>This is useful if a database querly is needed at the beginning of the handle.</p>
<p>You have have more work you need to complete over time without being able to keep the connection open.</p>
<hr />
<h3 id="new_non_ctx_session">new_non_ctx_session</h3>
<pre><code class="language-python">@asynccontextmanager
async def new_non_ctx_session(
connect: DBConnect,
) -&gt; AsyncGenerator[AsyncSession, None]:
</code></pre>
<p>A context manager that allows you to create a new session without placing
it in a context. It's used for manual session management when you
don't want to use a context.</p>
<p>A context manager that allows you to create a new session without placing it in a context.</p>
<hr />
<h3 id="new_non_ctx_atomic_session">new_non_ctx_atomic_session</h3>
<pre><code class="language-python">@asynccontextmanager
Expand All @@ -359,11 +354,11 @@ <h3 id="run_in_new_ctx">run_in_new_ctx</h3>
**kwargs: Any,
) -&gt; AsyncCallableResult:
</code></pre>
<p>Runs a function in a new context with new sessions that have their
own connection.</p>
<p>It will commit the transaction automatically if callable_func does not
raise exceptions. Otherwise, the transaction will be rolled back.</p>
<p>The intended use is to run multiple database queries concurrently.</p>
<p>Runs a function in a new context with new session(s) that have a
separate connection.</p>
<p>It commits the transaction automatically if <code>callable_func</code> does not
raise exceptions. Otherwise, the transaction rolls back.</p>
<p>It is intended to allow you to run multiple database queries concurrently.</p>
<p>example of use:</p>
<pre><code class="language-python">await asyncio.gather(
your_function_with_db_session(...),
Expand All @@ -374,30 +369,28 @@ <h3 id="run_in_new_ctx">run_in_new_ctx</h3>
)
</code></pre>
<h2 id="testing">Testing</h2>
<p>You can read more about testing here: <a href="../testing">Testing</a></p>

<h3 id="rollback_session">rollback_session</h3>
<pre><code class="language-python">@asynccontextmanager
async def rollback_session(
connection: DBConnect,
) -&gt; AsyncGenerator[AsyncSession, None]:
</code></pre>
<p>A context manager that creates a session which is automatically rolled
back at the end.
It’s intended for use in fixtures to execute SQL queries during tests.</p>
<p>A context manager that creates a session which automatically rolls back at the end of the session.
It is intended for you to use in fixtures to execute SQL queries during tests.</p>
<hr />
<h3 id="set_test_context">set_test_context</h3>
<pre><code class="language-python">@asynccontextmanager
async def set_test_context(auto_close: bool = False) -&gt; AsyncGenerator[None, None]:
</code></pre>
<p>A context manager that creates a new context in which you can place a
dedicated test session.
It’s intended for use in tests where the test and the application share
a single transaction.</p>
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, and you want all sessions to be closed automatically.</p>
active. All sessions close automatically.</p>
<hr />
<h3 id="put_savepoint_session_in_ctx">put_savepoint_session_in_ctx</h3>
<pre><code class="language-python">async def put_savepoint_session_in_ctx(
Expand All @@ -408,8 +401,11 @@ <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>

</div>
</div><footer>
Expand Down
16 changes: 7 additions & 9 deletions docs/concurrent_queries/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,10 @@ <h1 id="concurrent-sql-queries">Concurrent sql queries</h1>

async def handler_multiple_sessions() -&gt; None:
&quot;&quot;&quot;
In some situations, you need to have multiple sessions running
simultaneously. For example, to run several queries concurrently.
You may need to to run multiple sessions. For example, to run several queries concurrently.

You can also use these same techniques to create new sessions whenever you
need them. Not necessarily just because of the concurrent processing.
You can also use the same techniques to create new sessions whenever you
need them, ot necessarily because of the concurrent processing.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A small typo, missing the n in the word not

&quot;&quot;&quot;
await asyncio.gather(
_insert(), # context session
Expand All @@ -154,17 +153,16 @@ <h1 id="concurrent-sql-queries">Concurrent sql queries</h1>
stmt = insert(ExampleTable).values(text=text)
await session.execute(stmt)

# You can manually commit the transaction if you want, but it is not
# necessary
# manually commits the transaction (optional)
await commit_db_session(connection)

# You can manually close the session if you want, but it is not necessary
# manually closes the session (optional)
await close_db_session(connection)


async def _insert_non_ctx() -&gt; None:
&quot;&quot;&quot;
You don't have to use the context to work with sessions at all
Using context to work with sessions is optional.
&quot;&quot;&quot;
async with new_non_ctx_atomic_session(connection) as session:
stmt = insert(ExampleTable).values(text=&quot;example_multiple_sessions&quot;)
Expand All @@ -173,7 +171,7 @@ <h1 id="concurrent-sql-queries">Concurrent sql queries</h1>

async def _insert_non_ctx_manual() -&gt; None:
&quot;&quot;&quot;
You don't have to use the context to work with sessions at all
Using context to work with sessions is optional.
&quot;&quot;&quot;
async with new_non_ctx_session(connection) as session:
stmt = insert(ExampleTable).values(text=&quot;example_multiple_sessions&quot;)
Expand Down
Loading