From fd3f504dd7b2521a893ddafcf577c054e48d4426 Mon Sep 17 00:00:00 2001 From: Yehor Smoliakov Date: Sat, 10 Oct 2026 08:10:43 -0700 Subject: [PATCH 1/3] Add PostgreSQL lifecycle tests --- tests/ecosystem/test_postgresql.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/ecosystem/test_postgresql.py diff --git a/tests/ecosystem/test_postgresql.py b/tests/ecosystem/test_postgresql.py new file mode 100644 index 0000000..e69de29 From 40a9ad8f681df698392a68019481d9d8f1e2c255 Mon Sep 17 00:00:00 2001 From: Yehor Smoliakov Date: Sat, 10 Oct 2026 08:12:03 -0700 Subject: [PATCH 2/3] Test real PostgreSQL client lifecycles across event loops --- .github/workflows/async-ecosystem.yml | 49 +++++ docs/async-ecosystem-tests.md | 51 ++++- pyproject.toml | 1 + tests/ecosystem/test_postgresql.py | 257 ++++++++++++++++++++++++++ uv.lock | 85 +++++++++ 5 files changed, 440 insertions(+), 3 deletions(-) diff --git a/.github/workflows/async-ecosystem.yml b/.github/workflows/async-ecosystem.yml index 6eb88be..d7908b7 100644 --- a/.github/workflows/async-ecosystem.yml +++ b/.github/workflows/async-ecosystem.yml @@ -65,3 +65,52 @@ jobs: uv run --no-sync python -c "import aiohttp, aiosqlite, anyio, httpx, sqlalchemy, websockets; from importlib.metadata import version; print({p: version(p) for p in ('aiohttp', 'aiosqlite', 'anyio', 'httpx', 'sqlalchemy', 'websockets')})" - name: Compare real application lifecycles across event loops run: uv run --no-sync python scripts/run_python_tests.py tests/ecosystem -m ecosystem + + postgresql: + name: PostgreSQL / Linux / Python ${{ matrix.python }} + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + python: ["3.10", "3.14"] + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: rsloop + POSTGRES_PASSWORD: rsloop + POSTGRES_DB: rsloop_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U rsloop -d rsloop_test" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + env: + RSLOOP_POSTGRES_DSN: postgresql://rsloop:rsloop@127.0.0.1:5432/rsloop_test + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b + with: + python-version: ${{ matrix.python }} + cache-suffix: postgres-${{ matrix.python }} + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2026-09-25 + - uses: Swatinem/rust-cache@v2 + with: + cache-targets: false + - name: Install locked clients and build rsloop + run: | + uv sync --locked --no-default-groups --group ecosystem --no-install-project + uv run --no-sync maturin develop --locked + - name: Require clients and record environment + run: | + uv run --no-sync python -c "import asyncpg, sqlalchemy, uvloop, platform; from importlib.metadata import version; print(platform.platform(), platform.python_version()); print({p: version(p) for p in ('asyncpg', 'sqlalchemy', 'uvloop')})" + docker exec ${{ job.services.postgres.id }} postgres --version + - name: Compare PostgreSQL lifecycles + run: uv run --no-sync python scripts/run_python_tests.py tests/ecosystem/test_postgresql.py -m ecosystem -ra diff --git a/docs/async-ecosystem-tests.md b/docs/async-ecosystem-tests.md index f1feacc..8cf7c69 100644 --- a/docs/async-ecosystem-tests.md +++ b/docs/async-ecosystem-tests.md @@ -3,8 +3,8 @@ The downstream suite exercises actual clients, protocols, SQLite worker threads, and event loops. It extends the existing framework smoke scripts beyond a single successful request. HTTP clients run against loopback servers; databases use -temporary SQLite files. No public endpoint, database service, or credentials are -required at test time. +temporary SQLite files. The SQLite/HTTP suite requires no external service. PostgreSQL tests opt in +with `RSLOOP_POSTGRES_DSN` pointing to an isolated disposable database. ## Selected projects and contracts @@ -38,7 +38,7 @@ Contract sources: Existing Redis/hiredis and AnyIO CI jobs remain useful complements. Framework smokes for FastAPI, Starlette, Uvicorn and other packages remain under -tests/integration/packages. This suite does not establish PostgreSQL, Redis +tests/integration/packages. This suite does not establish Redis cluster, HTTP/2, proxy, or production load compatibility. ## Run the suite @@ -84,3 +84,48 @@ HTTPX 0.28.1, HTTPcore 1.0.9, AnyIO 4.14.2, websockets 17.0.1, SQLAlchemy 2.0.52 aiosqlite 0.22.1 and uvloop 0.23.0. These cases did not reproduce a new rsloop defect; no production implementation change was needed. See the PR checks for platform-specific execution results. + +## PostgreSQL contracts + +`tests/ecosystem/test_postgresql.py` adds 36 cases: asyncpg and SQLAlchemy +AsyncEngine, each compared on asyncio, rsloop and uvloop. Transactions are +interrupted by cancellation, timeout or application exceptions; rollback is +verified from an independent connection before a new transaction commits. +Concurrent transactions remain invisible until both are released by an event. +A held limit-one pool guarantees exhaustion until acquisition times out; +backend PIDs prove subsequent connection reuse. + +For in-flight interruption, an observer holds a PostgreSQL advisory lock and +polls `pg_stat_activity` until the target backend reports a lock wait. Thus +cancellation occurs during real database I/O, without arbitrary sleeps. The +20-second scenario deadline bounds server-state polling. Pool timeouts expire +while the sole slot is held; no scheduler speed assumption is required. + +TCP recovery terminates only the test backend with `pg_terminate_backend` +while its query is blocked. This tests a real server-side socket close and +replacement connection, not packet loss, a TCP RST proxy, or a full server +restart. The test user must be able to inspect and terminate its own backends. +Unique table names and advisory keys isolate cases; tables, locks, connections, +pools and tasks are cleaned up. Use only a disposable test database. + +```bash +export RSLOOP_POSTGRES_DSN=postgresql://rsloop:rsloop@127.0.0.1:5432/rsloop_test +uv run --no-sync python scripts/run_python_tests.py tests/ecosystem/test_postgresql.py -m ecosystem -ra +``` + +A separate Linux GitHub Actions job provisions PostgreSQL 16 with a health +check and runs Python 3.10/3.14 with all three loops. It requires imports and +records Python, platform, client and server versions. Existing SQLite and +HTTP/WebSocket OS jobs remain in place; without the DSN PostgreSQL cases skip. +PostgreSQL execution on macOS/Windows is not claimed. + +Local validation for this addition: Linux, CPython 3.14.7, release rsloop build; +66 existing ecosystem cases passed, 36 PostgreSQL cases skipped (no runnable +PostgreSQL service; container UID restrictions prevent starting the downloaded +server). Ruff and Pyright passed for the added test module. PostgreSQL results +must be obtained from the dedicated CI jobs; skips do not establish compatibility. + +Primary contracts: [asyncpg pools and transactions](https://magicstack.github.io/asyncpg/current/api/index.html), +[SQLAlchemy asyncio](https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html), +[PostgreSQL activity monitoring](https://www.postgresql.org/docs/16/monitoring-stats.html), +[backend termination](https://www.postgresql.org/docs/16/functions-admin.html). diff --git a/pyproject.toml b/pyproject.toml index 5e2c022..00a21ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,7 @@ integration = [ ] # Reproducible downstream lifecycle tests, separate from framework smoke scripts. ecosystem = [ + "asyncpg>=0.31.0", { include-group = "test" }, "maturin>=1.14.1", "aiohttp>=3.14.3", diff --git a/tests/ecosystem/test_postgresql.py b/tests/ecosystem/test_postgresql.py index e69de29..092e890 100644 --- a/tests/ecosystem/test_postgresql.py +++ b/tests/ecosystem/test_postgresql.py @@ -0,0 +1,257 @@ +"""Real PostgreSQL contracts; opt in with a dedicated RSLOOP_POSTGRES_DSN.""" + +import asyncio +import os +import uuid +from contextlib import asynccontextmanager + +import pytest + +asyncpg = pytest.importorskip("asyncpg") +sa = pytest.importorskip("sqlalchemy") +sa_async = pytest.importorskip("sqlalchemy.ext.asyncio") +pytestmark = pytest.mark.ecosystem + + +@pytest.fixture +def postgres_dsn(): + dsn = os.environ.get("RSLOOP_POSTGRES_DSN") + if not dsn: + pytest.skip("set RSLOOP_POSTGRES_DSN to an isolated test database") + return dsn + + +class Client: + """Same observable contracts through each driver's public API.""" + + def __init__(self, dsn, driver, size=1): + self.dsn, self.driver, self.size = dsn, driver, size + + async def open(self): + if self.driver == "asyncpg": + self.pool = await asyncpg.create_pool( + self.dsn, min_size=self.size, max_size=self.size + ) + else: + self.pool = sa_async.create_async_engine( + self.dsn.replace("postgresql://", "postgresql+asyncpg://", 1), + pool_size=self.size, + max_overflow=0, + pool_timeout=0.05, + ) + return self + + @asynccontextmanager + async def connection(self): + if self.driver == "asyncpg": + async with self.pool.acquire() as connection: + yield connection + else: + async with self.pool.connect() as connection: + yield connection + + async def scalar(self, connection, query): + if self.driver == "asyncpg": + return await connection.fetchval(query) + return await connection.scalar(sa.text(query)) + + def transaction(self, connection): + if self.driver == "asyncpg": + return connection.transaction() + return connection.begin() + + async def close(self): + if self.driver == "asyncpg": + await asyncio.wait_for(self.pool.close(), 5) + else: + await asyncio.wait_for(self.pool.dispose(), 5) + + +async def wait_for_lock(observer, pid): + # Observe the server state, not an assumed duration of query execution. + while not await observer.fetchval( + "SELECT EXISTS (SELECT FROM pg_stat_activity " + "WHERE pid=$1 AND wait_event_type='Lock')", + pid, + ): + await asyncio.sleep(0) + + +@pytest.mark.parametrize("driver", ["asyncpg", "sqlalchemy"]) +@pytest.mark.parametrize("interruption", ["cancel", "timeout", "exception"]) +def test_postgres_interrupted_transaction(loop, postgres_dsn, driver, interruption): + async def main(): + client = await Client(postgres_dsn, driver).open() + observer = await asyncpg.connect(postgres_dsn) + table = "rsloop_" + uuid.uuid4().hex + key = uuid.uuid4().int % (2**63) + task = None + pid_ready = asyncio.Future() + try: + await observer.execute(f"CREATE TABLE {table} (value integer)") + await observer.execute(f"SELECT pg_advisory_lock({key})") + + async def transaction(): + async with ( + client.connection() as connection, + client.transaction(connection), + ): + await client.scalar( + connection, + f"INSERT INTO {table} VALUES (1) RETURNING value", + ) + pid_ready.set_result( + await client.scalar(connection, "SELECT pg_backend_pid()") + ) + if interruption == "exception": + raise ValueError("application failure") + await client.scalar( + connection, f"SELECT pg_advisory_xact_lock({key})" + ) + + task = asyncio.create_task(transaction()) + pid = await pid_ready + if interruption == "exception": + with pytest.raises(ValueError, match="application failure"): + await task + else: + await wait_for_lock(observer, pid) + if interruption == "cancel": + task.cancel() + with pytest.raises(asyncio.CancelledError): + await task + else: + with pytest.raises(asyncio.TimeoutError): + await asyncio.wait_for(task, 0) + assert await observer.fetchval(f"SELECT count(*) FROM {table}") == 0 + # Acquiring the only slot proves pool return. Query proves protocol recovery. + async with ( + client.connection() as connection, + client.transaction(connection), + ): + assert ( + await client.scalar( + connection, + f"INSERT INTO {table} VALUES (2) RETURNING value", + ) + == 2 + ) + assert await observer.fetchval(f"SELECT sum(value) FROM {table}") == 2 + finally: + if task is not None: + task.cancel() + await asyncio.gather(task, return_exceptions=True) + await observer.execute("SELECT pg_advisory_unlock_all()") + await client.close() + await observer.execute(f"DROP TABLE IF EXISTS {table}") + await observer.close() + + loop.run_until_complete(asyncio.wait_for(main(), 20)) + + +@pytest.mark.parametrize("driver", ["asyncpg", "sqlalchemy"]) +def test_postgres_pool_exhaustion_and_reuse(loop, postgres_dsn, driver): + async def main(): + client = await Client(postgres_dsn, driver).open() + try: + async with client.connection() as held: + pid = await client.scalar(held, "SELECT pg_backend_pid()") + if driver == "asyncpg": + with pytest.raises(asyncio.TimeoutError): + await client.pool.acquire(timeout=0.05) + else: + with pytest.raises(sa.exc.TimeoutError): + async with client.connection(): + pytest.fail("exhausted pool returned a connection") + assert await client.scalar(held, "SELECT 42") == 42 + async with client.connection() as reused: + assert await client.scalar(reused, "SELECT pg_backend_pid()") == pid + finally: + await client.close() + + loop.run_until_complete(asyncio.wait_for(main(), 20)) + + +@pytest.mark.parametrize("driver", ["asyncpg", "sqlalchemy"]) +def test_postgres_concurrent_transactions(loop, postgres_dsn, driver): + async def main(): + client = await Client(postgres_dsn, driver, size=2).open() + observer = await asyncpg.connect(postgres_dsn) + table = "rsloop_" + uuid.uuid4().hex + ready = [asyncio.Event(), asyncio.Event()] + release = asyncio.Event() + tasks = [] + try: + await observer.execute(f"CREATE TABLE {table} (value integer)") + + async def writer(index): + async with ( + client.connection() as connection, + client.transaction(connection), + ): + await client.scalar( + connection, + f"INSERT INTO {table} VALUES ({index}) RETURNING value", + ) + ready[index].set() + await release.wait() + + tasks = [asyncio.create_task(writer(i)) for i in range(2)] + await asyncio.gather(*(event.wait() for event in ready)) + assert await observer.fetchval(f"SELECT count(*) FROM {table}") == 0 + release.set() + await asyncio.gather(*tasks) + assert await observer.fetchval( + f"SELECT array_agg(value ORDER BY value) FROM {table}" + ) == [0, 1] + finally: + release.set() + for task in tasks: + task.cancel() + await asyncio.gather(*tasks, return_exceptions=True) + await client.close() + await observer.execute(f"DROP TABLE IF EXISTS {table}") + await observer.close() + + loop.run_until_complete(asyncio.wait_for(main(), 20)) + + +@pytest.mark.parametrize("driver", ["asyncpg", "sqlalchemy"]) +def test_postgres_tcp_disconnect_and_pool_recovery(loop, postgres_dsn, driver): + async def main(): + client = await Client(postgres_dsn, driver).open() + observer = await asyncpg.connect(postgres_dsn) + key = uuid.uuid4().int % (2**63) + task = None + try: + await observer.execute(f"SELECT pg_advisory_lock({key})") + async with client.connection() as connection: + pid = await client.scalar(connection, "SELECT pg_backend_pid()") + task = asyncio.create_task( + client.scalar(connection, f"SELECT pg_advisory_xact_lock({key})") + ) + await wait_for_lock(observer, pid) + # Kill only our backend: the real server closes the TCP socket + # while the client is receiving a response. No private transport API. + assert await observer.fetchval("SELECT pg_terminate_backend($1)", pid) + expected = ( + asyncpg.PostgresError if driver == "asyncpg" else sa.exc.DBAPIError + ) + with pytest.raises(expected): + await task + if driver == "sqlalchemy": + await connection.rollback() + async with client.connection() as replacement: + assert ( + await client.scalar(replacement, "SELECT pg_backend_pid()") != pid + ) + assert await client.scalar(replacement, "SELECT 42") == 42 + finally: + if task is not None: + task.cancel() + await asyncio.gather(task, return_exceptions=True) + await observer.execute("SELECT pg_advisory_unlock_all()") + await client.close() + await observer.close() + + loop.run_until_complete(asyncio.wait_for(main(), 20)) diff --git a/uv.lock b/uv.lock index b97acb7..67b3c57 100644 --- a/uv.lock +++ b/uv.lock @@ -262,6 +262,89 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, ] +[[package]] +name = "asyncpg" +version = "0.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/4e/59dc964f962f09e3ed472e5d2d3ba670a41a2be25080dc62ab3db507ff5e/asyncpg-0.32.0.tar.gz", hash = "sha256:45e64e56714d888330b884aad1dfb363d0bf43fb343e3d1a8968525f3bade478", size = 1075156, upload-time = "2026-10-06T20:32:40.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/3a/6fa8478896f3f54d1aa7411ae6ba3105c7d3b172ab87d78839bdecc3f2e3/asyncpg-0.32.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd5adfb01cea16908d617af55b00a84c9e581964b77d4301c29fd735bb7850c3", size = 689260, upload-time = "2026-10-06T20:30:25.238Z" }, + { url = "https://files.pythonhosted.org/packages/c3/77/d332193fe023b450b2de89e9c5d35350d95144e3a42ade2ec5131a026359/asyncpg-0.32.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:23638de661ac9a7975278a4fafb1f4c8613e7aae04562675f604dd20ec10e8d8", size = 693995, upload-time = "2026-10-06T20:30:27.111Z" }, + { url = "https://files.pythonhosted.org/packages/31/ee/81338441f0d3749725b0543f199aeab20853fdfaebb749c217d6ed50f236/asyncpg-0.32.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0549af18b697221d1992b7def18aa61652a85ecbe6e19ba2a75277560efe6016", size = 3074342, upload-time = "2026-10-06T20:30:28.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/bd/2460a47ad82956cf6e89e2577711b05b584dc98cc5e379bfc919a25d74fb/asyncpg-0.32.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5faf73279afe1b2137ce503491500b664621762485233ebacb6fb91f7f092baa", size = 3133917, upload-time = "2026-10-06T20:30:30.454Z" }, + { url = "https://files.pythonhosted.org/packages/44/46/7e1e64ba336611e3a0f89c6502578aee34c99c8ee74711b80b0392f9a9a9/asyncpg-0.32.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6e83cdc21ed0a027d3065b19f9fffaf864b91bc007f30bf6e385f2fe84061a79", size = 3007136, upload-time = "2026-10-06T20:30:31.994Z" }, + { url = "https://files.pythonhosted.org/packages/84/97/38c138d7d189eac44f9b1c3e2374a3ce4e42f81e238d99cd1839edf1e8bf/asyncpg-0.32.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4412cb864442355a6d944adb34c098924d1e14230b6ddbbe9665cffdf2708e8a", size = 3126880, upload-time = "2026-10-06T20:30:33.605Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cf/ee2dfa7b288ef1f5022fb4b2549f10903af78554e2b6ad1fc3e81591647f/asyncpg-0.32.0-cp310-cp310-win32.whl", hash = "sha256:0e25fe441cca81c277554e0f8f7f9c6987d2aaf47cedfc7783d9717ce2853371", size = 542014, upload-time = "2026-10-06T20:30:35.239Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3a/ca9a61df849a7689be13ca3bd956f8671eb895f09a44f5d5b5f9b9c3e201/asyncpg-0.32.0-cp310-cp310-win_amd64.whl", hash = "sha256:0b7706ff96cfe26fc48aa191f72f8076ddc2c52a5bc75fa9d3f34066e734e2d6", size = 607734, upload-time = "2026-10-06T20:30:36.487Z" }, + { url = "https://files.pythonhosted.org/packages/88/a4/281f067513cc765a16ae73e3deffca9f9a959b23d0b1acabeb9ca2d54ddc/asyncpg-0.32.0-cp310-cp310-win_arm64.whl", hash = "sha256:87780aa30b40e2de89717b51cdae4bb80b21b8842c02fb560e1e907e5a856a3d", size = 573816, upload-time = "2026-10-06T20:30:37.816Z" }, + { url = "https://files.pythonhosted.org/packages/a3/27/1a7970f1ece6c205b03c79f45b89420dee9655ffb66bd2c11be8f40c248a/asyncpg-0.32.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5789340b9bcdab94a19eb8ff119322a09991e3626d131b55828535b373e285d4", size = 686071, upload-time = "2026-10-06T20:30:39.115Z" }, + { url = "https://files.pythonhosted.org/packages/2b/47/085934d0290806a92789eee860109c44bea71ff8bc7850a9d3a30da7a819/asyncpg-0.32.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:057ed2455e4e14ad9949f1ac1829112c7d0454c9810b124f36de1486febe6824", size = 692193, upload-time = "2026-10-06T20:30:40.563Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2c/d92524b9e860aecd119c0ebe43f3b9eca26dc2b75c4dfe1be3e999e3f6b1/asyncpg-0.32.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c938c4da9166ac1ef330475e314e2b94c68bde2795be0f4e8a1e00ccd806cadd", size = 3196713, upload-time = "2026-10-06T20:30:42.123Z" }, + { url = "https://files.pythonhosted.org/packages/85/b5/3ac7cb86aa287e5bbceaeb783ee6e4f51cd2a001f1747ef4f1236a20bde6/asyncpg-0.32.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:968c570c5913b7ce0995953d7239bd2367142d1af4359f87699f7a6ca75c4382", size = 3260618, upload-time = "2026-10-06T20:30:43.552Z" }, + { url = "https://files.pythonhosted.org/packages/e3/08/618ac36b2970b437d45523f50b5580dba0c34756bbf2153306f82a2697e5/asyncpg-0.32.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96c8226d2026e025852facb5a05035ea5e11b14bebb6b42e4e43948ef8f0d075", size = 3132973, upload-time = "2026-10-06T20:30:45.147Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/54db41b3d5fe26b0401a49327ffce439195c5f6073d8afbbdc9758cb35c3/asyncpg-0.32.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d3f745f4947df9004e2637753ff81d52f305f790f49d67f72e1677db12b07a7b", size = 3251612, upload-time = "2026-10-06T20:30:46.923Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e0/ed1e7536ce949896de29ee955b473659b3daa7887e7081030dba2b15ea5d/asyncpg-0.32.0-cp311-cp311-win32.whl", hash = "sha256:469e6520a839957304582eb8a708d874985914500b64517155f80e6fec00e742", size = 538739, upload-time = "2026-10-06T20:30:48.355Z" }, + { url = "https://files.pythonhosted.org/packages/df/eb/52c4bddad17ff1bee485ae83e08c752a998ef04ac5df76f03fef6430d0ed/asyncpg-0.32.0-cp311-cp311-win_amd64.whl", hash = "sha256:6a1e671e67f4b0bef3c03f37a896d61706f769a83922c119070f1f04e415dc17", size = 610534, upload-time = "2026-10-06T20:30:50.003Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/9af12f2b3300c425a151ef8f85f47c0db76135827c549031858954805ff7/asyncpg-0.32.0-cp311-cp311-win_arm64.whl", hash = "sha256:901bc87b94539f32853bd73a9b02fa78f7feed4cf628824caad3093ec6662f58", size = 574363, upload-time = "2026-10-06T20:30:51.489Z" }, + { url = "https://files.pythonhosted.org/packages/73/06/d5f956db9c936c90cd3289cf948a86c3efc9849e26354356c23da29f6a2d/asyncpg-0.32.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cb31f7a8472ddc6b6f5c9da1290e901d5c77c8441c7213bd13b13ef6fe6359c", size = 681566, upload-time = "2026-10-06T20:30:52.779Z" }, + { url = "https://files.pythonhosted.org/packages/09/93/ea55f3b26fd40ec90e5b6d6c53b9ff52633cf6b87a468d9c033a727832f4/asyncpg-0.32.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:643d8d6e955a355045dddfe827d74f4f0d1dc4a18e06963a08260af838fbf093", size = 704359, upload-time = "2026-10-06T20:30:54.608Z" }, + { url = "https://files.pythonhosted.org/packages/46/2c/a3704e8675d37b168f3584661fc9f64f3021659c9b94e51cf9ab957b2bc5/asyncpg-0.32.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:14ff79ca2574182ce258159c48978a086f9026fc121d935017b5d10c64fa3c72", size = 3707008, upload-time = "2026-10-06T20:30:56.326Z" }, + { url = "https://files.pythonhosted.org/packages/30/30/4fd8d1155b3d7a32a2c241dcb9c5d9e9bd74a59ae71ed25ef8ddb8e038e1/asyncpg-0.32.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:54851411bee2aa51a30d0911524201fbb05f82cc0f7c248b140203db637c723d", size = 3810163, upload-time = "2026-10-06T20:30:58.114Z" }, + { url = "https://files.pythonhosted.org/packages/c1/25/5b0992d45661e1488aba775cf17a2e6c82c7d1d7e10acc71efd394760a00/asyncpg-0.32.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8592f0ed9c315b2117dbdc707cf3292f09a89d5b07661016a84dd881326965cf", size = 3600446, upload-time = "2026-10-06T20:30:59.946Z" }, + { url = "https://files.pythonhosted.org/packages/ea/88/1c82c6feacec813423401b5aef1a43baea951694157f4d405b2d14e80e6d/asyncpg-0.32.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4dbe0982cb3ded878de0867dfaeae3116faf471d484ea28b3e3da942f01fb778", size = 3764563, upload-time = "2026-10-06T20:31:01.462Z" }, + { url = "https://files.pythonhosted.org/packages/84/f5/5a3796088f0c3f7d22aaf7c48536f40b27e44b7c9603d4d7abfeca2ed97e/asyncpg-0.32.0-cp312-cp312-win32.whl", hash = "sha256:fbe1f8c788fb5df18ea8a5432dfa2473fd8f7f088025fb83d089a7c7b37e37b0", size = 551810, upload-time = "2026-10-06T20:31:03.248Z" }, + { url = "https://files.pythonhosted.org/packages/af/42/f4d333a3f67b0e7cf58ea855f9d5d9104ce38c21f2a2f22bf7dce524428c/asyncpg-0.32.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd7157a86817730c3239bc687abf8186a471525d695e225c187b9a523a808a98", size = 626763, upload-time = "2026-10-06T20:31:04.927Z" }, + { url = "https://files.pythonhosted.org/packages/a8/82/9d82e16e1d0b4e2a639a2db649d4b444b8a479cd52553a9c36ba0d6320a8/asyncpg-0.32.0-cp312-cp312-win_arm64.whl", hash = "sha256:9509e21fc526f1fc27cf80ad9f9b8dde3f3e21935d46be66d649635321d3407c", size = 577288, upload-time = "2026-10-06T20:31:06.776Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ee/b6b5870b51e004880d9a216313ea7d4f180961c5869f32e58e8cb9b71e96/asyncpg-0.32.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c032869fd9c3c9fd1a86ad67e53f63906159068087c2674dd1e19be3cffff571", size = 683362, upload-time = "2026-10-06T20:31:08.078Z" }, + { url = "https://files.pythonhosted.org/packages/d8/8b/1f450742bc6eab0c015cae26aef94fac2ff29433e3f18a019126c3912c49/asyncpg-0.32.0-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:0c764dce865b41878396e736d4d2c6c6ce3a8e1b61d1f6bb292e30d265ae7ca6", size = 706652, upload-time = "2026-10-06T20:31:09.524Z" }, + { url = "https://files.pythonhosted.org/packages/05/dc/13f3c0ef7e867bafdccd470e5cfae1f2fd9a7085c771546bd4b94018e043/asyncpg-0.32.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:925ce1cc54419d468bfb77632d91e5e2be5be0fdf9d43680c68fe7cedf87051a", size = 3698244, upload-time = "2026-10-06T20:31:10.894Z" }, + { url = "https://files.pythonhosted.org/packages/1f/64/b00ef3fc0d861c28a1937f08d2c7f6e6119c152b414d50fa800c3aee83b5/asyncpg-0.32.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4cec40b66a36b14921c155db78631cd96ed00e225fdf38dd5532e9aef350a498", size = 3801314, upload-time = "2026-10-06T20:31:12.964Z" }, + { url = "https://files.pythonhosted.org/packages/de/1b/215067d97a13206ce1565da920ddbefe5a1e5f89903e6de862fdd0a034a1/asyncpg-0.32.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fba43a9a230ce4d2b4593b761b8e03630c613c282b24566e27c7f53695273b1", size = 3598650, upload-time = "2026-10-06T20:31:14.797Z" }, + { url = "https://files.pythonhosted.org/packages/37/45/2bfcb5c9b04df3f17fd367647c9f3ee9fe64ea0612b509a6b1832afcedae/asyncpg-0.32.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a8f7fa8304f757e23cccb8ffef6a6fce0b6320ffc565a884ee3cd0dfad1ac5", size = 3762739, upload-time = "2026-10-06T20:31:17.186Z" }, + { url = "https://files.pythonhosted.org/packages/08/45/e6b37756e6c8979fe070e9821654244f38319493f5b0589e549d9a40c001/asyncpg-0.32.0-cp313-cp313-win32.whl", hash = "sha256:d809399022e244eb86bb532a4ae9a45746e0f6dc5154fd6aa2f6ad63fa3f5373", size = 551065, upload-time = "2026-10-06T20:31:18.812Z" }, + { url = "https://files.pythonhosted.org/packages/ee/46/0a4e92f4310da644b28595b22ef2fff1ffd3dab84953dc8b4c5eef72b764/asyncpg-0.32.0-cp313-cp313-win_amd64.whl", hash = "sha256:38640b106705fef8b0f46cdb5fd9dcf6a638eed5cadb0f441714a21405ca8a0a", size = 625571, upload-time = "2026-10-06T20:31:20.571Z" }, + { url = "https://files.pythonhosted.org/packages/35/f4/48ed4b580b99b1fabc480c707229bb8f1e4ba0f5b24a50822b339efe1e48/asyncpg-0.32.0-cp313-cp313-win_arm64.whl", hash = "sha256:d78145adedfe51dc2fda623e6602cf816dabc2eafcff693bd50484321a1c9034", size = 576342, upload-time = "2026-10-06T20:31:22.29Z" }, + { url = "https://files.pythonhosted.org/packages/25/25/a30ca6417f9142c6a63a7caf5f33717902b2d0ca8a8ff8fc72c6cc2fa77d/asyncpg-0.32.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ac18d9ee7a8ca70aed276f79b249d9f37e4d55e3525db1002b5f0b62ddec4f5", size = 691699, upload-time = "2026-10-06T20:31:24.168Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b5/59f10f2381a073c199cd868fce0d8f7aa448b08412de4dc4dbe4118bcee9/asyncpg-0.32.0-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:e1120ef2ae3a5e514c9ea9fce83519ba692710ea5f38434eadbbf12789073dfe", size = 715194, upload-time = "2026-10-06T20:31:25.969Z" }, + { url = "https://files.pythonhosted.org/packages/54/59/79a5aebd58250bedefa6dcd43b22b037d9cf0054ceb4c718c53ebf04e63f/asyncpg-0.32.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fa68acb42f22436597016e5d7feef7b0b5c49b4c56aece3fdb3ba0da2326cb2", size = 3729978, upload-time = "2026-10-06T20:31:27.541Z" }, + { url = "https://files.pythonhosted.org/packages/68/db/fc91b503b3ec66cf242d83c799388285ea5f0ee238435d53dd9c1a8648a9/asyncpg-0.32.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63417b8f7369c54f6754c1fbd5a2968fbe632ff55bfbedd56a0177b6a96bd251", size = 3794539, upload-time = "2026-10-06T20:31:29.617Z" }, + { url = "https://files.pythonhosted.org/packages/40/bd/7359320499fdb2733206191b8fd15b7ec602656cbc1444bff7a8c66a365c/asyncpg-0.32.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c6366841a792d0a4d16991de240a8053b7c4772a18a5f27fa6fad09c0e359fb", size = 3632884, upload-time = "2026-10-06T20:31:31.298Z" }, + { url = "https://files.pythonhosted.org/packages/18/75/dd3c3dd99f1db55b9736d23a44da29501f07f852bf4df91507f37b156fb1/asyncpg-0.32.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3ef1dfd11919280e011ffd1c873323c5088a94fd2c3f77946a5250cf306e2eb", size = 3764931, upload-time = "2026-10-06T20:31:32.916Z" }, + { url = "https://files.pythonhosted.org/packages/38/4f/161b275759725a774d170a383c1208996865ebad50d6891e60d35461a3e6/asyncpg-0.32.0-cp314-cp314-win32.whl", hash = "sha256:77cf9d7023f063ae6f9e443077b55af0dc1807dd9afff1ae656b93ee0cddedc9", size = 557690, upload-time = "2026-10-06T20:31:34.856Z" }, + { url = "https://files.pythonhosted.org/packages/b5/03/880d0db1faedf8b740a57a7ba50e115651a0f05c5905140195813879b086/asyncpg-0.32.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f87452025b47ce80dcc3a0be2b5d1f8aab5deec2516d266f1643d4e53cc40d5", size = 634859, upload-time = "2026-10-06T20:31:36.512Z" }, + { url = "https://files.pythonhosted.org/packages/79/bb/2e86b462a2a2a795eaa7838266db019876b8e7a12c465b903517a4e87fd0/asyncpg-0.32.0-cp314-cp314-win_arm64.whl", hash = "sha256:d0e4508a3d62b0f42d7a99c030c364050b11e75f61c9dd4861e5fdda7cb60636", size = 594013, upload-time = "2026-10-06T20:31:37.91Z" }, + { url = "https://files.pythonhosted.org/packages/20/1d/5369c4438496e654121cbda75be2e8043d1fcae3552b856d44011a19b723/asyncpg-0.32.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:afec11e0b9c001e69966becacd2f948cc8949b4916ec4c0f4dc9b52e47de4528", size = 743832, upload-time = "2026-10-06T20:31:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/60/b0/4b92582c2339a164275a6418ccaeeb0453b72f2e0d7003702379cb50e852/asyncpg-0.32.0-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:418d266a553e932bf961bb43bfd610ee6c5425fb1b9a599a5828fd12bae8f5c4", size = 769568, upload-time = "2026-10-06T20:31:40.691Z" }, + { url = "https://files.pythonhosted.org/packages/3d/88/919d9ff7ca3c3b96aa404b88b6a53e142b4422623c5ee5a69c4b733240ce/asyncpg-0.32.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b1666e1b747ebbc75c87cb31972704ae8a3ca15b950f94456e97d26781c67d10", size = 3948962, upload-time = "2026-10-06T20:31:42.456Z" }, + { url = "https://files.pythonhosted.org/packages/27/8b/e9f412ae9a3e3f0eb23415249e8d5933e7aeb01068b4083fc86714043d1f/asyncpg-0.32.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83510bb25d38f0415e155aa3a7af78621369891f5ecd8730d012d9cb26143ffc", size = 3874815, upload-time = "2026-10-06T20:31:44.094Z" }, + { url = "https://files.pythonhosted.org/packages/08/71/24364e9ff7bb9860548452513f295306b12f5b24e8fb0b78f1605c443946/asyncpg-0.32.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:87957755d11639cf248c6aaa094eee9d150f07065866d1710c9427e02dfc0790", size = 3762465, upload-time = "2026-10-06T20:31:45.908Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e1/33cb7e805ec6806b196473e2c7a2ba9d5af3ad2928930aa06359c8eeef87/asyncpg-0.32.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:764227423bf30a3001d3da6df90e82d30a2a097d762e4ee5fa074236eda262f4", size = 3797285, upload-time = "2026-10-06T20:31:47.53Z" }, + { url = "https://files.pythonhosted.org/packages/be/e7/85eb86d6040725f5c191fd6af9f10769c60ed971634b47f4b4bcab293d44/asyncpg-0.32.0-cp314-cp314t-win32.whl", hash = "sha256:f2342b1f3e87b2096320a77edcbb830fbd23b1d4d4842c57567764430b95e4fc", size = 594006, upload-time = "2026-10-06T20:31:49.197Z" }, + { url = "https://files.pythonhosted.org/packages/f9/aa/ea75defe55718457bcf41cde42248db5bbee65fce8c6f0a0e43d9eca1723/asyncpg-0.32.0-cp314-cp314t-win_amd64.whl", hash = "sha256:5c3a48908cb0a02393e5bdab7fa92aefd700f2a93212bf91f04aa9657b4f554d", size = 674647, upload-time = "2026-10-06T20:31:50.547Z" }, + { url = "https://files.pythonhosted.org/packages/0d/0b/078d362872c6c72dd5d11c214dde8dac65b1c87ece96fd2fc2f786a8f66c/asyncpg-0.32.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f8eadd207c26850a2e15f3c2a1096b5d051ea6758a26f2f3e65ce16f84297ed8", size = 624589, upload-time = "2026-10-06T20:31:52.291Z" }, + { url = "https://files.pythonhosted.org/packages/5c/83/e0145d19197b965438693179c88dd99cfc69bc1bf954815f44762ab88843/asyncpg-0.32.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:58975b1a51a100c4716ebf22f84c249d27140f7b9385b64ad9b676836f1db9ab", size = 689708, upload-time = "2026-10-06T20:31:55.809Z" }, + { url = "https://files.pythonhosted.org/packages/2f/13/f394919a59f104288b1b17fb6c7a3ac4738b8c555690a63caf603f91ca83/asyncpg-0.32.0-cp315-cp315-macosx_11_0_x86_64.whl", hash = "sha256:6b95fc2ebdb4af072bfa8b64c6d0397b49242d17bef1c0337857904f9267dab2", size = 714408, upload-time = "2026-10-06T20:31:57.504Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3d/1123cf41bff78fdfd80e6fd143cc86bf1ef2875af8f5d8742c03f471e913/asyncpg-0.32.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a759f98c5652443db501b20041aeee548e9a04fe7ae939067321acd207218447", size = 3733440, upload-time = "2026-10-06T20:31:59.308Z" }, + { url = "https://files.pythonhosted.org/packages/de/24/ff4b045e85d7bdf6f61f67c285800abd6e82f26319671d7f0dfadadc1aa0/asyncpg-0.32.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ceea1064500d0d7a46c092cdbe9752064c23b720ab0e0bff83d1030fffe7a50a", size = 3824312, upload-time = "2026-10-06T20:32:01.021Z" }, + { url = "https://files.pythonhosted.org/packages/12/63/1ec7eb6e20f7e8ae120a41aad9669044cce964f39773baf644897a046aee/asyncpg-0.32.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:543f02790d086244c7cdc849e4b671b6c2048be0242b78d943494da6e80c0001", size = 3637212, upload-time = "2026-10-06T20:32:02.699Z" }, + { url = "https://files.pythonhosted.org/packages/79/68/528e362eb5adbc1a7defe4c5f157756a031346d3efa9920467b245e4ce41/asyncpg-0.32.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f24d20a68f0e37ca6fc490388e7eeb48abab3da0dbf06248135ed6179f5f521d", size = 3791355, upload-time = "2026-10-06T20:32:04.415Z" }, + { url = "https://files.pythonhosted.org/packages/38/e3/22f443f456bf93d1806f43a820da8ee463dfe9b93a9d77a3f00fedcdaad6/asyncpg-0.32.0-cp315-cp315-win32.whl", hash = "sha256:110f72d33c8b944ab421ca383db0b8849cfeb861547fee6cbb61f65a6bcd0985", size = 557457, upload-time = "2026-10-06T20:32:06.52Z" }, + { url = "https://files.pythonhosted.org/packages/54/d5/ccb76555a333f543c4d6ad6422b616efc0811dbbde5054fda071e249c7bf/asyncpg-0.32.0-cp315-cp315-win_amd64.whl", hash = "sha256:6d1d1cd1348ebb9b204b5f56f977c5d4380674c25cc094064bf32bd9c3b7273d", size = 635573, upload-time = "2026-10-06T20:32:08.197Z" }, + { url = "https://files.pythonhosted.org/packages/38/70/dff17e837ba0eb4347bb33da33f54df87230d3d176793d4bb2ad7786b1b8/asyncpg-0.32.0-cp315-cp315-win_arm64.whl", hash = "sha256:cd5d16b3a5db37c1e6e445e362952b4af569f85f94e162f947bfa8ea25a45fa5", size = 594218, upload-time = "2026-10-06T20:32:09.717Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b8/c5506dbde0cfb213963210fd0c80e60036ddaaa883ac0d3c55d05a10ebe8/asyncpg-0.32.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:4ea1a72a00fe705b68a9727c3d538c4c56690af9bb1cbbf3c089f5d3ddcccea0", size = 741693, upload-time = "2026-10-06T20:32:11.168Z" }, + { url = "https://files.pythonhosted.org/packages/23/98/9f998c651aa5d66b59ab6c13da71a15d74ccb1ddc4d65290ea5e2e5aedc1/asyncpg-0.32.0-cp315-cp315t-macosx_11_0_x86_64.whl", hash = "sha256:ed3ae4c3659aea1fb0e3a6c1061fc4c64d9b7a2a8f4a27443dc43d74fa84cf03", size = 768101, upload-time = "2026-10-06T20:32:12.948Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ce/d8c63a71e908f5d80de1a3a057c8407aaea07cf19980d4b24ab624943c99/asyncpg-0.32.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:db69b9cf879bddeea41210c80b8c8877bfe2709e2bee9d18d5a5c00e7eb75972", size = 3940715, upload-time = "2026-10-06T20:32:14.544Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a5/5d2b17682e297e39206eda1dfe0120fc239e84d3440b39ff7c9cc7ec83db/asyncpg-0.32.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bee7bb5394bf55fc3bf4144625c33f298949961acdb1e0d67e60f958ac9a2e6", size = 3907504, upload-time = "2026-10-06T20:32:16.212Z" }, + { url = "https://files.pythonhosted.org/packages/b1/80/38ec7277f31f26267a0a0547d0997d936850d05007d1e0e1041bf8070e1d/asyncpg-0.32.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:d74eabd68e68861333e3fcb92b520a2a851f6485abf4b723887590399d4980c1", size = 3750324, upload-time = "2026-10-06T20:32:18.061Z" }, + { url = "https://files.pythonhosted.org/packages/dc/74/089e80eda7d543a49875687a84121e2ad61a7c69698963623ee77372c4e9/asyncpg-0.32.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6af2af292a93d5ef800007c8f8f66b85af2a49b49e4b56a10685a0dc24a6af83", size = 3826457, upload-time = "2026-10-06T20:32:19.757Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3c/38104e60cda6131977f95b634d45536ddc1cde53ef8bc765f9056e3e17ee/asyncpg-0.32.0-cp315-cp315t-win32.whl", hash = "sha256:d148cb6a9081ed999ca3cd0d95fb9eaf79bf17d885bba93c83de52273d2fe0af", size = 592437, upload-time = "2026-10-06T20:32:21.668Z" }, + { url = "https://files.pythonhosted.org/packages/95/09/85cba249db0910708826ea428b32a4a05630df993621c369bdb8d42c73c5/asyncpg-0.32.0-cp315-cp315t-win_amd64.whl", hash = "sha256:e101801b4124e905da0732cf2b0d838f682a9ea5273d7cced3d54bdbe744e6f7", size = 672417, upload-time = "2026-10-06T20:32:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/38/11/ec5f7f306dd361aa9558f002cbb6acfa1e9ba32fa59b8f53135fbdfa14f1/asyncpg-0.32.0-cp315-cp315t-win_arm64.whl", hash = "sha256:3bbf08c08e31f43be858255614518e78cdfb343571e557e818e9fe736334f4c8", size = 622767, upload-time = "2026-10-06T20:32:24.64Z" }, +] + [[package]] name = "attrs" version = "26.1.0" @@ -2902,6 +2985,7 @@ dev = [ ecosystem = [ { name = "aiohttp" }, { name = "aiosqlite" }, + { name = "asyncpg" }, { name = "httpx" }, { name = "maturin" }, { name = "pytest" }, @@ -2965,6 +3049,7 @@ dev = [ ecosystem = [ { name = "aiohttp", specifier = ">=3.14.3" }, { name = "aiosqlite", specifier = ">=0.20.0" }, + { name = "asyncpg", specifier = ">=0.31.0" }, { name = "httpx", specifier = ">=0.28.1" }, { name = "maturin", specifier = ">=1.14.1" }, { name = "pytest", specifier = ">=8.4" }, From 40183ad931721ae74ee8d64eaaed8108288ef23f Mon Sep 17 00:00:00 2001 From: Yehor Smoliakov Date: Sat, 10 Oct 2026 08:14:47 -0700 Subject: [PATCH 3/3] Document actual PostgreSQL CI environments and passing results --- docs/async-ecosystem-tests.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/async-ecosystem-tests.md b/docs/async-ecosystem-tests.md index 8cf7c69..baf81d7 100644 --- a/docs/async-ecosystem-tests.md +++ b/docs/async-ecosystem-tests.md @@ -122,8 +122,20 @@ PostgreSQL execution on macOS/Windows is not claimed. Local validation for this addition: Linux, CPython 3.14.7, release rsloop build; 66 existing ecosystem cases passed, 36 PostgreSQL cases skipped (no runnable PostgreSQL service; container UID restrictions prevent starting the downloaded -server). Ruff and Pyright passed for the added test module. PostgreSQL results -must be obtained from the dedicated CI jobs; skips do not establish compatibility. +server). Ruff and Pyright passed for the added test module. Skips do not establish PostgreSQL compatibility. + +[GitHub Actions run 38062646332](https://github.com/RustedBytes/rsloop/actions/runs/38062646332) +executed the added PostgreSQL suite at code commit `40a9ad8`: + +| Actual environment | Result | +| --- | --- | +| Linux x86_64, CPython 3.10.22, PostgreSQL 16.15 | 36 passed (asyncio, rsloop, uvloop) | +| Linux x86_64, CPython 3.14.8, PostgreSQL 16.15 | 36 passed (asyncio, rsloop, uvloop) | + +Both jobs used asyncpg 0.32.0, SQLAlchemy 2.0.52 and uvloop 0.23.0. +These scenarios reproduced no rsloop implementation defect; no production-code +change was needed. PostgreSQL ran in CI, not locally. This is lifecycle +compatibility evidence under the listed conditions, not a production-load test. Primary contracts: [asyncpg pools and transactions](https://magicstack.github.io/asyncpg/current/api/index.html), [SQLAlchemy asyncio](https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html),