prefect-client 3.2.7__py3-none-any.whl → 3.2.8__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.
- prefect/_build_info.py +3 -3
- prefect/_experimental/bundles.py +73 -0
- prefect/_waiters.py +254 -0
- prefect/client/subscriptions.py +2 -1
- prefect/events/clients.py +19 -17
- prefect/flow_runs.py +67 -35
- prefect/flows.py +3 -1
- prefect/futures.py +192 -22
- prefect/runner/runner.py +95 -34
- prefect/server/api/artifacts.py +5 -0
- prefect/server/api/automations.py +5 -0
- prefect/server/api/block_capabilities.py +5 -0
- prefect/server/api/block_documents.py +2 -0
- prefect/server/api/block_schemas.py +5 -0
- prefect/server/api/block_types.py +3 -1
- prefect/server/api/concurrency_limits.py +5 -0
- prefect/server/api/concurrency_limits_v2.py +5 -0
- prefect/server/api/deployments.py +2 -0
- prefect/server/api/events.py +5 -1
- prefect/server/api/flow_run_notification_policies.py +2 -0
- prefect/server/api/flow_run_states.py +2 -0
- prefect/server/api/flow_runs.py +2 -0
- prefect/server/api/flows.py +2 -0
- prefect/server/api/logs.py +5 -1
- prefect/server/api/task_run_states.py +2 -0
- prefect/server/api/task_runs.py +2 -0
- prefect/server/api/task_workers.py +5 -1
- prefect/server/api/variables.py +5 -0
- prefect/server/api/work_queues.py +2 -0
- prefect/server/api/workers.py +4 -0
- prefect/settings/profiles.py +6 -5
- prefect/task_worker.py +3 -3
- prefect/telemetry/instrumentation.py +2 -2
- prefect/utilities/templating.py +50 -11
- prefect/workers/base.py +3 -3
- prefect/workers/process.py +22 -319
- {prefect_client-3.2.7.dist-info → prefect_client-3.2.8.dist-info}/METADATA +2 -2
- {prefect_client-3.2.7.dist-info → prefect_client-3.2.8.dist-info}/RECORD +40 -39
- {prefect_client-3.2.7.dist-info → prefect_client-3.2.8.dist-info}/WHEEL +0 -0
- {prefect_client-3.2.7.dist-info → prefect_client-3.2.8.dist-info}/licenses/LICENSE +0 -0
prefect/_build_info.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Generated by versioningit
|
2
|
-
__version__ = "3.2.
|
3
|
-
__build_date__ = "2025-02-
|
4
|
-
__git_commit__ = "
|
2
|
+
__version__ = "3.2.8"
|
3
|
+
__build_date__ = "2025-02-28 00:11:08.300212+00:00"
|
4
|
+
__git_commit__ = "2c0860d5dcd6156fc3fae75d53f7f48f4bd1a522"
|
5
5
|
__dirty__ = False
|
prefect/_experimental/bundles.py
CHANGED
@@ -6,9 +6,12 @@ import gzip
|
|
6
6
|
import multiprocessing
|
7
7
|
import multiprocessing.context
|
8
8
|
import os
|
9
|
+
import subprocess
|
10
|
+
import sys
|
9
11
|
from typing import Any, TypedDict
|
10
12
|
|
11
13
|
import cloudpickle
|
14
|
+
import uv
|
12
15
|
|
13
16
|
from prefect.client.schemas.objects import FlowRun
|
14
17
|
from prefect.context import SettingsContext, get_settings_context, serialize_context
|
@@ -17,6 +20,7 @@ from prefect.flow_engine import run_flow
|
|
17
20
|
from prefect.flows import Flow
|
18
21
|
from prefect.settings.context import get_current_settings
|
19
22
|
from prefect.settings.models.root import Settings
|
23
|
+
from prefect.utilities.slugify import slugify
|
20
24
|
|
21
25
|
|
22
26
|
class SerializedBundle(TypedDict):
|
@@ -28,6 +32,7 @@ class SerializedBundle(TypedDict):
|
|
28
32
|
function: str
|
29
33
|
context: str
|
30
34
|
flow_run: dict[str, Any]
|
35
|
+
dependencies: str
|
31
36
|
|
32
37
|
|
33
38
|
def _serialize_bundle_object(obj: Any) -> str:
|
@@ -66,6 +71,17 @@ def create_bundle_for_flow_run(
|
|
66
71
|
"function": _serialize_bundle_object(flow),
|
67
72
|
"context": _serialize_bundle_object(context),
|
68
73
|
"flow_run": flow_run.model_dump(mode="json"),
|
74
|
+
"dependencies": subprocess.check_output(
|
75
|
+
[
|
76
|
+
uv.find_uv_bin(),
|
77
|
+
"pip",
|
78
|
+
"freeze",
|
79
|
+
# Exclude editable installs because we won't be able to install them in the execution environment
|
80
|
+
"--exclude-editable",
|
81
|
+
]
|
82
|
+
)
|
83
|
+
.decode()
|
84
|
+
.strip(),
|
69
85
|
}
|
70
86
|
|
71
87
|
|
@@ -129,6 +145,14 @@ def execute_bundle_in_subprocess(
|
|
129
145
|
|
130
146
|
ctx = multiprocessing.get_context("spawn")
|
131
147
|
|
148
|
+
# Install dependencies if necessary
|
149
|
+
if dependencies := bundle.get("dependencies"):
|
150
|
+
subprocess.check_call(
|
151
|
+
[uv.find_uv_bin(), "pip", "install", *dependencies.split("\n")],
|
152
|
+
# Copy the current environment to ensure we install into the correct venv
|
153
|
+
env=os.environ,
|
154
|
+
)
|
155
|
+
|
132
156
|
process = ctx.Process(
|
133
157
|
target=_extract_and_run_flow,
|
134
158
|
kwargs={
|
@@ -141,3 +165,52 @@ def execute_bundle_in_subprocess(
|
|
141
165
|
process.start()
|
142
166
|
|
143
167
|
return process
|
168
|
+
|
169
|
+
|
170
|
+
def convert_step_to_command(step: dict[str, Any], key: str) -> list[str]:
|
171
|
+
"""
|
172
|
+
Converts a bundle upload or execution step to a command.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
step: The step to convert.
|
176
|
+
key: The key to use for the remote file when downloading or uploading.
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
A list of strings representing the command to run the step.
|
180
|
+
"""
|
181
|
+
# Start with uv run
|
182
|
+
command = ["uv", "run"]
|
183
|
+
|
184
|
+
step_keys = list(step.keys())
|
185
|
+
|
186
|
+
if len(step_keys) != 1:
|
187
|
+
raise ValueError("Expected exactly one function in step")
|
188
|
+
|
189
|
+
function_fqn = step_keys[0]
|
190
|
+
function_args = step[function_fqn]
|
191
|
+
|
192
|
+
# Add the `--with` argument to handle dependencies for running the step
|
193
|
+
requires: list[str] | str = function_args.get("requires", [])
|
194
|
+
if isinstance(requires, str):
|
195
|
+
requires = [requires]
|
196
|
+
if requires:
|
197
|
+
command.extend(["--with", ",".join(requires)])
|
198
|
+
|
199
|
+
# Add the `--python` argument to handle the Python version for running the step
|
200
|
+
python_version = sys.version_info
|
201
|
+
command.extend(["--python", f"{python_version.major}.{python_version.minor}"])
|
202
|
+
|
203
|
+
# Add the `-m` argument to defined the function to run
|
204
|
+
command.extend(["-m", function_fqn])
|
205
|
+
|
206
|
+
# Add any arguments with values defined in the step
|
207
|
+
for arg_name, arg_value in function_args.items():
|
208
|
+
if arg_name == "requires":
|
209
|
+
continue
|
210
|
+
|
211
|
+
command.extend([f"--{slugify(arg_name)}", arg_value])
|
212
|
+
|
213
|
+
# Add the `--key` argument to specify the remote file name
|
214
|
+
command.extend(["--key", key])
|
215
|
+
|
216
|
+
return command
|
prefect/_waiters.py
ADDED
@@ -0,0 +1,254 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import atexit
|
5
|
+
import threading
|
6
|
+
import uuid
|
7
|
+
from typing import (
|
8
|
+
TYPE_CHECKING,
|
9
|
+
Callable,
|
10
|
+
)
|
11
|
+
|
12
|
+
import anyio
|
13
|
+
from cachetools import TTLCache
|
14
|
+
from typing_extensions import Self
|
15
|
+
|
16
|
+
from prefect._internal.concurrency.api import create_call, from_async, from_sync
|
17
|
+
from prefect._internal.concurrency.threads import get_global_loop
|
18
|
+
from prefect.client.schemas.objects import (
|
19
|
+
TERMINAL_STATES,
|
20
|
+
)
|
21
|
+
from prefect.events.clients import get_events_subscriber
|
22
|
+
from prefect.events.filters import EventFilter, EventNameFilter
|
23
|
+
from prefect.logging import get_logger
|
24
|
+
|
25
|
+
if TYPE_CHECKING:
|
26
|
+
import logging
|
27
|
+
|
28
|
+
|
29
|
+
class FlowRunWaiter:
|
30
|
+
"""
|
31
|
+
A service used for waiting for a flow run to finish.
|
32
|
+
|
33
|
+
This service listens for flow run events and provides a way to wait for a specific
|
34
|
+
flow run to finish. This is useful for waiting for a flow run to finish before
|
35
|
+
continuing execution.
|
36
|
+
|
37
|
+
The service is a singleton and must be started before use. The service will
|
38
|
+
automatically start when the first instance is created. A single websocket
|
39
|
+
connection is used to listen for flow run events.
|
40
|
+
|
41
|
+
The service can be used to wait for a flow run to finish by calling
|
42
|
+
`FlowRunWaiter.wait_for_flow_run` with the flow run ID to wait for. The method
|
43
|
+
will return when the flow run has finished or the timeout has elapsed.
|
44
|
+
|
45
|
+
The service will automatically stop when the Python process exits or when the
|
46
|
+
global loop thread is stopped.
|
47
|
+
|
48
|
+
Example:
|
49
|
+
```python
|
50
|
+
import asyncio
|
51
|
+
from uuid import uuid4
|
52
|
+
|
53
|
+
from prefect import flow
|
54
|
+
from prefect.flow_engine import run_flow_async
|
55
|
+
from prefect.flow_runs import FlowRunWaiter
|
56
|
+
|
57
|
+
|
58
|
+
@flow
|
59
|
+
async def test_flow():
|
60
|
+
await asyncio.sleep(5)
|
61
|
+
print("Done!")
|
62
|
+
|
63
|
+
|
64
|
+
async def main():
|
65
|
+
flow_run_id = uuid4()
|
66
|
+
asyncio.create_flow(run_flow_async(flow=test_flow, flow_run_id=flow_run_id))
|
67
|
+
|
68
|
+
await FlowRunWaiter.wait_for_flow_run(flow_run_id)
|
69
|
+
print("Flow run finished")
|
70
|
+
|
71
|
+
|
72
|
+
if __name__ == "__main__":
|
73
|
+
asyncio.run(main())
|
74
|
+
```
|
75
|
+
"""
|
76
|
+
|
77
|
+
_instance: Self | None = None
|
78
|
+
_instance_lock = threading.Lock()
|
79
|
+
|
80
|
+
def __init__(self):
|
81
|
+
self.logger: "logging.Logger" = get_logger("FlowRunWaiter")
|
82
|
+
self._consumer_task: asyncio.Task[None] | None = None
|
83
|
+
self._observed_completed_flow_runs: TTLCache[uuid.UUID, bool] = TTLCache(
|
84
|
+
maxsize=10000, ttl=600
|
85
|
+
)
|
86
|
+
self._completion_events: dict[uuid.UUID, asyncio.Event] = {}
|
87
|
+
self._completion_callbacks: dict[uuid.UUID, Callable[[], None]] = {}
|
88
|
+
self._loop: asyncio.AbstractEventLoop | None = None
|
89
|
+
self._observed_completed_flow_runs_lock = threading.Lock()
|
90
|
+
self._completion_events_lock = threading.Lock()
|
91
|
+
self._started = False
|
92
|
+
|
93
|
+
def start(self) -> None:
|
94
|
+
"""
|
95
|
+
Start the FlowRunWaiter service.
|
96
|
+
"""
|
97
|
+
if self._started:
|
98
|
+
return
|
99
|
+
self.logger.debug("Starting FlowRunWaiter")
|
100
|
+
loop_thread = get_global_loop()
|
101
|
+
|
102
|
+
if not asyncio.get_running_loop() == loop_thread.loop:
|
103
|
+
raise RuntimeError("FlowRunWaiter must run on the global loop thread.")
|
104
|
+
|
105
|
+
self._loop = loop_thread.loop
|
106
|
+
if TYPE_CHECKING:
|
107
|
+
assert self._loop is not None
|
108
|
+
|
109
|
+
consumer_started = asyncio.Event()
|
110
|
+
self._consumer_task = self._loop.create_task(
|
111
|
+
self._consume_events(consumer_started)
|
112
|
+
)
|
113
|
+
asyncio.run_coroutine_threadsafe(consumer_started.wait(), self._loop)
|
114
|
+
|
115
|
+
loop_thread.add_shutdown_call(create_call(self.stop))
|
116
|
+
atexit.register(self.stop)
|
117
|
+
self._started = True
|
118
|
+
|
119
|
+
async def _consume_events(self, consumer_started: asyncio.Event):
|
120
|
+
async with get_events_subscriber(
|
121
|
+
filter=EventFilter(
|
122
|
+
event=EventNameFilter(
|
123
|
+
name=[
|
124
|
+
f"prefect.flow-run.{state.name.title()}"
|
125
|
+
for state in TERMINAL_STATES
|
126
|
+
],
|
127
|
+
)
|
128
|
+
)
|
129
|
+
) as subscriber:
|
130
|
+
consumer_started.set()
|
131
|
+
async for event in subscriber:
|
132
|
+
try:
|
133
|
+
self.logger.debug(
|
134
|
+
f"Received event: {event.resource['prefect.resource.id']}"
|
135
|
+
)
|
136
|
+
flow_run_id = uuid.UUID(
|
137
|
+
event.resource["prefect.resource.id"].replace(
|
138
|
+
"prefect.flow-run.", ""
|
139
|
+
)
|
140
|
+
)
|
141
|
+
|
142
|
+
with self._observed_completed_flow_runs_lock:
|
143
|
+
# Cache the flow run ID for a short period of time to avoid
|
144
|
+
# unnecessary waits
|
145
|
+
self._observed_completed_flow_runs[flow_run_id] = True
|
146
|
+
with self._completion_events_lock:
|
147
|
+
# Set the event for the flow run ID if it is in the cache
|
148
|
+
# so the waiter can wake up the waiting coroutine
|
149
|
+
if flow_run_id in self._completion_events:
|
150
|
+
self._completion_events[flow_run_id].set()
|
151
|
+
if flow_run_id in self._completion_callbacks:
|
152
|
+
self._completion_callbacks[flow_run_id]()
|
153
|
+
except Exception as exc:
|
154
|
+
self.logger.error(f"Error processing event: {exc}")
|
155
|
+
|
156
|
+
def stop(self) -> None:
|
157
|
+
"""
|
158
|
+
Stop the FlowRunWaiter service.
|
159
|
+
"""
|
160
|
+
self.logger.debug("Stopping FlowRunWaiter")
|
161
|
+
if self._consumer_task:
|
162
|
+
self._consumer_task.cancel()
|
163
|
+
self._consumer_task = None
|
164
|
+
self.__class__._instance = None
|
165
|
+
self._started = False
|
166
|
+
|
167
|
+
@classmethod
|
168
|
+
async def wait_for_flow_run(
|
169
|
+
cls, flow_run_id: uuid.UUID, timeout: float | None = None
|
170
|
+
) -> None:
|
171
|
+
"""
|
172
|
+
Wait for a flow run to finish.
|
173
|
+
|
174
|
+
Note this relies on a websocket connection to receive events from the server
|
175
|
+
and will not work with an ephemeral server.
|
176
|
+
|
177
|
+
Args:
|
178
|
+
flow_run_id: The ID of the flow run to wait for.
|
179
|
+
timeout: The maximum time to wait for the flow run to
|
180
|
+
finish. Defaults to None.
|
181
|
+
"""
|
182
|
+
instance = cls.instance()
|
183
|
+
with instance._observed_completed_flow_runs_lock:
|
184
|
+
if flow_run_id in instance._observed_completed_flow_runs:
|
185
|
+
return
|
186
|
+
|
187
|
+
# Need to create event in loop thread to ensure it can be set
|
188
|
+
# from the loop thread
|
189
|
+
finished_event = await from_async.wait_for_call_in_loop_thread(
|
190
|
+
create_call(asyncio.Event)
|
191
|
+
)
|
192
|
+
with instance._completion_events_lock:
|
193
|
+
# Cache the event for the flow run ID so the consumer can set it
|
194
|
+
# when the event is received
|
195
|
+
instance._completion_events[flow_run_id] = finished_event
|
196
|
+
|
197
|
+
try:
|
198
|
+
# Now check one more time whether the flow run arrived before we start to
|
199
|
+
# wait on it, in case it came in while we were setting up the event above.
|
200
|
+
with instance._observed_completed_flow_runs_lock:
|
201
|
+
if flow_run_id in instance._observed_completed_flow_runs:
|
202
|
+
return
|
203
|
+
|
204
|
+
with anyio.move_on_after(delay=timeout):
|
205
|
+
await from_async.wait_for_call_in_loop_thread(
|
206
|
+
create_call(finished_event.wait)
|
207
|
+
)
|
208
|
+
finally:
|
209
|
+
with instance._completion_events_lock:
|
210
|
+
# Remove the event from the cache after it has been waited on
|
211
|
+
instance._completion_events.pop(flow_run_id, None)
|
212
|
+
|
213
|
+
@classmethod
|
214
|
+
def add_done_callback(
|
215
|
+
cls, flow_run_id: uuid.UUID, callback: Callable[[], None]
|
216
|
+
) -> None:
|
217
|
+
"""
|
218
|
+
Add a callback to be called when a flow run finishes.
|
219
|
+
|
220
|
+
Args:
|
221
|
+
flow_run_id: The ID of the flow run to wait for.
|
222
|
+
callback: The callback to call when the flow run finishes.
|
223
|
+
"""
|
224
|
+
instance = cls.instance()
|
225
|
+
with instance._observed_completed_flow_runs_lock:
|
226
|
+
if flow_run_id in instance._observed_completed_flow_runs:
|
227
|
+
callback()
|
228
|
+
return
|
229
|
+
|
230
|
+
with instance._completion_events_lock:
|
231
|
+
# Cache the event for the flow run ID so the consumer can set it
|
232
|
+
# when the event is received
|
233
|
+
instance._completion_callbacks[flow_run_id] = callback
|
234
|
+
|
235
|
+
@classmethod
|
236
|
+
def instance(cls) -> Self:
|
237
|
+
"""
|
238
|
+
Get the singleton instance of FlowRunWaiter.
|
239
|
+
"""
|
240
|
+
with cls._instance_lock:
|
241
|
+
if cls._instance is None:
|
242
|
+
cls._instance = cls._new_instance()
|
243
|
+
return cls._instance
|
244
|
+
|
245
|
+
@classmethod
|
246
|
+
def _new_instance(cls):
|
247
|
+
instance = cls()
|
248
|
+
|
249
|
+
if threading.get_ident() == get_global_loop().thread.ident:
|
250
|
+
instance.start()
|
251
|
+
else:
|
252
|
+
from_sync.call_soon_in_loop_thread(create_call(instance.start)).result()
|
253
|
+
|
254
|
+
return instance
|
prefect/client/subscriptions.py
CHANGED
@@ -5,6 +5,7 @@ from typing import Any, Generic, Optional, TypeVar
|
|
5
5
|
|
6
6
|
import orjson
|
7
7
|
import websockets
|
8
|
+
import websockets.asyncio.client
|
8
9
|
import websockets.exceptions
|
9
10
|
from starlette.status import WS_1008_POLICY_VIOLATION
|
10
11
|
from typing_extensions import Self
|
@@ -45,7 +46,7 @@ class Subscription(Generic[S]):
|
|
45
46
|
return self
|
46
47
|
|
47
48
|
@property
|
48
|
-
def websocket(self) -> websockets.
|
49
|
+
def websocket(self) -> websockets.asyncio.client.ClientConnection:
|
49
50
|
if not self._websocket:
|
50
51
|
raise RuntimeError("Subscription is not connected")
|
51
52
|
return self._websocket
|
prefect/events/clients.py
CHANGED
@@ -27,12 +27,12 @@ from prometheus_client import Counter
|
|
27
27
|
from python_socks.async_.asyncio import Proxy
|
28
28
|
from typing_extensions import Self
|
29
29
|
from websockets import Subprotocol
|
30
|
+
from websockets.asyncio.client import ClientConnection, connect
|
30
31
|
from websockets.exceptions import (
|
31
32
|
ConnectionClosed,
|
32
33
|
ConnectionClosedError,
|
33
34
|
ConnectionClosedOK,
|
34
35
|
)
|
35
|
-
from websockets.legacy.client import Connect, WebSocketClientProtocol
|
36
36
|
|
37
37
|
from prefect.events import Event
|
38
38
|
from prefect.logging import get_logger
|
@@ -91,7 +91,7 @@ def events_out_socket_from_api_url(url: str) -> str:
|
|
91
91
|
return http_to_ws(url) + "/events/out"
|
92
92
|
|
93
93
|
|
94
|
-
class WebsocketProxyConnect(
|
94
|
+
class WebsocketProxyConnect(connect):
|
95
95
|
def __init__(self: Self, uri: str, **kwargs: Any):
|
96
96
|
# super() is intentionally deferred to the _proxy_connect method
|
97
97
|
# to allow for the socket to be established first
|
@@ -130,7 +130,7 @@ class WebsocketProxyConnect(Connect):
|
|
130
130
|
ctx.verify_mode = ssl.CERT_NONE
|
131
131
|
self._kwargs.setdefault("ssl", ctx)
|
132
132
|
|
133
|
-
async def _proxy_connect(self: Self) ->
|
133
|
+
async def _proxy_connect(self: Self) -> ClientConnection:
|
134
134
|
if self._proxy:
|
135
135
|
sock = await self._proxy.connect(
|
136
136
|
dest_host=self._host,
|
@@ -142,7 +142,7 @@ class WebsocketProxyConnect(Connect):
|
|
142
142
|
proto = await self.__await_impl__()
|
143
143
|
return proto
|
144
144
|
|
145
|
-
def __await__(self: Self) -> Generator[Any, None,
|
145
|
+
def __await__(self: Self) -> Generator[Any, None, ClientConnection]:
|
146
146
|
return self._proxy_connect().__await__()
|
147
147
|
|
148
148
|
|
@@ -311,7 +311,7 @@ def _get_api_url_and_key(
|
|
311
311
|
class PrefectEventsClient(EventsClient):
|
312
312
|
"""A Prefect Events client that streams events to a Prefect server"""
|
313
313
|
|
314
|
-
_websocket: Optional[
|
314
|
+
_websocket: Optional[ClientConnection]
|
315
315
|
_unconfirmed_events: List[Event]
|
316
316
|
|
317
317
|
def __init__(
|
@@ -403,19 +403,11 @@ class PrefectEventsClient(EventsClient):
|
|
403
403
|
await self.emit(event)
|
404
404
|
logger.debug("Finished resending unconfirmed events.")
|
405
405
|
|
406
|
-
async def _checkpoint(self
|
406
|
+
async def _checkpoint(self) -> None:
|
407
407
|
assert self._websocket
|
408
408
|
|
409
|
-
self._unconfirmed_events.append(event)
|
410
|
-
|
411
409
|
unconfirmed_count = len(self._unconfirmed_events)
|
412
410
|
|
413
|
-
logger.debug(
|
414
|
-
"Added event id=%s to unconfirmed events list. "
|
415
|
-
"There are now %s unconfirmed events.",
|
416
|
-
event.id,
|
417
|
-
unconfirmed_count,
|
418
|
-
)
|
419
411
|
if unconfirmed_count < self._checkpoint_every:
|
420
412
|
return
|
421
413
|
|
@@ -433,6 +425,16 @@ class PrefectEventsClient(EventsClient):
|
|
433
425
|
|
434
426
|
async def _emit(self, event: Event) -> None:
|
435
427
|
self._log_debug("Emitting event id=%s.", event.id)
|
428
|
+
|
429
|
+
self._unconfirmed_events.append(event)
|
430
|
+
|
431
|
+
logger.debug(
|
432
|
+
"Added event id=%s to unconfirmed events list. "
|
433
|
+
"There are now %s unconfirmed events.",
|
434
|
+
event.id,
|
435
|
+
len(self._unconfirmed_events),
|
436
|
+
)
|
437
|
+
|
436
438
|
for i in range(self._reconnection_attempts + 1):
|
437
439
|
self._log_debug("Emit reconnection attempt %s.", i)
|
438
440
|
try:
|
@@ -450,7 +452,7 @@ class PrefectEventsClient(EventsClient):
|
|
450
452
|
self._log_debug("Sending event id=%s.", event.id)
|
451
453
|
await self._websocket.send(event.model_dump_json())
|
452
454
|
self._log_debug("Checkpointing event id=%s.", event.id)
|
453
|
-
await self._checkpoint(
|
455
|
+
await self._checkpoint()
|
454
456
|
|
455
457
|
return
|
456
458
|
except ConnectionClosed:
|
@@ -537,7 +539,7 @@ class PrefectCloudEventsClient(PrefectEventsClient):
|
|
537
539
|
)
|
538
540
|
self._connect = websocket_connect(
|
539
541
|
self._events_socket_url,
|
540
|
-
|
542
|
+
additional_headers={"Authorization": f"bearer {api_key}"},
|
541
543
|
)
|
542
544
|
|
543
545
|
|
@@ -562,7 +564,7 @@ class PrefectEventSubscriber:
|
|
562
564
|
|
563
565
|
"""
|
564
566
|
|
565
|
-
_websocket: Optional[
|
567
|
+
_websocket: Optional[ClientConnection]
|
566
568
|
_filter: "EventFilter"
|
567
569
|
_seen_events: MutableMapping[UUID, bool]
|
568
570
|
|