add listener callback not getting executed in asyncio pytest framework
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 35/100
Línea de trabajo
Comienza con dlnspublisher.core.notification_manager y la configuración de _listen_to_channel/add_listener; después, inspecciona el ciclo de vida de pg_conn_fixture y fixture_app. Ejecuta test_sse_event_queue_receives_inserted_notification y sigue la inserción en la base de datos, la conexión LISTEN y el callback; se considera terminado cuando se ejecuta el callback y la prueba SSE recibe el payload de ejemplo insertado.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
def _notification_callback(self, _, pid, channel, payload):
LOG.info("_notification_callback invoked", channel=channel, pid=pid, payload_preview=str(payload)[:100])
print(f"Notification callback invoked for channel: {channel}, pid: {pid}, payload: {payload}")
# Create a task to process this notification asynchronously
# This avoids blocking the callback which would block the connection
try:
asyncio.create_task(
self._process_notification_callback(payload, channel, pid)
)
except RuntimeError as error:
self._log_error(
"Failed to create notification processing task",
pid=pid,
error=str(error),
)
async def _process_notification_callback(self, payload, channel, pid):
"""
Process a notification received via callback.
This runs in a separate task to avoid blocking the connection.
"""
try:
notification = json.loads(payload)
LOG.info("Raw payload received", notification=notification, channel=channel)
await self.distribute_notification(notification)
except asyncio.CancelledError:
LOG.info("Notification processing cancelled", channel=channel, pid=pid)
raise
except json.JSONDecodeError as error:
self._log_error(
"Failed to decode JSON payload from notification",
pid=pid,
error=str(error),
payload=payload,
)
except Exception as error:
self._log_error(
"Error processing notification from callback",
pid=pid,
error=str(error),
)
async def _listen_to_channel(self):
"""
Background task that listens to the database notification channel.
Uses db_backoff decorator to handle reconnection logic.
"""
LOG.info("Connecting to database for notifications", channel=self.channel)
conn = await asyncpg.connect(
user=SETTINGS.db_user,
password=SETTINGS.db_password,
host=SETTINGS.db_host,
port=SETTINGS.db_port,
database=SETTINGS.database,
)
LOG.info("Database connection established", channel=self.channel)
try:
# Listen to the channel
await conn.add_listener(self.channel, self._notification_callback)
LOG.info("Listening on channel", channel=self.channel)
# Main notification loop
while self.running:
try:
# Just keep the connection alive
await asyncio.sleep(1)
except asyncio.TimeoutError:
# Timeout is expected, just continue the loop
continue
except asyncio.CancelledError:
LOG.info(
"Notification listener cancelled received from the running task",
channel=self.channel,
)
break
except Exception as error:
self._log_error(
"Error processing notification",
error=str(error),
)
finally:
# Clean up listener and close connection
try:
await conn.remove_listener(self.channel, self._notification_callback)
await conn.close()
LOG.info("Database connection closed", channel=self.channel)
except Exception as error:
# Handle any errors during cleanup
self._log_error(
"Error closing database connection",
error=str(error),
)
Below is the tests setup
@pytest_asyncio.fixture(scope="session")
async def pg_conn_fixture():
"""PG Connection fixture"""
with patch(
"dlnspublisher.core.notification_manager.DalEngine.get_connection"
) as mock_get_connection:
conn = await asyncpg.connect(
dsn="postgres://postgres:postgres@dlnsdb:5432/postgres"
)
mock_get_connection.return_value = conn
yield
await conn.close()
@pytest_asyncio.fixture(scope="session")
async def fixture_app(pg_conn_fixture):
yield create_app()
@pytest.fixture(scope="session")
async def fixture_async_client(fixture_app) -> AsyncGenerator[AsyncClient, None]:
async with LifespanManager(fixture_app):
async with AsyncClient(
transport=ASGITransport(app=fixture_app), base_url="http://test"
) as async_client:
yield async_client
@pytest.mark.asyncio
async def test_sse_event_queue_receives_inserted_notification(
fixture_async_client: AsyncClient,
fixture_app,
insert_distribution_notification,
):
"""
tests /eap/notifications/sse returns OK when valid JWT is passed.
"""
insert_distribution_notification()
async with fixture_async_client.stream(
"GET",
"/eap/notifications/sse",
headers={HTTP_HEADER_CIS_JWT: VALID_JWT},
) as response:
try:
chunk = await asyncio.wait_for(
response.aiter_bytes().__anext__(), timeout=0.1
)
# SSE events are typically in the format: b'data: ...\n\n'
assert chunk
# Optionally, parse the event data
if chunk.startswith(b"data:"):
data = chunk[len(b"data:") :].strip()
# Try to decode JSON if possible
try:
payload = json.loads(data)
assert payload.get("payload") == "example"
except Exception:
# If not JSON, just check content
assert b"example" in data
except asyncio.TimeoutError:
assert False, "No event received from SSE endpoint after DB insert"
When I run the test case, fixture_app runs the fastAPI app and starts listening to the channel and then insert_distribution_notification inserts some data into a table which triggers the NOTIFY channel, however the callback for add_listener nevers gets executed, as I have a pdb debugger in that function but it never executes the call back and the test case gets stuck indefinitely.
I could see in my pg_stats_activity that the LISTEN channel process through fixture_app is successful, however callback nevers executed even though the NOTIFY channel name is triggered through insert_distribution_notification fixture
- Lenguaje dominante
- Python
- Estrellas
- 8.1k
- Forks
- 469
- Merge medio
- 4 d 1 h
- PR fusionados (30 d)
- 14
Preparar el entorno
Aún no hemos revisado los archivos de configuración de este proyecto. Empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de MagicStack/asyncpg
-
Dificultad 3/5 1-2 días Aptitud para principiantes 72/100
MagicStack/asyncpg#1342 ·
-
Dificultad 3/5 1-2 días Aptitud para principiantes 56/100
MagicStack/asyncpg#1340 · 1 comentario ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 28/100
MagicStack/asyncpg#1337 ·
-
Dificultad 4/5 3-5 días Aptitud para principiantes 42/100
MagicStack/asyncpg#1330 · 1 comentario ·
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 38/100
MagicStack/asyncpg#1322 ·
Todos los issues de MagicStack/asyncpg
Issues similares
-
documentation
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
kristofdegrave/homeassistant-smart-charging#1413 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
nasa/earthdata-varinfo#113 ·
-
curriculum documentation quality
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
githubnext/gh-aw-workshop#3849 ·
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 90/100
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
Los mantenedores suelen responder en 1 día