apify 1.7.1b1__py3-none-any.whl → 2.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of apify might be problematic. Click here for more details.

Files changed (62) hide show
  1. apify/__init__.py +33 -4
  2. apify/_actor.py +1074 -0
  3. apify/_configuration.py +370 -0
  4. apify/_consts.py +10 -0
  5. apify/_crypto.py +31 -27
  6. apify/_models.py +117 -0
  7. apify/_platform_event_manager.py +231 -0
  8. apify/_proxy_configuration.py +320 -0
  9. apify/_utils.py +18 -484
  10. apify/apify_storage_client/__init__.py +3 -0
  11. apify/apify_storage_client/_apify_storage_client.py +68 -0
  12. apify/apify_storage_client/_dataset_client.py +190 -0
  13. apify/apify_storage_client/_dataset_collection_client.py +51 -0
  14. apify/apify_storage_client/_key_value_store_client.py +94 -0
  15. apify/apify_storage_client/_key_value_store_collection_client.py +51 -0
  16. apify/apify_storage_client/_request_queue_client.py +176 -0
  17. apify/apify_storage_client/_request_queue_collection_client.py +51 -0
  18. apify/apify_storage_client/py.typed +0 -0
  19. apify/log.py +22 -105
  20. apify/scrapy/__init__.py +11 -3
  21. apify/scrapy/middlewares/__init__.py +3 -1
  22. apify/scrapy/middlewares/apify_proxy.py +29 -27
  23. apify/scrapy/middlewares/py.typed +0 -0
  24. apify/scrapy/pipelines/__init__.py +3 -1
  25. apify/scrapy/pipelines/actor_dataset_push.py +6 -3
  26. apify/scrapy/pipelines/py.typed +0 -0
  27. apify/scrapy/py.typed +0 -0
  28. apify/scrapy/requests.py +60 -58
  29. apify/scrapy/scheduler.py +28 -19
  30. apify/scrapy/utils.py +10 -32
  31. apify/storages/__init__.py +4 -10
  32. apify/storages/_request_list.py +150 -0
  33. apify/storages/py.typed +0 -0
  34. apify-2.2.1.dist-info/METADATA +211 -0
  35. apify-2.2.1.dist-info/RECORD +38 -0
  36. {apify-1.7.1b1.dist-info → apify-2.2.1.dist-info}/WHEEL +1 -2
  37. apify/_memory_storage/__init__.py +0 -3
  38. apify/_memory_storage/file_storage_utils.py +0 -71
  39. apify/_memory_storage/memory_storage_client.py +0 -219
  40. apify/_memory_storage/resource_clients/__init__.py +0 -19
  41. apify/_memory_storage/resource_clients/base_resource_client.py +0 -141
  42. apify/_memory_storage/resource_clients/base_resource_collection_client.py +0 -114
  43. apify/_memory_storage/resource_clients/dataset.py +0 -452
  44. apify/_memory_storage/resource_clients/dataset_collection.py +0 -48
  45. apify/_memory_storage/resource_clients/key_value_store.py +0 -533
  46. apify/_memory_storage/resource_clients/key_value_store_collection.py +0 -48
  47. apify/_memory_storage/resource_clients/request_queue.py +0 -466
  48. apify/_memory_storage/resource_clients/request_queue_collection.py +0 -48
  49. apify/actor.py +0 -1351
  50. apify/config.py +0 -127
  51. apify/consts.py +0 -67
  52. apify/event_manager.py +0 -236
  53. apify/proxy_configuration.py +0 -365
  54. apify/storages/base_storage.py +0 -181
  55. apify/storages/dataset.py +0 -494
  56. apify/storages/key_value_store.py +0 -257
  57. apify/storages/request_queue.py +0 -602
  58. apify/storages/storage_client_manager.py +0 -72
  59. apify-1.7.1b1.dist-info/METADATA +0 -149
  60. apify-1.7.1b1.dist-info/RECORD +0 -41
  61. apify-1.7.1b1.dist-info/top_level.txt +0 -1
  62. {apify-1.7.1b1.dist-info → apify-2.2.1.dist-info}/LICENSE +0 -0
apify/config.py DELETED
@@ -1,127 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from apify_shared.consts import ActorEnvVars, ApifyEnvVars
4
-
5
- from apify._utils import fetch_and_parse_env_var
6
-
7
-
8
- class Configuration:
9
- """A class for specifying the configuration of an actor.
10
-
11
- Can be used either globally via `Configuration.get_global_configuration()`,
12
- or it can be specific to each `Actor` instance on the `actor.config` property.
13
- """
14
-
15
- _default_instance: Configuration | None = None
16
-
17
- def __init__(
18
- self: Configuration,
19
- *,
20
- api_base_url: str | None = None,
21
- api_public_base_url: str | None = None,
22
- container_port: int | None = None,
23
- container_url: str | None = None,
24
- default_dataset_id: str | None = None,
25
- default_key_value_store_id: str | None = None,
26
- default_request_queue_id: str | None = None,
27
- input_key: str | None = None,
28
- max_used_cpu_ratio: float | None = None,
29
- metamorph_after_sleep_millis: int | None = None,
30
- persist_state_interval_millis: int | None = None,
31
- persist_storage: bool | None = None,
32
- proxy_hostname: str | None = None,
33
- proxy_password: str | None = None,
34
- proxy_port: int | None = None,
35
- proxy_status_url: str | None = None,
36
- purge_on_start: bool | None = None,
37
- token: str | None = None,
38
- system_info_interval_millis: int | None = None,
39
- ) -> None:
40
- """Create a `Configuration` instance.
41
-
42
- All the parameters are loaded by default from environment variables when running on the Apify platform.
43
- You can override them here in the Configuration constructor, which might be useful for local testing of your actors.
44
-
45
- Args:
46
- api_base_url (str, optional): The URL of the Apify API.
47
- This is the URL actually used for connecting to the API, so it can contain an IP address when running in a container on the platform.
48
- api_public_base_url (str, optional): The public URL of the Apify API.
49
- This will always contain the public URL of the API, even when running in a container on the platform.
50
- Useful for generating shareable URLs to key-value store records or datasets.
51
- container_port (int, optional): The port on which the container can listen for HTTP requests.
52
- container_url (str, optional): The URL on which the container can listen for HTTP requests.
53
- default_dataset_id (str, optional): The ID of the default dataset for the actor.
54
- default_key_value_store_id (str, optional): The ID of the default key-value store for the actor.
55
- default_request_queue_id (str, optional): The ID of the default request queue for the actor.
56
- input_key (str, optional): The key of the input record in the actor's default key-value store
57
- max_used_cpu_ratio (float, optional): The CPU usage above which the SYSTEM_INFO event will report the CPU is overloaded.
58
- metamorph_after_sleep_millis (int, optional): How long should the actor sleep after calling metamorph.
59
- persist_state_interval_millis (int, optional): How often should the actor emit the PERSIST_STATE event.
60
- persist_storage (bool, optional): Whether the actor should persist its used storages to the filesystem when running locally.
61
- proxy_hostname (str, optional): The hostname of Apify Proxy.
62
- proxy_password (str, optional): The password for Apify Proxy.
63
- proxy_port (str, optional): The port of Apify Proxy.
64
- proxy_status_url (str, optional): The URL on which the Apify Proxy status page is available.
65
- purge_on_start (str, optional): Whether the actor should purge its default storages on startup, when running locally.
66
- token (str, optional): The API token for the Apify API this actor should use.
67
- system_info_interval_millis (str, optional): How often should the actor emit the SYSTEM_INFO event when running locally.
68
- """
69
- # TODO: Document all these members
70
- # https://github.com/apify/apify-sdk-python/issues/147
71
- self.actor_build_id = fetch_and_parse_env_var(ActorEnvVars.BUILD_ID)
72
- self.actor_build_number = fetch_and_parse_env_var(ActorEnvVars.BUILD_NUMBER)
73
- self.actor_events_ws_url = fetch_and_parse_env_var(ActorEnvVars.EVENTS_WEBSOCKET_URL)
74
- self.actor_id = fetch_and_parse_env_var(ActorEnvVars.ID)
75
- self.actor_run_id = fetch_and_parse_env_var(ActorEnvVars.RUN_ID)
76
- self.actor_task_id = fetch_and_parse_env_var(ActorEnvVars.TASK_ID)
77
- self.api_base_url = api_base_url or fetch_and_parse_env_var(ApifyEnvVars.API_BASE_URL, 'https://api.apify.com')
78
- self.api_public_base_url = api_public_base_url or fetch_and_parse_env_var(ApifyEnvVars.API_PUBLIC_BASE_URL, 'https://api.apify.com')
79
- self.chrome_executable_path = fetch_and_parse_env_var(ApifyEnvVars.CHROME_EXECUTABLE_PATH)
80
- self.container_port = container_port or fetch_and_parse_env_var(ActorEnvVars.WEB_SERVER_PORT, 4321)
81
- self.container_url = container_url or fetch_and_parse_env_var(ActorEnvVars.WEB_SERVER_URL, 'http://localhost:4321')
82
- self.dedicated_cpus = fetch_and_parse_env_var(ApifyEnvVars.DEDICATED_CPUS)
83
- self.default_browser_path = fetch_and_parse_env_var(ApifyEnvVars.DEFAULT_BROWSER_PATH)
84
- self.default_dataset_id = default_dataset_id or fetch_and_parse_env_var(ActorEnvVars.DEFAULT_DATASET_ID, 'default')
85
- self.default_key_value_store_id = default_key_value_store_id or fetch_and_parse_env_var(ActorEnvVars.DEFAULT_KEY_VALUE_STORE_ID, 'default')
86
- self.default_request_queue_id = default_request_queue_id or fetch_and_parse_env_var(ActorEnvVars.DEFAULT_REQUEST_QUEUE_ID, 'default')
87
- self.disable_browser_sandbox = fetch_and_parse_env_var(ApifyEnvVars.DISABLE_BROWSER_SANDBOX, default=False)
88
- self.headless = fetch_and_parse_env_var(ApifyEnvVars.HEADLESS, default=True)
89
- self.input_key = input_key or fetch_and_parse_env_var(ActorEnvVars.INPUT_KEY, 'INPUT')
90
- self.input_secrets_private_key_file = fetch_and_parse_env_var(ApifyEnvVars.INPUT_SECRETS_PRIVATE_KEY_FILE)
91
- self.input_secrets_private_key_passphrase = fetch_and_parse_env_var(ApifyEnvVars.INPUT_SECRETS_PRIVATE_KEY_PASSPHRASE)
92
- self.is_at_home = fetch_and_parse_env_var(ApifyEnvVars.IS_AT_HOME, default=False)
93
- self.max_used_cpu_ratio = max_used_cpu_ratio or fetch_and_parse_env_var(ApifyEnvVars.MAX_USED_CPU_RATIO, 0.95)
94
- self.memory_mbytes = fetch_and_parse_env_var(ActorEnvVars.MEMORY_MBYTES)
95
- self.meta_origin = fetch_and_parse_env_var(ApifyEnvVars.META_ORIGIN)
96
- self.metamorph_after_sleep_millis = metamorph_after_sleep_millis or fetch_and_parse_env_var(ApifyEnvVars.METAMORPH_AFTER_SLEEP_MILLIS, 300000)
97
- self.persist_state_interval_millis = persist_state_interval_millis or fetch_and_parse_env_var(
98
- ApifyEnvVars.PERSIST_STATE_INTERVAL_MILLIS, 60000
99
- )
100
- self.persist_storage = persist_storage or fetch_and_parse_env_var(ApifyEnvVars.PERSIST_STORAGE, default=True)
101
- self.proxy_hostname = proxy_hostname or fetch_and_parse_env_var(ApifyEnvVars.PROXY_HOSTNAME, 'proxy.apify.com')
102
- self.proxy_password = proxy_password or fetch_and_parse_env_var(ApifyEnvVars.PROXY_PASSWORD)
103
- self.proxy_port = proxy_port or fetch_and_parse_env_var(ApifyEnvVars.PROXY_PORT, 8000)
104
- self.proxy_status_url = proxy_status_url or fetch_and_parse_env_var(ApifyEnvVars.PROXY_STATUS_URL, 'http://proxy.apify.com')
105
- self.purge_on_start = purge_on_start or fetch_and_parse_env_var(ApifyEnvVars.PURGE_ON_START, default=False)
106
- self.started_at = fetch_and_parse_env_var(ActorEnvVars.STARTED_AT)
107
- self.timeout_at = fetch_and_parse_env_var(ActorEnvVars.TIMEOUT_AT)
108
- self.token = token or fetch_and_parse_env_var(ApifyEnvVars.TOKEN)
109
- self.user_id = fetch_and_parse_env_var(ApifyEnvVars.USER_ID)
110
- self.xvfb = fetch_and_parse_env_var(ApifyEnvVars.XVFB, default=False)
111
- self.system_info_interval_millis = system_info_interval_millis or fetch_and_parse_env_var(ApifyEnvVars.SYSTEM_INFO_INTERVAL_MILLIS, 60000)
112
-
113
- @classmethod
114
- def _get_default_instance(cls: type[Configuration]) -> Configuration:
115
- if cls._default_instance is None:
116
- cls._default_instance = cls()
117
-
118
- return cls._default_instance
119
-
120
- @classmethod
121
- def get_global_configuration(cls: type[Configuration]) -> Configuration:
122
- """Retrive the global configuration.
123
-
124
- The global configuration applies when you call actor methods via their static versions, e.g. `Actor.init()`.
125
- Also accessible via `Actor.config`.
126
- """
127
- return cls._get_default_instance()
apify/consts.py DELETED
@@ -1,67 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
4
- import warnings
5
- from enum import Enum
6
- from typing import Any
7
-
8
- from apify_shared.consts import BOOL_ENV_VARS as _BOOL_ENV_VARS # noqa: F401
9
- from apify_shared.consts import DATETIME_ENV_VARS as _DATETIME_ENV_VARS # noqa: F401
10
- from apify_shared.consts import FLOAT_ENV_VARS as _FLOAT_ENV_VARS # noqa: F401
11
- from apify_shared.consts import INTEGER_ENV_VARS as _INTEGER_ENV_VARS # noqa: F401
12
- from apify_shared.consts import STRING_ENV_VARS as _STRING_ENV_VARS # noqa: F401
13
- from apify_shared.consts import ActorEventTypes as _ActorEventTypes # noqa: F401
14
- from apify_shared.consts import ActorExitCodes as _ActorExitCodes # noqa: F401
15
- from apify_shared.consts import ApifyEnvVars as _ApifyEnvVars # noqa: F401
16
-
17
- DEPRECATED_NAMES = [
18
- 'BOOL_ENV_VARS',
19
- 'DATETIME_ENV_VARS',
20
- 'FLOAT_ENV_VARS',
21
- 'INTEGER_ENV_VARS',
22
- 'STRING_ENV_VARS',
23
- 'ActorEventTypes',
24
- 'ActorExitCodes',
25
- 'ApifyEnvVars',
26
- ]
27
-
28
-
29
- # The following piece of code is highly inspired by the example in https://peps.python.org/pep-0562.
30
- # The else branch is missing intentionally! Check the following discussion for details:
31
- # https://github.com/apify/apify-client-python/pull/132#discussion_r1277294315.
32
- def __getattr__(name: str) -> Any:
33
- if name in DEPRECATED_NAMES:
34
- warnings.warn(
35
- (
36
- f'Importing "{name}" from "apify_client.consts" is deprecated and will be removed in the future. '
37
- 'Please use "apify_shared" library instead.'
38
- ),
39
- category=DeprecationWarning,
40
- stacklevel=2,
41
- )
42
- return globals()[f'_{name}']
43
- raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
44
-
45
-
46
- class StorageTypes(str, Enum):
47
- """Possible Apify storage types."""
48
-
49
- DATASET = 'Dataset'
50
- KEY_VALUE_STORE = 'Key-value store'
51
- REQUEST_QUEUE = 'Request queue'
52
-
53
-
54
- DEFAULT_API_PARAM_LIMIT = 1000
55
-
56
- REQUEST_ID_LENGTH = 15
57
-
58
- REQUEST_QUEUE_HEAD_MAX_LIMIT = 1000
59
-
60
- EVENT_LISTENERS_TIMEOUT_SECS = 5
61
-
62
- BASE64_REGEXP = '[-A-Za-z0-9+/]*={0,3}'
63
- ENCRYPTED_INPUT_VALUE_PREFIX = 'ENCRYPTED_VALUE'
64
- ENCRYPTED_INPUT_VALUE_REGEXP = re.compile(f'^{ENCRYPTED_INPUT_VALUE_PREFIX}:({BASE64_REGEXP}):({BASE64_REGEXP})$')
65
-
66
- # 9MB
67
- MAX_PAYLOAD_SIZE_BYTES = 9437184
apify/event_manager.py DELETED
@@ -1,236 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import contextlib
5
- import inspect
6
- import json
7
- from collections import defaultdict
8
- from typing import TYPE_CHECKING, Any, Callable, Coroutine, Union
9
-
10
- import websockets.client
11
- from apify_shared.utils import ignore_docs, maybe_extract_enum_member_value, parse_date_fields
12
- from pyee.asyncio import AsyncIOEventEmitter
13
-
14
- from apify.log import logger
15
-
16
- if TYPE_CHECKING:
17
- from apify_shared.consts import ActorEventTypes
18
-
19
- from apify.config import Configuration
20
-
21
- ListenerType = Union[Callable[[], None], Callable[[Any], None], Callable[[], Coroutine[Any, Any, None]], Callable[[Any], Coroutine[Any, Any, None]]]
22
-
23
-
24
- @ignore_docs
25
- class EventManager:
26
- """A class for managing actor events.
27
-
28
- You shouldn't use this class directly,
29
- but instead use it via the `Actor.on()` and `Actor.off()` methods.
30
- """
31
-
32
- _platform_events_websocket: websockets.client.WebSocketClientProtocol | None = None
33
- _process_platform_messages_task: asyncio.Task | None = None
34
- _send_persist_state_interval_task: asyncio.Task | None = None
35
- _send_system_info_interval_task: asyncio.Task | None = None
36
- _listener_tasks: set[asyncio.Task]
37
- _listeners_to_wrappers: dict[ActorEventTypes, dict[Callable, list[Callable]]]
38
- _connected_to_platform_websocket: asyncio.Future | None = None
39
-
40
- def __init__(self: EventManager, config: Configuration) -> None:
41
- """Create an instance of the EventManager.
42
-
43
- Args:
44
- config (Configuration): The actor configuration to be used in this event manager.
45
- """
46
- self._config = config
47
- self._event_emitter = AsyncIOEventEmitter()
48
- self._initialized = False
49
- self._listener_tasks = set()
50
- self._listeners_to_wrappers = defaultdict(lambda: defaultdict(list))
51
-
52
- async def init(self: EventManager) -> None:
53
- """Initialize the event manager.
54
-
55
- When running this on the Apify Platform, this will start processing events
56
- send by the platform to the events websocket and emitting them as events
57
- that can be listened to by the `Actor.on()` method.
58
- """
59
- if self._initialized:
60
- raise RuntimeError('EventManager was already initialized!')
61
-
62
- # Run tasks but don't await them
63
- if self._config.actor_events_ws_url:
64
- self._connected_to_platform_websocket = asyncio.Future()
65
- self._process_platform_messages_task = asyncio.create_task(self._process_platform_messages())
66
- is_connected = await self._connected_to_platform_websocket
67
- if not is_connected:
68
- raise RuntimeError('Error connecting to platform events websocket!')
69
- else:
70
- logger.debug('APIFY_ACTOR_EVENTS_WS_URL env var not set, no events from Apify platform will be emitted.')
71
-
72
- self._initialized = True
73
-
74
- async def close(self: EventManager, event_listeners_timeout_secs: float | None = None) -> None:
75
- """Initialize the event manager.
76
-
77
- This will stop listening for the platform events,
78
- and it will wait for all the event listeners to finish.
79
-
80
- Args:
81
- event_listeners_timeout_secs (float, optional): Optional timeout after which the pending event listeners are canceled.
82
- """
83
- if not self._initialized:
84
- raise RuntimeError('EventManager was not initialized!')
85
-
86
- if self._platform_events_websocket:
87
- await self._platform_events_websocket.close()
88
-
89
- if self._process_platform_messages_task:
90
- await self._process_platform_messages_task
91
-
92
- await self.wait_for_all_listeners_to_complete(timeout_secs=event_listeners_timeout_secs)
93
-
94
- self._event_emitter.remove_all_listeners()
95
-
96
- self._initialized = False
97
-
98
- def on(self: EventManager, event_name: ActorEventTypes, listener: ListenerType) -> Callable:
99
- """Add an event listener to the event manager.
100
-
101
- Args:
102
- event_name (ActorEventTypes): The actor event for which to listen to.
103
- listener (Callable): The function which is to be called when the event is emitted (can be async).
104
- Must accept either zero or one arguments (the first argument will be the event data).
105
- """
106
- if not self._initialized:
107
- raise RuntimeError('EventManager was not initialized!')
108
-
109
- # Detect whether the listener will accept the event_data argument
110
- try:
111
- signature = inspect.signature(listener)
112
- except (ValueError, TypeError):
113
- # If we can't determine the listener argument count (e.g. for the built-in `print` function),
114
- # let's assume the listener will accept the argument
115
- listener_argument_count = 1
116
- else:
117
- try:
118
- dummy_event_data: dict = {}
119
- signature.bind(dummy_event_data)
120
- listener_argument_count = 1
121
- except TypeError:
122
- try:
123
- signature.bind()
124
- listener_argument_count = 0
125
- except TypeError as err:
126
- raise ValueError('The "listener" argument must be a callable which accepts 0 or 1 arguments!') from err
127
-
128
- event_name = maybe_extract_enum_member_value(event_name)
129
-
130
- async def inner_wrapper(event_data: Any) -> None:
131
- if inspect.iscoroutinefunction(listener):
132
- if listener_argument_count == 0:
133
- await listener()
134
- else:
135
- await listener(event_data)
136
- elif listener_argument_count == 0:
137
- listener() # type: ignore[call-arg]
138
- else:
139
- listener(event_data) # type: ignore[call-arg]
140
-
141
- async def outer_wrapper(event_data: Any) -> None:
142
- listener_task = asyncio.create_task(inner_wrapper(event_data))
143
- self._listener_tasks.add(listener_task)
144
- try:
145
- await listener_task
146
- except asyncio.CancelledError:
147
- raise
148
- except Exception:
149
- # We need to swallow the exception and just log it here, since it could break the event emitter otherwise
150
- logger.exception('Exception in event listener', extra={'event_name': event_name, 'listener_name': listener.__name__})
151
- finally:
152
- self._listener_tasks.remove(listener_task)
153
-
154
- self._listeners_to_wrappers[event_name][listener].append(outer_wrapper)
155
-
156
- return self._event_emitter.add_listener(event_name, outer_wrapper)
157
-
158
- def off(self: EventManager, event_name: ActorEventTypes, listener: Callable | None = None) -> None:
159
- """Remove a listener, or all listeners, from an actor event.
160
-
161
- Args:
162
- event_name (ActorEventTypes): The actor event for which to remove listeners.
163
- listener (Callable, optional): The listener which is supposed to be removed. If not passed, all listeners of this event are removed.
164
- """
165
- if not self._initialized:
166
- raise RuntimeError('EventManager was not initialized!')
167
-
168
- event_name = maybe_extract_enum_member_value(event_name)
169
-
170
- if listener:
171
- for listener_wrapper in self._listeners_to_wrappers[event_name][listener]:
172
- self._event_emitter.remove_listener(event_name, listener_wrapper)
173
- self._listeners_to_wrappers[event_name][listener] = []
174
- else:
175
- self._listeners_to_wrappers[event_name] = defaultdict(list)
176
- self._event_emitter.remove_all_listeners(event_name)
177
-
178
- def emit(self: EventManager, event_name: ActorEventTypes, data: Any) -> None:
179
- """Emit an actor event manually.
180
-
181
- Args:
182
- event_name (ActorEventTypes): The actor event which should be emitted.
183
- data (Any): The data that should be emitted with the event.
184
- """
185
- event_name = maybe_extract_enum_member_value(event_name)
186
-
187
- self._event_emitter.emit(event_name, data)
188
-
189
- async def wait_for_all_listeners_to_complete(self: EventManager, *, timeout_secs: float | None = None) -> None:
190
- """Wait for all event listeners which are currently being executed to complete.
191
-
192
- Args:
193
- timeout_secs (float, optional): Timeout for the wait. If the event listeners don't finish until the timeout, they will be canceled.
194
- """
195
-
196
- async def _wait_for_listeners() -> None:
197
- results = await asyncio.gather(*self._listener_tasks, return_exceptions=True)
198
- for result in results:
199
- if result is Exception:
200
- logger.exception('Event manager encountered an exception in one of the event listeners', exc_info=result)
201
-
202
- if timeout_secs:
203
- _, pending = await asyncio.wait([asyncio.create_task(_wait_for_listeners())], timeout=timeout_secs)
204
- if pending:
205
- logger.warning('Timed out waiting for event listeners to complete, unfinished event listeners will be canceled')
206
- for pending_task in pending:
207
- pending_task.cancel()
208
- with contextlib.suppress(asyncio.CancelledError):
209
- await pending_task
210
- else:
211
- await _wait_for_listeners()
212
-
213
- async def _process_platform_messages(self: EventManager) -> None:
214
- # This should be called only on the platform, where we have the ACTOR_EVENTS_WS_URL configured
215
- assert self._config.actor_events_ws_url is not None # noqa: S101
216
- assert self._connected_to_platform_websocket is not None # noqa: S101
217
-
218
- try:
219
- async with websockets.client.connect(self._config.actor_events_ws_url) as websocket:
220
- self._platform_events_websocket = websocket
221
- self._connected_to_platform_websocket.set_result(True)
222
- async for message in websocket:
223
- try:
224
- parsed_message = json.loads(message)
225
- assert isinstance(parsed_message, dict) # noqa: S101
226
- parsed_message = parse_date_fields(parsed_message)
227
- event_name = parsed_message['name']
228
- event_data = parsed_message.get('data') # 'data' can be missing
229
-
230
- self._event_emitter.emit(event_name, event_data)
231
-
232
- except Exception:
233
- logger.exception('Cannot parse actor event', extra={'message': message})
234
- except Exception:
235
- logger.exception('Error in websocket connection')
236
- self._connected_to_platform_websocket.set_result(False)