Skip to content

Commit da8aba2

Browse files
committed
Fix wrong event loop usage from PGMQueue
1 parent 4e471cf commit da8aba2

6 files changed

Lines changed: 54 additions & 145 deletions

File tree

pgmq_sqlalchemy/queue.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
from typing import List, Optional, TYPE_CHECKING
32

43
from sqlalchemy import create_engine
@@ -28,14 +27,12 @@ class PGMQueue:
2827

2928
is_async: bool = False
3029
is_pg_partman_ext_checked: bool = False
31-
loop: asyncio.AbstractEventLoop = None
3230

3331
def __init__(
3432
self,
3533
dsn: Optional[str] = None,
3634
engine: Optional[ENGINE_TYPE] = None,
3735
session_maker: Optional[sessionmaker] = None,
38-
loop: Optional[asyncio.AbstractEventLoop] = None,
3936
) -> None:
4037
"""
4138
@@ -84,8 +81,6 @@ def __init__(
8481
dsn (Optional[str]): Database connection string.
8582
engine (Optional[ENGINE_TYPE]): SQLAlchemy engine (sync or async).
8683
session_maker (Optional[sessionmaker]): SQLAlchemy session maker.
87-
loop (Optional[asyncio.AbstractEventLoop]): Event loop for async operations.
88-
If not provided, a new event loop will be created for async engines.
8984
9085
.. note::
9186
| ``PGMQueue`` will **auto create** the ``pgmq`` extension ( and ``pg_partman`` extension if the method is related with **partitioned_queue** ) if it does not exist in the Postgres.
@@ -112,17 +107,6 @@ def __init__(
112107
bind=self.engine, class_=get_session_type(self.engine)
113108
)
114109

115-
if self.is_async:
116-
if loop is not None:
117-
# Use the provided event loop
118-
self.loop = loop
119-
else:
120-
# Create a new event loop
121-
self.loop = asyncio.new_event_loop()
122-
123-
# create pgmq extension if not exists
124-
self._check_pgmq_ext()
125-
126110
def _check_pgmq_ext(self) -> None:
127111
"""Check if the pgmq extension exists."""
128112
self._execute_operation(PGMQOperation.check_pgmq_ext, session=None, commit=True)

tests/conftest.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
from tests.constant import ASYNC_DRIVERS, SYNC_DRIVERS
1111

1212
# Async fixture names for test filtering
13-
ASYNC_FIXTURE_NAMES = ['pgmq_by_async_dsn', 'pgmq_by_async_engine', 'pgmq_by_async_session_maker']
13+
ASYNC_FIXTURE_NAMES = [
14+
"pgmq_by_async_dsn",
15+
"pgmq_by_async_engine",
16+
"pgmq_by_async_session_maker",
17+
]
1418

1519

16-
def pytest_addoption(parser):
20+
def pytest_addoption(parser: pytest.Parser):
1721
"""Add custom command-line options for pytest."""
1822
parser.addoption(
1923
"--driver",
@@ -29,30 +33,30 @@ def pytest_addoption(parser):
2933
)
3034

3135

32-
def pytest_generate_tests(metafunc):
36+
def pytest_generate_tests(metafunc: pytest.Metafunc):
3337
"""
3438
Dynamically generate test parametrization based on CLI options.
35-
39+
3640
This allows us to parametrize fixtures based on the --driver option.
3741
"""
3842
if "pgmq_all_variants" in metafunc.fixturenames:
3943
driver_from_cli = metafunc.config.getoption("--driver")
40-
44+
4145
# Define sync and async fixture variants
4246
sync_fixtures = [
43-
'pgmq_by_dsn',
44-
'pgmq_by_engine',
45-
'pgmq_by_session_maker',
46-
'pgmq_by_dsn_and_engine',
47-
'pgmq_by_dsn_and_session_maker',
47+
"pgmq_by_dsn",
48+
"pgmq_by_engine",
49+
"pgmq_by_session_maker",
50+
"pgmq_by_dsn_and_engine",
51+
"pgmq_by_dsn_and_session_maker",
4852
]
49-
53+
5054
async_fixtures = [
51-
'pgmq_by_async_dsn',
52-
'pgmq_by_async_engine',
53-
'pgmq_by_async_session_maker',
55+
"pgmq_by_async_dsn",
56+
"pgmq_by_async_engine",
57+
"pgmq_by_async_session_maker",
5458
]
55-
59+
5660
# Determine which fixtures to use
5761
if not driver_from_cli:
5862
# No driver specified, use all fixtures
@@ -63,13 +67,9 @@ def pytest_generate_tests(metafunc):
6367
else:
6468
# Sync driver specified
6569
fixture_params = sync_fixtures
66-
70+
6771
# Parametrize the test
68-
metafunc.parametrize(
69-
"pgmq_all_variants",
70-
fixture_params,
71-
indirect=True
72-
)
72+
metafunc.parametrize("pgmq_all_variants", fixture_params, indirect=True)
7373

7474

7575
@pytest.fixture(scope="module")
@@ -93,7 +93,7 @@ def get_sa_password():
9393

9494

9595
@pytest.fixture(scope="module")
96-
def get_sa_db(request):
96+
def get_sa_db(request: pytest.FixtureRequest):
9797
"""Get database name from CLI argument or environment variable."""
9898
db_name_from_cli = request.config.getoption("--db-name")
9999
if db_name_from_cli:
@@ -112,14 +112,14 @@ def get_dsn(
112112
):
113113
"""Get DSN for sync drivers based on CLI option."""
114114
driver_from_cli = request.config.getoption("--driver")
115-
115+
116116
# Use CLI driver if specified and it's a sync driver
117117
if driver_from_cli and driver_from_cli in SYNC_DRIVERS:
118118
driver = driver_from_cli
119119
else:
120120
# Default to first sync driver if no CLI option or invalid
121121
driver = SYNC_DRIVERS[0]
122-
122+
123123
return f"postgresql+{driver}://{get_sa_user}:{get_sa_password}@{get_sa_host}:{get_sa_port}/{get_sa_db}"
124124

125125

@@ -134,14 +134,14 @@ def get_async_dsn(
134134
):
135135
"""Get DSN for async drivers based on CLI option."""
136136
driver_from_cli = request.config.getoption("--driver")
137-
137+
138138
# Use CLI driver if specified and it's an async driver
139139
if driver_from_cli and driver_from_cli in ASYNC_DRIVERS:
140140
driver = driver_from_cli
141141
else:
142142
# Default to first async driver if no CLI option or invalid
143143
driver = ASYNC_DRIVERS[0]
144-
144+
145145
return f"postgresql+{driver}://{get_sa_user}:{get_sa_password}@{get_sa_host}:{get_sa_port}/{get_sa_db}"
146146

147147

tests/fixture_deps.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import uuid
22
from typing import Tuple
3+
from inspect import iscoroutinefunction
34

45
import pytest
56

67
from pgmq_sqlalchemy import PGMQueue
8+
from tests.constant import ASYNC_DRIVERS
79
from tests._utils import check_queue_exists
810

911
PGMQ_WITH_QUEUE = Tuple[PGMQueue, str]
@@ -13,18 +15,24 @@
1315
def pgmq_all_variants(request: pytest.FixtureRequest) -> PGMQueue:
1416
"""
1517
Fixture that parametrizes tests across all appropriate PGMQueue initialization methods.
16-
18+
1719
When --driver is specified, only fixtures matching that driver type (sync/async) are used.
1820
Without --driver, all fixtures are used.
19-
21+
2022
The parametrization is handled by pytest_generate_tests in conftest.py.
21-
23+
2224
Usage:
2325
def test_something(pgmq_all_variants):
2426
pgmq: PGMQueue = pgmq_all_variants
2527
# test code here
2628
"""
2729
# The param is set by pytest_generate_tests via indirect parametrization
30+
is_async_test = iscoroutinefunction(request.function)
31+
driver_from_cli = request.config.getoption("--driver")
32+
if driver_from_cli and (driver_from_cli in ASYNC_DRIVERS and not is_async_test):
33+
pytest.skip(
34+
reason=f"Skip sync test: {request.function.__name__}, as driver: {driver_from_cli} is async"
35+
)
2836
return request.getfixturevalue(request.param)
2937

3038

tests/test_construct_pgmq.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from tests.fixture_deps import pgmq_all_variants
55

6+
use_fixtures = [pgmq_all_variants]
7+
68

79
def test_construct_pgmq(pgmq_all_variants):
810
pgmq: PGMQueue = pgmq_all_variants

tests/test_event_loop.py

Lines changed: 0 additions & 58 deletions
This file was deleted.

tests/test_queue.py

Lines changed: 15 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,15 @@
88

99
from tests.fixture_deps import (
1010
PGMQ_WITH_QUEUE,
11-
pgmq_all_variants,
1211
pgmq_setup_teardown,
1312
pgmq_partitioned_setup_teardown,
13+
pgmq_all_variants,
1414
)
1515

1616
from tests._utils import check_queue_exists
1717
from tests.constant import MSG, LOCK_FILE_NAME
1818

19-
use_fixtures = [
20-
pgmq_setup_teardown,
21-
pgmq_partitioned_setup_teardown,
22-
]
19+
use_fixtures = [pgmq_setup_teardown, pgmq_partitioned_setup_teardown, pgmq_all_variants]
2320

2421

2522
def test_create_queue(pgmq_all_variants, db_session):
@@ -486,13 +483,13 @@ def test_read_with_poll_without_vt(pgmq_setup_teardown: PGMQ_WITH_QUEUE):
486483
"""Test read_with_poll when vt parameter is not provided (None)."""
487484

488485
pgmq, queue_name = pgmq_setup_teardown
489-
486+
490487
# Set a custom default vt for the pgmq instance
491488
pgmq.vt = 100
492-
489+
493490
# Send a message
494491
msg_id = pgmq.send(queue_name, MSG)
495-
492+
496493
# Call read_with_poll with vt=None to test the fallback logic
497494
# When vt is None, it should fall back to using pgmq.vt value (100)
498495
msgs = pgmq.read_with_poll(
@@ -502,67 +499,43 @@ def test_read_with_poll_without_vt(pgmq_setup_teardown: PGMQ_WITH_QUEUE):
502499
max_poll_seconds=2,
503500
poll_interval_ms=100,
504501
)
505-
502+
506503
assert msgs is not None
507504
assert len(msgs) == 1
508505
assert msgs[0].msg_id == msg_id
509506
assert msgs[0].message == MSG
510507

511508

512-
def test_execute_operation_with_provided_sync_session(pgmq_by_session_maker, get_session_maker, db_session):
509+
def test_execute_operation_with_provided_sync_session(
510+
pgmq_by_session_maker, get_session_maker, db_session
511+
):
513512
"""Test _execute_operation sync path when session is provided."""
514513

515514
pgmq: PGMQueue = pgmq_by_session_maker
516515
queue_name = f"test_queue_{uuid.uuid4().hex}"
517-
516+
518517
# Create a session to pass to the operations
519518
# Using the same session across multiple operations demonstrates
520519
# that the sync path with provided session works correctly
521520
with get_session_maker() as session:
522521
# Create queue with provided session
523522
pgmq.create_queue(queue_name, session=session)
524-
523+
525524
# Verify queue was created
526525
assert check_queue_exists(db_session, queue_name) is True
527-
526+
528527
# Send a message with the same provided session
529528
msg_id = pgmq.send(queue_name, MSG, session=session)
530-
529+
531530
# Read message with the same provided session
532531
msg = pgmq.read(queue_name, vt=30, session=session)
533-
532+
534533
assert msg is not None
535534
assert msg.msg_id == msg_id
536535
assert msg.message == MSG
537-
536+
538537
# Clean up with the same provided session
539538
pgmq.drop_queue(queue_name, session=session)
540-
541-
# Verify queue was dropped
542-
assert check_queue_exists(db_session, queue_name) is False
543-
544539

545-
def test_execute_operation_async_with_session_none(pgmq_by_async_dsn, db_session):
546-
"""Test _execute_operation async path when session is None."""
547-
548-
pgmq: PGMQueue = pgmq_by_async_dsn
549-
queue_name = f"test_queue_{uuid.uuid4().hex}"
550-
551-
# Verify this is an async PGMQueue
552-
assert pgmq.is_async is True
553-
554-
# When session is None, _execute_operation creates a new async session
555-
# and uses loop.run_until_complete to execute the operation
556-
pgmq.create_queue(queue_name)
557-
msg_id = pgmq.send(queue_name, MSG)
558-
msg = pgmq.read(queue_name, vt=30)
559-
560-
assert msg is not None
561-
assert msg.msg_id == msg_id
562-
assert msg.message == MSG
563-
564-
# Clean up
565-
pgmq.drop_queue(queue_name)
566-
567540
# Verify queue was dropped
568541
assert check_queue_exists(db_session, queue_name) is False

0 commit comments

Comments
 (0)