From e2314474bb4da2b698f70091b7358dd8a795c830 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 11:02:48 +0200 Subject: [PATCH 01/11] tests: Move asyncpg under toxgen --- .github/workflows/test-integrations-dbs.yml | 2 +- scripts/populate_tox/config.py | 6 ++++++ scripts/populate_tox/populate_tox.py | 1 - scripts/populate_tox/tox.jinja | 9 --------- tox.ini | 22 +++++++++++---------- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/.github/workflows/test-integrations-dbs.yml b/.github/workflows/test-integrations-dbs.yml index 5fc0be029b..2d6af43bc3 100644 --- a/.github/workflows/test-integrations-dbs.yml +++ b/.github/workflows/test-integrations-dbs.yml @@ -29,7 +29,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7","3.8","3.11","3.12","3.13"] + python-version: ["3.7","3.12","3.13"] # python3.6 reached EOL and is no longer being supported on # new versions of hosted runners on Github Actions # ubuntu-20.04 is the last version that supported python3.6 diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index a2c4c8770c..fb81c231d1 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -36,6 +36,12 @@ "<=0.23": ["pydantic<2"], }, }, + "asyncpg": { + "package": "asyncpg", + "deps": { + "*": ["pytest-asyncio"], + }, + }, "bottle": { "package": "bottle", "deps": { diff --git a/scripts/populate_tox/populate_tox.py b/scripts/populate_tox/populate_tox.py index a8c58938ae..317ba00586 100644 --- a/scripts/populate_tox/populate_tox.py +++ b/scripts/populate_tox/populate_tox.py @@ -67,7 +67,6 @@ "potel", # Integrations that can be migrated -- we should eventually remove all # of these from the IGNORE list - "asyncpg", "beam", "boto3", "chalice", diff --git a/scripts/populate_tox/tox.jinja b/scripts/populate_tox/tox.jinja index 115b99fd5c..e10707b9c4 100755 --- a/scripts/populate_tox/tox.jinja +++ b/scripts/populate_tox/tox.jinja @@ -39,10 +39,6 @@ envlist = # Asgi {py3.7,py3.12,py3.13}-asgi - # asyncpg - {py3.7,py3.10}-asyncpg-v{0.23} - {py3.8,py3.11,py3.12}-asyncpg-latest - # AWS Lambda {py3.8,py3.9,py3.11,py3.13}-aws_lambda @@ -164,11 +160,6 @@ deps = asgi: pytest-asyncio asgi: async-asgi-testclient - # Asyncpg - asyncpg-v0.23: asyncpg~=0.23.0 - asyncpg-latest: asyncpg - asyncpg: pytest-asyncio - # AWS Lambda aws_lambda: aws-cdk-lib aws_lambda: aws-sam-cli diff --git a/tox.ini b/tox.ini index 67ba6eadc6..d12cd7ba83 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-09-04T07:00:53.509946+00:00 +# Last generated: 2025-09-04T09:02:27.457083+00:00 [tox] requires = @@ -39,10 +39,6 @@ envlist = # Asgi {py3.7,py3.12,py3.13}-asgi - # asyncpg - {py3.7,py3.10}-asyncpg-v{0.23} - {py3.8,py3.11,py3.12}-asyncpg-latest - # AWS Lambda {py3.8,py3.9,py3.11,py3.13}-aws_lambda @@ -158,6 +154,11 @@ envlist = # ~~~ DBs ~~~ + {py3.6,py3.8,py3.9}-asyncpg-v0.23.0 + {py3.6,py3.9,py3.10}-asyncpg-v0.25.0 + {py3.7,py3.9,py3.10}-asyncpg-v0.27.0 + {py3.8,py3.11,py3.12}-asyncpg-v0.30.0 + {py3.7,py3.11,py3.12}-clickhouse_driver-v0.2.9 {py3.6}-pymongo-v3.5.1 @@ -358,11 +359,6 @@ deps = asgi: pytest-asyncio asgi: async-asgi-testclient - # Asyncpg - asyncpg-v0.23: asyncpg~=0.23.0 - asyncpg-latest: asyncpg - asyncpg: pytest-asyncio - # AWS Lambda aws_lambda: aws-cdk-lib aws_lambda: aws-sam-cli @@ -541,6 +537,12 @@ deps = # ~~~ DBs ~~~ + asyncpg-v0.23.0: asyncpg==0.23.0 + asyncpg-v0.25.0: asyncpg==0.25.0 + asyncpg-v0.27.0: asyncpg==0.27.0 + asyncpg-v0.30.0: asyncpg==0.30.0 + asyncpg: pytest-asyncio + clickhouse_driver-v0.2.9: clickhouse-driver==0.2.9 pymongo-v3.5.1: pymongo==3.5.1 From f974e667097bf874d8219f27f89cc60c1483bb59 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 11:33:07 +0200 Subject: [PATCH 02/11] update docker snippet to work ootb --- tests/integrations/asyncpg/test_asyncpg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integrations/asyncpg/test_asyncpg.py b/tests/integrations/asyncpg/test_asyncpg.py index e36d15c5d2..92fdba4b1c 100644 --- a/tests/integrations/asyncpg/test_asyncpg.py +++ b/tests/integrations/asyncpg/test_asyncpg.py @@ -3,7 +3,7 @@ Tests need a local postgresql instance running, this can best be done using ```sh -docker run --rm --name some-postgres -e POSTGRES_USER=foo -e POSTGRES_PASSWORD=bar -d -p 5432:5432 postgres +docker run --rm --name some-postgres -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=sentry -d -p 5432:5432 postgres ``` The tests use the following credentials to establish a database connection. From b2e988f8d030bc239c4b45707b8e158b19d38634 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 11:44:00 +0200 Subject: [PATCH 03/11] trying something --- tests/integrations/asyncpg/test_asyncpg.py | 37 +++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/integrations/asyncpg/test_asyncpg.py b/tests/integrations/asyncpg/test_asyncpg.py index 92fdba4b1c..ee888d5fc8 100644 --- a/tests/integrations/asyncpg/test_asyncpg.py +++ b/tests/integrations/asyncpg/test_asyncpg.py @@ -16,7 +16,19 @@ PG_PORT = int(os.getenv("SENTRY_PYTHON_TEST_POSTGRES_PORT", "5432")) PG_USER = os.getenv("SENTRY_PYTHON_TEST_POSTGRES_USER", "postgres") PG_PASSWORD = os.getenv("SENTRY_PYTHON_TEST_POSTGRES_PASSWORD", "sentry") -PG_NAME = os.getenv("SENTRY_PYTHON_TEST_POSTGRES_NAME", "postgres") +PG_NAME_BASE = os.getenv("SENTRY_PYTHON_TEST_POSTGRES_NAME", "postgres") + + +def _get_db_name(): + """Get database name, using worker ID for parallel test isolation.""" + # Check if we're running with pytest-xdist (parallel execution) + worker_id = os.getenv("PYTEST_XDIST_WORKER") + if worker_id: + return f"{PG_NAME_BASE}_{worker_id}" + return PG_NAME_BASE + + +PG_NAME = _get_db_name() import datetime from contextlib import contextmanager @@ -55,6 +67,29 @@ @pytest_asyncio.fixture(autouse=True) async def _clean_pg(): + # Connect to the default postgres database to create our test database + default_conn_uri = "postgresql://{}:{}@{}/{}".format( + PG_USER, PG_PASSWORD, PG_HOST, PG_NAME_BASE + ) + + # Create the test database if it doesn't exist + try: + default_conn = await connect(default_conn_uri) + try: + # Check if database exists, create if not + result = await default_conn.fetchval( + "SELECT 1 FROM pg_database WHERE datname = $1", PG_NAME + ) + if not result: + await default_conn.execute(f'CREATE DATABASE "{PG_NAME}"') + finally: + await default_conn.close() + except Exception: + # If we can't connect to default postgres db, assume our test db already exists + # or that we're connecting to the same database (PG_NAME == PG_NAME_BASE) + pass + + # Now connect to our test database and set up the table conn = await connect(PG_CONNECTION_URI) await conn.execute("DROP TABLE IF EXISTS users") await conn.execute( From f79b6168927e092292a81230206e334cc9363ca9 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 11:49:32 +0200 Subject: [PATCH 04/11] . --- tests/integrations/asyncpg/test_asyncpg.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/integrations/asyncpg/test_asyncpg.py b/tests/integrations/asyncpg/test_asyncpg.py index ee888d5fc8..46bc18c104 100644 --- a/tests/integrations/asyncpg/test_asyncpg.py +++ b/tests/integrations/asyncpg/test_asyncpg.py @@ -10,6 +10,7 @@ """ import os +import threading PG_HOST = os.getenv("SENTRY_PYTHON_TEST_POSTGRES_HOST", "localhost") @@ -20,12 +21,12 @@ def _get_db_name(): - """Get database name, using worker ID for parallel test isolation.""" - # Check if we're running with pytest-xdist (parallel execution) - worker_id = os.getenv("PYTEST_XDIST_WORKER") - if worker_id: - return f"{PG_NAME_BASE}_{worker_id}" - return PG_NAME_BASE + """Get database name, using worker/process ID for parallel test isolation.""" + # For tox parallel execution or other parallel scenarios, use process ID + # This ensures each process gets its own database + pid = os.getpid() + thread_id = threading.get_ident() + return f"{PG_NAME_BASE}_{pid}_{thread_id}" PG_NAME = _get_db_name() From b4773c6a6cf31df4d5416257ba86d7caca46ce25 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 11:56:09 +0200 Subject: [PATCH 05/11] . --- scripts/populate_tox/config.py | 1 + tests/integrations/asyncpg/test_asyncpg.py | 3 --- tox.ini | 6 +++--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index fb81c231d1..ff0a2a242e 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -41,6 +41,7 @@ "deps": { "*": ["pytest-asyncio"], }, + "python": ">=3.7", }, "bottle": { "package": "bottle", diff --git a/tests/integrations/asyncpg/test_asyncpg.py b/tests/integrations/asyncpg/test_asyncpg.py index 46bc18c104..4e7a27302d 100644 --- a/tests/integrations/asyncpg/test_asyncpg.py +++ b/tests/integrations/asyncpg/test_asyncpg.py @@ -21,9 +21,6 @@ def _get_db_name(): - """Get database name, using worker/process ID for parallel test isolation.""" - # For tox parallel execution or other parallel scenarios, use process ID - # This ensures each process gets its own database pid = os.getpid() thread_id = threading.get_ident() return f"{PG_NAME_BASE}_{pid}_{thread_id}" diff --git a/tox.ini b/tox.ini index d12cd7ba83..c9d8f9e5bd 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-09-04T09:02:27.457083+00:00 +# Last generated: 2025-09-04T09:55:59.328948+00:00 [tox] requires = @@ -154,8 +154,8 @@ envlist = # ~~~ DBs ~~~ - {py3.6,py3.8,py3.9}-asyncpg-v0.23.0 - {py3.6,py3.9,py3.10}-asyncpg-v0.25.0 + {py3.7,py3.8,py3.9}-asyncpg-v0.23.0 + {py3.7,py3.9,py3.10}-asyncpg-v0.25.0 {py3.7,py3.9,py3.10}-asyncpg-v0.27.0 {py3.8,py3.11,py3.12}-asyncpg-v0.30.0 From f4dd6c7bf99227652a0e6800e991fc37bd9b7673 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 12:02:45 +0200 Subject: [PATCH 06/11] cleanup --- tests/integrations/asyncpg/test_asyncpg.py | 52 ++++++---------------- 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/tests/integrations/asyncpg/test_asyncpg.py b/tests/integrations/asyncpg/test_asyncpg.py index 4e7a27302d..9a8677aebf 100644 --- a/tests/integrations/asyncpg/test_asyncpg.py +++ b/tests/integrations/asyncpg/test_asyncpg.py @@ -11,7 +11,20 @@ import os import threading +import datetime +from contextlib import contextmanager +from unittest import mock + +import asyncpg +import pytest +import pytest_asyncio +from asyncpg import connect, Connection +from sentry_sdk import capture_message, start_transaction +from sentry_sdk.integrations.asyncpg import AsyncPGIntegration +from sentry_sdk.consts import SPANDATA +from sentry_sdk.tracing_utils import record_sql_queries +from tests.conftest import ApproxDict PG_HOST = os.getenv("SENTRY_PYTHON_TEST_POSTGRES_HOST", "localhost") PG_PORT = int(os.getenv("SENTRY_PYTHON_TEST_POSTGRES_PORT", "5432")) @@ -28,22 +41,6 @@ def _get_db_name(): PG_NAME = _get_db_name() -import datetime -from contextlib import contextmanager -from unittest import mock - -import asyncpg -import pytest -import pytest_asyncio -from asyncpg import connect, Connection - -from sentry_sdk import capture_message, start_transaction -from sentry_sdk.integrations.asyncpg import AsyncPGIntegration -from sentry_sdk.consts import SPANDATA -from sentry_sdk.tracing_utils import record_sql_queries -from tests.conftest import ApproxDict - - PG_CONNECTION_URI = "postgresql://{}:{}@{}/{}".format( PG_USER, PG_PASSWORD, PG_HOST, PG_NAME ) @@ -65,29 +62,6 @@ def _get_db_name(): @pytest_asyncio.fixture(autouse=True) async def _clean_pg(): - # Connect to the default postgres database to create our test database - default_conn_uri = "postgresql://{}:{}@{}/{}".format( - PG_USER, PG_PASSWORD, PG_HOST, PG_NAME_BASE - ) - - # Create the test database if it doesn't exist - try: - default_conn = await connect(default_conn_uri) - try: - # Check if database exists, create if not - result = await default_conn.fetchval( - "SELECT 1 FROM pg_database WHERE datname = $1", PG_NAME - ) - if not result: - await default_conn.execute(f'CREATE DATABASE "{PG_NAME}"') - finally: - await default_conn.close() - except Exception: - # If we can't connect to default postgres db, assume our test db already exists - # or that we're connecting to the same database (PG_NAME == PG_NAME_BASE) - pass - - # Now connect to our test database and set up the table conn = await connect(PG_CONNECTION_URI) await conn.execute("DROP TABLE IF EXISTS users") await conn.execute( From 17bf0845b74c116473e20cf6b45c4149912eeb6c Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 12:04:44 +0200 Subject: [PATCH 07/11] just isolate on process level --- tests/integrations/asyncpg/test_asyncpg.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/integrations/asyncpg/test_asyncpg.py b/tests/integrations/asyncpg/test_asyncpg.py index 9a8677aebf..439d14b6b2 100644 --- a/tests/integrations/asyncpg/test_asyncpg.py +++ b/tests/integrations/asyncpg/test_asyncpg.py @@ -10,7 +10,6 @@ """ import os -import threading import datetime from contextlib import contextmanager from unittest import mock @@ -35,8 +34,7 @@ def _get_db_name(): pid = os.getpid() - thread_id = threading.get_ident() - return f"{PG_NAME_BASE}_{pid}_{thread_id}" + return f"{PG_NAME_BASE}_{pid}" PG_NAME = _get_db_name() From 44922785e392448bf8695ee97a0bbb94722351e2 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 12:06:41 +0200 Subject: [PATCH 08/11] . --- tests/integrations/asyncpg/test_asyncpg.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/integrations/asyncpg/test_asyncpg.py b/tests/integrations/asyncpg/test_asyncpg.py index 439d14b6b2..37b02d5f27 100644 --- a/tests/integrations/asyncpg/test_asyncpg.py +++ b/tests/integrations/asyncpg/test_asyncpg.py @@ -60,6 +60,24 @@ def _get_db_name(): @pytest_asyncio.fixture(autouse=True) async def _clean_pg(): + # Create the test database if it doesn't exist + try: + default_conn = await connect(PG_CONNECTION_URI) + try: + # Check if database exists, create if not + result = await default_conn.fetchval( + "SELECT 1 FROM pg_database WHERE datname = $1", PG_NAME + ) + if not result: + await default_conn.execute(f'CREATE DATABASE "{PG_NAME}"') + finally: + await default_conn.close() + except Exception: + # If we can't connect to default postgres db, assume our test db already exists + # or that we're connecting to the same database (PG_NAME == PG_NAME_BASE) + pass + + # Now connect to our test database and set up the table conn = await connect(PG_CONNECTION_URI) await conn.execute("DROP TABLE IF EXISTS users") await conn.execute( From 0de8099307d2e2a336c9f644fbd43d1072b1eab0 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 12:11:42 +0200 Subject: [PATCH 09/11] . --- tests/integrations/asyncpg/test_asyncpg.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/integrations/asyncpg/test_asyncpg.py b/tests/integrations/asyncpg/test_asyncpg.py index 37b02d5f27..3028f10e12 100644 --- a/tests/integrations/asyncpg/test_asyncpg.py +++ b/tests/integrations/asyncpg/test_asyncpg.py @@ -62,7 +62,9 @@ def _get_db_name(): async def _clean_pg(): # Create the test database if it doesn't exist try: - default_conn = await connect(PG_CONNECTION_URI) + default_conn = await connect( + "postgresql://{}:{}@{}".format(PG_USER, PG_PASSWORD, PG_HOST) + ) try: # Check if database exists, create if not result = await default_conn.fetchval( From 0b5939451d4cb522d6f78fd524d2062a6aab4fa8 Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 12:21:24 +0200 Subject: [PATCH 10/11] . --- tests/integrations/asyncpg/test_asyncpg.py | 25 +++++++++------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/tests/integrations/asyncpg/test_asyncpg.py b/tests/integrations/asyncpg/test_asyncpg.py index 3028f10e12..e23612c055 100644 --- a/tests/integrations/asyncpg/test_asyncpg.py +++ b/tests/integrations/asyncpg/test_asyncpg.py @@ -61,23 +61,18 @@ def _get_db_name(): @pytest_asyncio.fixture(autouse=True) async def _clean_pg(): # Create the test database if it doesn't exist + default_conn = await connect( + "postgresql://{}:{}@{}".format(PG_USER, PG_PASSWORD, PG_HOST) + ) try: - default_conn = await connect( - "postgresql://{}:{}@{}".format(PG_USER, PG_PASSWORD, PG_HOST) + # Check if database exists, create if not + result = await default_conn.fetchval( + "SELECT 1 FROM pg_database WHERE datname = $1", PG_NAME ) - try: - # Check if database exists, create if not - result = await default_conn.fetchval( - "SELECT 1 FROM pg_database WHERE datname = $1", PG_NAME - ) - if not result: - await default_conn.execute(f'CREATE DATABASE "{PG_NAME}"') - finally: - await default_conn.close() - except Exception: - # If we can't connect to default postgres db, assume our test db already exists - # or that we're connecting to the same database (PG_NAME == PG_NAME_BASE) - pass + if not result: + await default_conn.execute(f'CREATE DATABASE "{PG_NAME}"') + finally: + await default_conn.close() # Now connect to our test database and set up the table conn = await connect(PG_CONNECTION_URI) From b4c9bc54a447f3a7513d1646f2c3729d5371182c Mon Sep 17 00:00:00 2001 From: Ivana Kellyer Date: Thu, 4 Sep 2025 15:37:52 +0200 Subject: [PATCH 11/11] . --- scripts/populate_tox/config.py | 1 + tox.ini | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/populate_tox/config.py b/scripts/populate_tox/config.py index 0d5f08b706..a1ada77c98 100644 --- a/scripts/populate_tox/config.py +++ b/scripts/populate_tox/config.py @@ -41,6 +41,7 @@ "deps": { "*": ["pytest-asyncio"], }, + "python": ">=3.7", }, "beam": { "package": "apache-beam", diff --git a/tox.ini b/tox.ini index 7752669880..5999804d6a 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ # The file (and all resulting CI YAMLs) then need to be regenerated via # "scripts/generate-test-files.sh". # -# Last generated: 2025-09-04T12:41:07.285452+00:00 +# Last generated: 2025-09-04T12:49:06.316162+00:00 [tox] requires = @@ -150,8 +150,8 @@ envlist = # ~~~ DBs ~~~ - {py3.6,py3.8,py3.9}-asyncpg-v0.23.0 - {py3.6,py3.9,py3.10}-asyncpg-v0.25.0 + {py3.7,py3.8,py3.9}-asyncpg-v0.23.0 + {py3.7,py3.9,py3.10}-asyncpg-v0.25.0 {py3.7,py3.9,py3.10}-asyncpg-v0.27.0 {py3.8,py3.11,py3.12}-asyncpg-v0.30.0