From 541bbe79e0b770f144a7a304d7a9a1af6b7ce534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ahlert?= Date: Wed, 18 Mar 2026 10:49:33 -0300 Subject: [PATCH 1/7] fix: support `async with` on async persister factory methods `AsyncSQLitePersister.from_values()` and `AsyncPostgreSQLPersister.from_values()` were async classmethods returning coroutines, which cannot be used directly with `async with`. This wraps them in `_AsyncPersisterContextManager` that supports both `await` (backwards compatible) and `async with` protocols. Closes #546 --- burr/integrations/persisters/b_aiosqlite.py | 60 +++++++++++++++++--- burr/integrations/persisters/b_asyncpg.py | 61 ++++++++++++++------- 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/burr/integrations/persisters/b_aiosqlite.py b/burr/integrations/persisters/b_aiosqlite.py index 9ce3c4a5d..aadb73dce 100644 --- a/burr/integrations/persisters/b_aiosqlite.py +++ b/burr/integrations/persisters/b_aiosqlite.py @@ -33,6 +33,32 @@ Self = None +class _AsyncPersisterContextManager: + """Wraps an async coroutine that returns a persister so it can be used + directly with ``async with``:: + + async with AsyncSQLitePersister.from_values(...) as persister: + ... + + The wrapper awaits the coroutine on ``__aenter__`` and delegates + ``__aexit__`` to the persister's own ``__aexit__``. + """ + + def __init__(self, coro): + self._coro = coro + self._persister = None + + def __await__(self): + return self._coro.__await__() + + async def __aenter__(self): + self._persister = await self._coro + return await self._persister.__aenter__() + + async def __aexit__(self, exc_type, exc_value, traceback): + return await self._persister.__aexit__(exc_type, exc_value, traceback) + + class AsyncSQLitePersister(AsyncBaseStatePersister, BaseCopyable): """Class for asynchronous SQLite persistence of state. This is a simple implementation. @@ -60,27 +86,41 @@ def copy(self) -> "Self": PARTITION_KEY_DEFAULT = "" @classmethod - async def from_config(cls, config: dict) -> "AsyncSQLitePersister": + def from_config(cls, config: dict) -> "_AsyncPersisterContextManager": """Creates a new instance of the AsyncSQLitePersister from a configuration dictionary. + Can be used with ``await`` or as an async context manager:: + + persister = await AsyncSQLitePersister.from_config(config) + # or + async with AsyncSQLitePersister.from_config(config) as persister: + ... + The config key:value pair needed are: db_path: str, table_name: str, serde_kwargs: dict, connect_kwargs: dict, """ - return await cls.from_values(**config) + return cls.from_values(**config) @classmethod - async def from_values( + def from_values( cls, db_path: str, table_name: str = "burr_state", serde_kwargs: dict = None, connect_kwargs: dict = None, - ) -> "AsyncSQLitePersister": + ) -> "_AsyncPersisterContextManager": """Creates a new instance of the AsyncSQLitePersister from passed in values. + Can be used with ``await`` or as an async context manager:: + + persister = await AsyncSQLitePersister.from_values(db_path="test.db") + # or + async with AsyncSQLitePersister.from_values(db_path="test.db") as persister: + ... + :param db_path: the path the DB will be stored. :param table_name: the table name to store things under. :param serde_kwargs: kwargs for state serialization/deserialization. @@ -88,10 +128,14 @@ async def from_values( :return: async sqlite persister instance with an open connection. You are responsible for closing the connection yourself. """ - connection = await aiosqlite.connect( - db_path, **connect_kwargs if connect_kwargs is not None else {} - ) - return cls(connection, table_name, serde_kwargs) + + async def _create(): + connection = await aiosqlite.connect( + db_path, **connect_kwargs if connect_kwargs is not None else {} + ) + return cls(connection, table_name, serde_kwargs) + + return _AsyncPersisterContextManager(_create()) def __init__( self, diff --git a/burr/integrations/persisters/b_asyncpg.py b/burr/integrations/persisters/b_asyncpg.py index 66f91f206..b3af4db11 100644 --- a/burr/integrations/persisters/b_asyncpg.py +++ b/burr/integrations/persisters/b_asyncpg.py @@ -22,6 +22,7 @@ from burr.common.types import BaseCopyable from burr.core import persistence, state from burr.integrations import base +from burr.integrations.persisters.b_aiosqlite import _AsyncPersisterContextManager try: import asyncpg @@ -106,12 +107,20 @@ async def create_pool( return cls._pool @classmethod - async def from_config(cls, config: dict) -> "AsyncPostgreSQLPersister": - """Creates a new instance of the PostgreSQLPersister from a configuration dictionary.""" - return await cls.from_values(**config) + def from_config(cls, config: dict) -> "_AsyncPersisterContextManager": + """Creates a new instance of the PostgreSQLPersister from a configuration dictionary. + + Can be used with ``await`` or as an async context manager:: + + persister = await AsyncPostgreSQLPersister.from_config(config) + # or + async with AsyncPostgreSQLPersister.from_config(config) as persister: + ... + """ + return cls.from_values(**config) @classmethod - async def from_values( + def from_values( cls, db_name: str, user: str, @@ -121,9 +130,16 @@ async def from_values( table_name: str = "burr_state", use_pool: bool = False, **pool_kwargs, - ) -> "AsyncPostgreSQLPersister": + ) -> "_AsyncPersisterContextManager": """Builds a new instance of the PostgreSQLPersister from the provided values. + Can be used with ``await`` or as an async context manager:: + + persister = await AsyncPostgreSQLPersister.from_values(...) + # or + async with AsyncPostgreSQLPersister.from_values(...) as persister: + ... + :param db_name: the name of the PostgreSQL database. :param user: the username to connect to the PostgreSQL database. :param password: the password to connect to the PostgreSQL database. @@ -133,22 +149,25 @@ async def from_values( :param use_pool: whether to use a connection pool (True) or a direct connection (False) :param pool_kwargs: additional kwargs to pass to the pool creation """ - if use_pool: - pool = await cls.create_pool( - user=user, - password=password, - database=db_name, - host=host, - port=port, - **pool_kwargs, - ) - return cls(connection=None, pool=pool, table_name=table_name) - else: - # Original behavior - direct connection - connection = await asyncpg.connect( - user=user, password=password, database=db_name, host=host, port=port - ) - return cls(connection=connection, table_name=table_name) + + async def _create(): + if use_pool: + pool = await cls.create_pool( + user=user, + password=password, + database=db_name, + host=host, + port=port, + **pool_kwargs, + ) + return cls(connection=None, pool=pool, table_name=table_name) + else: + connection = await asyncpg.connect( + user=user, password=password, database=db_name, host=host, port=port + ) + return cls(connection=connection, table_name=table_name) + + return _AsyncPersisterContextManager(_create()) def __init__( self, From 87a25acf0c6c3fd94f78927f39c5e2424d8e8b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ahlert?= Date: Tue, 24 Mar 2026 08:47:32 -0300 Subject: [PATCH 2/7] refactor: move _AsyncPersisterContextManager to burr/common/async_utils.py and add tests Address review feedback: - Move _AsyncPersisterContextManager from b_aiosqlite.py to burr/common/async_utils.py to avoid cross-dependency between unrelated integrations - Add type annotation to coro parameter - Add tests for async with pattern on from_values and from_config --- burr/common/async_utils.py | 28 ++++++++++++++++++- burr/integrations/persisters/b_aiosqlite.py | 27 +----------------- burr/integrations/persisters/b_asyncpg.py | 2 +- .../persisters/test_b_aiosqlite.py | 22 +++++++++++++++ 4 files changed, 51 insertions(+), 28 deletions(-) diff --git a/burr/common/async_utils.py b/burr/common/async_utils.py index b1f60881f..d9e1a68e1 100644 --- a/burr/common/async_utils.py +++ b/burr/common/async_utils.py @@ -16,7 +16,7 @@ # under the License. import inspect -from typing import AsyncGenerator, AsyncIterable, Generator, List, TypeVar, Union +from typing import Any, AsyncGenerator, AsyncIterable, Coroutine, Generator, List, TypeVar, Union T = TypeVar("T") @@ -27,6 +27,32 @@ SyncOrAsyncGeneratorOrItemOrList = Union[SyncOrAsyncGenerator[GenType], List[GenType], GenType] +class _AsyncPersisterContextManager: + """Wraps an async coroutine that returns a persister so it can be used + directly with ``async with``:: + + async with AsyncSQLitePersister.from_values(...) as persister: + ... + + The wrapper awaits the coroutine on ``__aenter__`` and delegates + ``__aexit__`` to the persister's own ``__aexit__``. + """ + + def __init__(self, coro: Coroutine[Any, Any, Any]): + self._coro = coro + self._persister = None + + def __await__(self): + return self._coro.__await__() + + async def __aenter__(self): + self._persister = await self._coro + return await self._persister.__aenter__() + + async def __aexit__(self, exc_type, exc_value, traceback): + return await self._persister.__aexit__(exc_type, exc_value, traceback) + + async def asyncify_generator( generator: SyncOrAsyncGenerator[GenType], ) -> AsyncGenerator[GenType, None]: diff --git a/burr/integrations/persisters/b_aiosqlite.py b/burr/integrations/persisters/b_aiosqlite.py index aadb73dce..a75eb682c 100644 --- a/burr/integrations/persisters/b_aiosqlite.py +++ b/burr/integrations/persisters/b_aiosqlite.py @@ -21,6 +21,7 @@ import aiosqlite +from burr.common.async_utils import _AsyncPersisterContextManager from burr.common.types import BaseCopyable from burr.core import State from burr.core.persistence import AsyncBaseStatePersister, PersistedStateData @@ -33,32 +34,6 @@ Self = None -class _AsyncPersisterContextManager: - """Wraps an async coroutine that returns a persister so it can be used - directly with ``async with``:: - - async with AsyncSQLitePersister.from_values(...) as persister: - ... - - The wrapper awaits the coroutine on ``__aenter__`` and delegates - ``__aexit__`` to the persister's own ``__aexit__``. - """ - - def __init__(self, coro): - self._coro = coro - self._persister = None - - def __await__(self): - return self._coro.__await__() - - async def __aenter__(self): - self._persister = await self._coro - return await self._persister.__aenter__() - - async def __aexit__(self, exc_type, exc_value, traceback): - return await self._persister.__aexit__(exc_type, exc_value, traceback) - - class AsyncSQLitePersister(AsyncBaseStatePersister, BaseCopyable): """Class for asynchronous SQLite persistence of state. This is a simple implementation. diff --git a/burr/integrations/persisters/b_asyncpg.py b/burr/integrations/persisters/b_asyncpg.py index b3af4db11..48cab6435 100644 --- a/burr/integrations/persisters/b_asyncpg.py +++ b/burr/integrations/persisters/b_asyncpg.py @@ -22,7 +22,7 @@ from burr.common.types import BaseCopyable from burr.core import persistence, state from burr.integrations import base -from burr.integrations.persisters.b_aiosqlite import _AsyncPersisterContextManager +from burr.common.async_utils import _AsyncPersisterContextManager try: import asyncpg diff --git a/tests/integrations/persisters/test_b_aiosqlite.py b/tests/integrations/persisters/test_b_aiosqlite.py index 00c98677a..9da28a5c1 100644 --- a/tests/integrations/persisters/test_b_aiosqlite.py +++ b/tests/integrations/persisters/test_b_aiosqlite.py @@ -118,6 +118,28 @@ async def test_async_persister_methods_none_partition_key( # these operations are stateful (i.e., read/write to a db) +async def test_async_sqlite_from_values_as_context_manager(tmp_path): + """Test that from_values works directly with async with (issue #546).""" + db_path = str(tmp_path / "test.db") + async with AsyncSQLitePersister.from_values(db_path=db_path) as persister: + await persister.initialize() + await persister.save("pk", "app1", 1, "pos", State({"k": "v"}), "completed") + loaded = await persister.load("pk", "app1") + assert loaded is not None + assert loaded["state"] == State({"k": "v"}) + + +async def test_async_sqlite_from_config_as_context_manager(tmp_path): + """Test that from_config works directly with async with (issue #546).""" + db_path = str(tmp_path / "test.db") + config = {"db_path": db_path, "table_name": "burr_state"} + async with AsyncSQLitePersister.from_config(config) as persister: + await persister.initialize() + await persister.save("pk", "app1", 1, "pos", State({"k": "v"}), "completed") + loaded = await persister.load("pk", "app1") + assert loaded is not None + + async def test_AsyncSQLitePersister_from_values(): await asyncio.sleep(0.00001) connection = await aiosqlite.connect(":memory:") From 059a0a0d7674be2033bcc8b55be22dfae26c6c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ahlert?= Date: Tue, 24 Mar 2026 08:54:06 -0300 Subject: [PATCH 3/7] fix: guard against __aexit__ crash and double consumption in context manager wrapper - __aexit__ now returns False when __aenter__ failed (persister is None), preventing AttributeError that would mask the original exception - Add _consumed flag to prevent silent coroutine reuse, raising RuntimeError with clear message on second await/async with - Add tests for both edge cases --- burr/common/async_utils.py | 14 ++++++++++++ .../persisters/test_b_aiosqlite.py | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/burr/common/async_utils.py b/burr/common/async_utils.py index d9e1a68e1..56ce75ff2 100644 --- a/burr/common/async_utils.py +++ b/burr/common/async_utils.py @@ -36,20 +36,34 @@ class _AsyncPersisterContextManager: The wrapper awaits the coroutine on ``__aenter__`` and delegates ``__aexit__`` to the persister's own ``__aexit__``. + + .. note:: + Each instance wraps a single coroutine and can only be consumed once, + either via ``await`` or ``async with``. A second use will raise + ``RuntimeError``. """ def __init__(self, coro: Coroutine[Any, Any, Any]): self._coro = coro self._persister = None + self._consumed = False def __await__(self): + if self._consumed: + raise RuntimeError("This factory result has already been consumed") + self._consumed = True return self._coro.__await__() async def __aenter__(self): + if self._consumed: + raise RuntimeError("This factory result has already been consumed") + self._consumed = True self._persister = await self._coro return await self._persister.__aenter__() async def __aexit__(self, exc_type, exc_value, traceback): + if self._persister is None: + return False return await self._persister.__aexit__(exc_type, exc_value, traceback) diff --git a/tests/integrations/persisters/test_b_aiosqlite.py b/tests/integrations/persisters/test_b_aiosqlite.py index 9da28a5c1..c72cc59fc 100644 --- a/tests/integrations/persisters/test_b_aiosqlite.py +++ b/tests/integrations/persisters/test_b_aiosqlite.py @@ -140,6 +140,28 @@ async def test_async_sqlite_from_config_as_context_manager(tmp_path): assert loaded is not None +async def test_async_sqlite_from_values_cannot_be_consumed_twice(): + """Test that the factory wrapper raises on double consumption.""" + wrapper = AsyncSQLitePersister.from_values(db_path=":memory:") + persister = await wrapper + with pytest.raises(RuntimeError, match="already been consumed"): + await wrapper + await persister.cleanup() + + +async def test_async_sqlite_context_manager_aexit_safe_on_failed_aenter(tmp_path): + """Test that __aexit__ doesn't crash if __aenter__ never completed.""" + from burr.common.async_utils import _AsyncPersisterContextManager + + async def _failing_create(): + raise ConnectionError("simulated connection failure") + + mgr = _AsyncPersisterContextManager(_failing_create()) + with pytest.raises(ConnectionError, match="simulated connection failure"): + async with mgr: + pass # should never reach here + + async def test_AsyncSQLitePersister_from_values(): await asyncio.sleep(0.00001) connection = await aiosqlite.connect(":memory:") From 483905cad5645846323bb21ff8885f120c5635a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ahlert?= Date: Tue, 24 Mar 2026 09:06:18 -0300 Subject: [PATCH 4/7] docs: fix async persister examples and remove redundant test helpers - Add missing `await` to from_values() calls in parallelism.rst docs - Remove AsyncSQLiteContextManager helper class from both test files, now that from_values() natively supports async with - Replace deprecated .close() calls with .cleanup() in test fixtures --- docs/concepts/parallelism.rst | 8 +++---- tests/core/test_persistence.py | 15 ++---------- .../persisters/test_b_aiosqlite.py | 23 ++++--------------- 3 files changed, 10 insertions(+), 36 deletions(-) diff --git a/docs/concepts/parallelism.rst b/docs/concepts/parallelism.rst index 0ced6bcc7..c875c5f83 100644 --- a/docs/concepts/parallelism.rst +++ b/docs/concepts/parallelism.rst @@ -698,7 +698,7 @@ When using state persistence with async parallelism, make sure to use the async from burr.integrations.persisters.b_asyncpg import AsyncPGPersister # Create an async persister with a connection pool - persister = AsyncPGPersister.from_values( + persister = await AsyncPGPersister.from_values( host="localhost", port=5432, user="postgres", @@ -707,7 +707,7 @@ When using state persistence with async parallelism, make sure to use the async use_pool=True # Important for parallelism! ) - app = ( + app = await ( ApplicationBuilder() .with_state_persister(persister) .with_action( @@ -722,12 +722,12 @@ Remember to properly clean up your async persisters when you're done with them: .. code-block:: python - # Using as a context manager + # Using as a context manager (recommended) async with AsyncPGPersister.from_values(..., use_pool=True) as persister: # Use persister here # Or manual cleanup - persister = AsyncPGPersister.from_values(..., use_pool=True) + persister = await AsyncPGPersister.from_values(..., use_pool=True) try: # Use persister here finally: diff --git a/tests/core/test_persistence.py b/tests/core/test_persistence.py index b362cd96d..deadb42a8 100644 --- a/tests/core/test_persistence.py +++ b/tests/core/test_persistence.py @@ -168,15 +168,6 @@ def test_persister_methods_none_partition_key(persistence, method_name: str, kwa """Asyncio integration for sqlite persister + """ -class AsyncSQLiteContextManager: - def __init__(self, sqlite_object): - self.client = sqlite_object - - async def __aenter__(self): - return self.client - - async def __aexit__(self, exc_type, exc, tb): - await self.client.close() @pytest.fixture() @@ -276,11 +267,9 @@ async def test_AsyncSQLitePersister_connection_shutdown(): @pytest.fixture() async def initializing_async_persistence(): - sqlite_persister = await AsyncSQLitePersister.from_values( + async with AsyncSQLitePersister.from_values( db_path=":memory:", table_name="test_table" - ) - async_context_manager = AsyncSQLiteContextManager(sqlite_persister) - async with async_context_manager as client: + ) as client: yield client diff --git a/tests/integrations/persisters/test_b_aiosqlite.py b/tests/integrations/persisters/test_b_aiosqlite.py index c72cc59fc..adb97532c 100644 --- a/tests/integrations/persisters/test_b_aiosqlite.py +++ b/tests/integrations/persisters/test_b_aiosqlite.py @@ -25,17 +25,6 @@ from burr.integrations.persisters.b_aiosqlite import AsyncSQLitePersister -class AsyncSQLiteContextManager: - def __init__(self, sqlite_object): - self.client = sqlite_object - - async def __aenter__(self): - return self.client - - async def __aexit__(self, exc_type, exc, tb): - await self.client.cleanup() - - async def test_copy_persister(async_persistence: AsyncSQLitePersister): copy = async_persistence.copy() assert copy.table_name == async_persistence.table_name @@ -45,11 +34,9 @@ async def test_copy_persister(async_persistence: AsyncSQLitePersister): @pytest.fixture() async def async_persistence(request): - sqlite_persister = await AsyncSQLitePersister.from_values( + async with AsyncSQLitePersister.from_values( db_path=":memory:", table_name="test_table" - ) - async_context_manager = AsyncSQLiteContextManager(sqlite_persister) - async with async_context_manager as client: + ) as client: yield client @@ -189,11 +176,9 @@ async def test_AsyncSQLitePersister_connection_shutdown(): @pytest.fixture() async def initializing_async_persistence(): - sqlite_persister = await AsyncSQLitePersister.from_values( + async with AsyncSQLitePersister.from_values( db_path=":memory:", table_name="test_table" - ) - async_context_manager = AsyncSQLiteContextManager(sqlite_persister) - async with async_context_manager as client: + ) as client: yield client From b2f04f5624da518ef496a82a0a4a9b80857a4896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ahlert?= Date: Sat, 28 Mar 2026 15:35:56 -0300 Subject: [PATCH 5/7] style: fix black formatting and isort import order --- burr/integrations/persisters/b_asyncpg.py | 2 +- tests/core/test_persistence.py | 45 +++++++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/burr/integrations/persisters/b_asyncpg.py b/burr/integrations/persisters/b_asyncpg.py index 48cab6435..c694350d5 100644 --- a/burr/integrations/persisters/b_asyncpg.py +++ b/burr/integrations/persisters/b_asyncpg.py @@ -19,10 +19,10 @@ import logging from typing import Any, ClassVar, Literal, Optional +from burr.common.async_utils import _AsyncPersisterContextManager from burr.common.types import BaseCopyable from burr.core import persistence, state from burr.integrations import base -from burr.common.async_utils import _AsyncPersisterContextManager try: import asyncpg diff --git a/tests/core/test_persistence.py b/tests/core/test_persistence.py index deadb42a8..c4d2818e4 100644 --- a/tests/core/test_persistence.py +++ b/tests/core/test_persistence.py @@ -53,7 +53,9 @@ def test_persistence_initialization_creates_table(initializing_persistence): def test_persistence_saves_and_loads_state(persistence): if hasattr(persistence, "initialize"): persistence.initialize() - persistence.save("partition_key", "app_id", 1, "position", State({"key": "value"}), "status") + persistence.save( + "partition_key", "app_id", 1, "position", State({"key": "value"}), "status" + ) loaded_state = persistence.load("partition_key", "app_id") assert loaded_state["state"] == State({"key": "value"}) @@ -68,8 +70,12 @@ def test_persistence_returns_none_when_no_state(persistence): def test_persistence_lists_app_ids(persistence): if hasattr(persistence, "initialize"): persistence.initialize() - persistence.save("partition_key", "app_id1", 1, "position", State({"key": "value"}), "status") - persistence.save("partition_key", "app_id2", 1, "position", State({"key": "value"}), "status") + persistence.save( + "partition_key", "app_id1", 1, "position", State({"key": "value"}), "status" + ) + persistence.save( + "partition_key", "app_id2", 1, "position", State({"key": "value"}), "status" + ) app_ids = persistence.list_app_ids("partition_key") assert set(app_ids) == set(["app_id1", "app_id2"]) @@ -110,7 +116,12 @@ def test_sqlite_persister_save_without_initialize_raises_runtime_error(): try: with pytest.raises(RuntimeError, match="Uninitialized persister"): persister.save( - "partition_key", "app_id", 1, "position", State({"key": "value"}), "completed" + "partition_key", + "app_id", + 1, + "position", + State({"key": "value"}), + "completed", ) finally: persister.cleanup() @@ -144,7 +155,9 @@ def test_sqlite_persister_list_app_ids_without_initialize_raises_runtime_error() ), ], ) -def test_persister_methods_none_partition_key(persistence, method_name: str, kwargs: dict): +def test_persister_methods_none_partition_key( + persistence, method_name: str, kwargs: dict +): if hasattr(persistence, "initialize"): persistence.initialize() method = getattr(persistence, method_name) @@ -168,8 +181,6 @@ def test_persister_methods_none_partition_key(persistence, method_name: str, kwa """Asyncio integration for sqlite persister + """ - - @pytest.fixture() async def async_persistence(request): yield AsyncInMemoryPersister() @@ -243,7 +254,9 @@ async def test_async_persister_methods_none_partition_key( async def test_AsyncSQLitePersister_from_values(): await asyncio.sleep(0.00001) connection = await aiosqlite.connect(":memory:") - sqlite_persister_init = AsyncSQLitePersister(connection=connection, table_name="test_table") + sqlite_persister_init = AsyncSQLitePersister( + connection=connection, table_name="test_table" + ) sqlite_persister_from_values = await AsyncSQLitePersister.from_values( db_path=":memory:", table_name="test_table" ) @@ -273,7 +286,9 @@ async def initializing_async_persistence(): yield client -async def test_async_persistence_initialization_creates_table(initializing_async_persistence): +async def test_async_persistence_initialization_creates_table( + initializing_async_persistence, +): await asyncio.sleep(0.00001) await initializing_async_persistence.initialize() assert await initializing_async_persistence.list_app_ids("partition_key") == [] @@ -295,7 +310,9 @@ async def test_asyncsqlite_persistence_is_initialized_true_new_connection(tmp_pa db_path = tmp_path / "test.db" p = await AsyncSQLitePersister.from_values(db_path=db_path, table_name="test_table") await p.initialize() - p2 = await AsyncSQLitePersister.from_values(db_path=db_path, table_name="test_table") + p2 = await AsyncSQLitePersister.from_values( + db_path=db_path, table_name="test_table" + ) try: assert await p.is_initialized() assert await p2.is_initialized() @@ -341,7 +358,9 @@ async def dummy_response(state: State) -> Tuple[dict, State]: app = await ( ApplicationBuilder() .with_actions(dummy_input, dummy_response) - .with_transitions(("dummy_input", "dummy_response"), ("dummy_response", "dummy_input")) + .with_transitions( + ("dummy_input", "dummy_response"), ("dummy_response", "dummy_input") + ) .initialize_from( initializer=sqlite_persister, resume_at_next_action=True, @@ -371,7 +390,9 @@ async def dummy_response(state: State) -> Tuple[dict, State]: new_app = await ( ApplicationBuilder() .with_actions(dummy_input, dummy_response) - .with_transitions(("dummy_input", "dummy_response"), ("dummy_response", "dummy_input")) + .with_transitions( + ("dummy_input", "dummy_response"), ("dummy_response", "dummy_input") + ) .initialize_from( initializer=sqlite_persister_2, resume_at_next_action=True, From 006278fa99a291f01a4ab8527b39809ddde2058e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ahlert?= Date: Sat, 28 Mar 2026 15:40:39 -0300 Subject: [PATCH 6/7] style: reformat with black 23.11.0 (project version) --- tests/core/test_persistence.py | 32 ++++++++------------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/tests/core/test_persistence.py b/tests/core/test_persistence.py index c4d2818e4..18c65a0d1 100644 --- a/tests/core/test_persistence.py +++ b/tests/core/test_persistence.py @@ -53,9 +53,7 @@ def test_persistence_initialization_creates_table(initializing_persistence): def test_persistence_saves_and_loads_state(persistence): if hasattr(persistence, "initialize"): persistence.initialize() - persistence.save( - "partition_key", "app_id", 1, "position", State({"key": "value"}), "status" - ) + persistence.save("partition_key", "app_id", 1, "position", State({"key": "value"}), "status") loaded_state = persistence.load("partition_key", "app_id") assert loaded_state["state"] == State({"key": "value"}) @@ -70,12 +68,8 @@ def test_persistence_returns_none_when_no_state(persistence): def test_persistence_lists_app_ids(persistence): if hasattr(persistence, "initialize"): persistence.initialize() - persistence.save( - "partition_key", "app_id1", 1, "position", State({"key": "value"}), "status" - ) - persistence.save( - "partition_key", "app_id2", 1, "position", State({"key": "value"}), "status" - ) + persistence.save("partition_key", "app_id1", 1, "position", State({"key": "value"}), "status") + persistence.save("partition_key", "app_id2", 1, "position", State({"key": "value"}), "status") app_ids = persistence.list_app_ids("partition_key") assert set(app_ids) == set(["app_id1", "app_id2"]) @@ -155,9 +149,7 @@ def test_sqlite_persister_list_app_ids_without_initialize_raises_runtime_error() ), ], ) -def test_persister_methods_none_partition_key( - persistence, method_name: str, kwargs: dict -): +def test_persister_methods_none_partition_key(persistence, method_name: str, kwargs: dict): if hasattr(persistence, "initialize"): persistence.initialize() method = getattr(persistence, method_name) @@ -254,9 +246,7 @@ async def test_async_persister_methods_none_partition_key( async def test_AsyncSQLitePersister_from_values(): await asyncio.sleep(0.00001) connection = await aiosqlite.connect(":memory:") - sqlite_persister_init = AsyncSQLitePersister( - connection=connection, table_name="test_table" - ) + sqlite_persister_init = AsyncSQLitePersister(connection=connection, table_name="test_table") sqlite_persister_from_values = await AsyncSQLitePersister.from_values( db_path=":memory:", table_name="test_table" ) @@ -310,9 +300,7 @@ async def test_asyncsqlite_persistence_is_initialized_true_new_connection(tmp_pa db_path = tmp_path / "test.db" p = await AsyncSQLitePersister.from_values(db_path=db_path, table_name="test_table") await p.initialize() - p2 = await AsyncSQLitePersister.from_values( - db_path=db_path, table_name="test_table" - ) + p2 = await AsyncSQLitePersister.from_values(db_path=db_path, table_name="test_table") try: assert await p.is_initialized() assert await p2.is_initialized() @@ -358,9 +346,7 @@ async def dummy_response(state: State) -> Tuple[dict, State]: app = await ( ApplicationBuilder() .with_actions(dummy_input, dummy_response) - .with_transitions( - ("dummy_input", "dummy_response"), ("dummy_response", "dummy_input") - ) + .with_transitions(("dummy_input", "dummy_response"), ("dummy_response", "dummy_input")) .initialize_from( initializer=sqlite_persister, resume_at_next_action=True, @@ -390,9 +376,7 @@ async def dummy_response(state: State) -> Tuple[dict, State]: new_app = await ( ApplicationBuilder() .with_actions(dummy_input, dummy_response) - .with_transitions( - ("dummy_input", "dummy_response"), ("dummy_response", "dummy_input") - ) + .with_transitions(("dummy_input", "dummy_response"), ("dummy_response", "dummy_input")) .initialize_from( initializer=sqlite_persister_2, resume_at_next_action=True, From ffcbeabaed33438c5d3a58488faf55363d340e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ahlert?= Date: Sat, 28 Mar 2026 16:05:06 -0300 Subject: [PATCH 7/7] ci: trigger workflow rerun