From 8e7a94b34eac9809a4e72b8ea1ebcc7636911888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Nesveda?= Date: Wed, 29 Mar 2023 15:40:12 +0200 Subject: [PATCH 1/2] Fix detecting signature of built-in functions in event handler --- src/apify/event_manager.py | 21 ++++++++++++++++++--- tests/unit/test_event_manager.py | 17 ++++++++++++++++- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/apify/event_manager.py b/src/apify/event_manager.py index 4241c071..d408ae97 100644 --- a/src/apify/event_manager.py +++ b/src/apify/event_manager.py @@ -100,9 +100,24 @@ def on(self, event_name: ActorEventTypes, listener: ListenerType) -> Callable: if not self._initialized: raise RuntimeError('EventManager was not initialized!') - listener_argument_count = len(inspect.signature(listener).parameters) - if listener_argument_count > 1: - raise ValueError('The "listener" argument must be a callable which accepts 0 or 1 arguments!') + # Detect whether the listener will accept the event_data argument + try: + signature = inspect.signature(listener) + except (ValueError, TypeError): + # If we can't determine the listener argument count (e.g. for the built-in `print` function), + # let's assume the listener will accept the argument + listener_argument_count = 1 + else: + try: + dummy_event_data: Dict = {} + signature.bind(dummy_event_data) + listener_argument_count = 1 + except TypeError: + try: + signature.bind() + listener_argument_count = 0 + except TypeError: + raise ValueError('The "listener" argument must be a callable which accepts 0 or 1 arguments!') event_name = _maybe_extract_enum_member_value(event_name) diff --git a/tests/unit/test_event_manager.py b/tests/unit/test_event_manager.py index 11dcd3c1..48f74099 100644 --- a/tests/unit/test_event_manager.py +++ b/tests/unit/test_event_manager.py @@ -135,10 +135,23 @@ def sync_two_arguments(_arg1: Any, _arg2: Any) -> None: async def async_two_arguments(_arg1: Any, _arg2: Any) -> None: pass + def sync_two_arguments_one_default(event_data: Any, _arg2: Any = 'default_value') -> None: + nonlocal event_calls + event_calls.append(('sync_two_arguments_one_default', event_data)) + + async def async_two_arguments_one_default(event_data: Any, _arg2: Any = 'default_value') -> None: + nonlocal event_calls + event_calls.append(('async_two_arguments_one_default', event_data)) + event_manager.on(ActorEventTypes.SYSTEM_INFO, sync_no_arguments) event_manager.on(ActorEventTypes.SYSTEM_INFO, async_no_arguments) event_manager.on(ActorEventTypes.SYSTEM_INFO, sync_one_argument) event_manager.on(ActorEventTypes.SYSTEM_INFO, async_one_argument) + event_manager.on(ActorEventTypes.SYSTEM_INFO, sync_two_arguments_one_default) + event_manager.on(ActorEventTypes.SYSTEM_INFO, async_two_arguments_one_default) + + # built-in functions should work too + event_manager.on(ActorEventTypes.SYSTEM_INFO, print) with pytest.raises(ValueError, match='The "listener" argument must be a callable which accepts 0 or 1 arguments!'): event_manager.on(ActorEventTypes.SYSTEM_INFO, sync_two_arguments) # type: ignore[arg-type] @@ -148,11 +161,13 @@ async def async_two_arguments(_arg1: Any, _arg2: Any) -> None: event_manager.emit(ActorEventTypes.SYSTEM_INFO, 'DUMMY_SYSTEM_INFO') await asyncio.sleep(0.1) - assert len(event_calls) == 4 + assert len(event_calls) == 6 assert ('sync_no_arguments', None) in event_calls assert ('async_no_arguments', None) in event_calls assert ('sync_one_argument', 'DUMMY_SYSTEM_INFO') in event_calls assert ('async_one_argument', 'DUMMY_SYSTEM_INFO') in event_calls + assert ('sync_two_arguments_one_default', 'DUMMY_SYSTEM_INFO') in event_calls + assert ('async_two_arguments_one_default', 'DUMMY_SYSTEM_INFO') in event_calls async def test_event_async_handling_local(self) -> None: config = Configuration() From c4292058646579cc6142b6a5b9687d30afc3365d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Nesveda?= Date: Wed, 29 Mar 2023 15:45:02 +0200 Subject: [PATCH 2/2] Add test for `pprint` --- tests/unit/test_event_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/test_event_manager.py b/tests/unit/test_event_manager.py index 48f74099..4e68c241 100644 --- a/tests/unit/test_event_manager.py +++ b/tests/unit/test_event_manager.py @@ -3,6 +3,7 @@ import logging import time from collections import defaultdict +from pprint import pprint from typing import Any, Callable, Dict, Optional, Set import pytest @@ -153,6 +154,9 @@ async def async_two_arguments_one_default(event_data: Any, _arg2: Any = 'default # built-in functions should work too event_manager.on(ActorEventTypes.SYSTEM_INFO, print) + # functions from the standard library should work too + event_manager.on(ActorEventTypes.SYSTEM_INFO, pprint) + with pytest.raises(ValueError, match='The "listener" argument must be a callable which accepts 0 or 1 arguments!'): event_manager.on(ActorEventTypes.SYSTEM_INFO, sync_two_arguments) # type: ignore[arg-type] with pytest.raises(ValueError, match='The "listener" argument must be a callable which accepts 0 or 1 arguments!'):