prefect-client 3.4.7.dev3__py3-none-any.whl → 3.4.7.dev5__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/AGENTS.md +20 -30
- prefect/_build_info.py +3 -3
- prefect/_internal/websockets.py +106 -0
- prefect/client/orchestration/_deployments/client.py +137 -2
- prefect/events/clients.py +2 -76
- prefect/logging/clients.py +347 -0
- prefect/runner/runner.py +1 -1
- prefect/server/api/logs.py +20 -15
- {prefect_client-3.4.7.dev3.dist-info → prefect_client-3.4.7.dev5.dist-info}/METADATA +1 -1
- {prefect_client-3.4.7.dev3.dist-info → prefect_client-3.4.7.dev5.dist-info}/RECORD +12 -10
- {prefect_client-3.4.7.dev3.dist-info → prefect_client-3.4.7.dev5.dist-info}/WHEEL +0 -0
- {prefect_client-3.4.7.dev3.dist-info → prefect_client-3.4.7.dev5.dist-info}/licenses/LICENSE +0 -0
prefect/AGENTS.md
CHANGED
@@ -1,38 +1,28 @@
|
|
1
|
-
# Core Prefect
|
1
|
+
# Core Prefect SDK
|
2
2
|
|
3
|
-
|
3
|
+
The foundation for building and executing workflows with Python.
|
4
4
|
|
5
|
-
##
|
5
|
+
## Key Components
|
6
6
|
|
7
|
-
- **Flows
|
8
|
-
- **
|
9
|
-
- **
|
10
|
-
- **
|
11
|
-
- **
|
7
|
+
- **Flows & Tasks**: Workflow definition with `@flow` and `@task` decorators
|
8
|
+
- **States**: Execution status tracking (Pending, Running, Completed, Failed)
|
9
|
+
- **Context**: Runtime information and dependency injection
|
10
|
+
- **Results**: Task output persistence and retrieval
|
11
|
+
- **Deployments**: Packaging flows for scheduled/triggered execution
|
12
|
+
- **Blocks**: Reusable configuration for external systems
|
12
13
|
|
13
|
-
##
|
14
|
+
## Main Modules
|
14
15
|
|
15
|
-
- `flows.py` - Flow
|
16
|
-
- `tasks.py` - Task definition
|
17
|
-
- `states.py` - State management and transitions
|
18
|
-
- `context.py` - Runtime context and dependency injection
|
16
|
+
- `flows.py` - Flow lifecycle and execution
|
17
|
+
- `tasks.py` - Task definition and dependency resolution
|
19
18
|
- `engine.py` - Core execution engine
|
20
|
-
- `client/` -
|
21
|
-
- `
|
22
|
-
- `
|
23
|
-
- `blocks/` - Reusable configuration and infrastructure components
|
19
|
+
- `client/` - Server/Cloud API communication
|
20
|
+
- `deployments/` - Deployment management
|
21
|
+
- `blocks/` - Infrastructure and storage blocks
|
24
22
|
|
25
|
-
##
|
23
|
+
## SDK-Specific Notes
|
26
24
|
|
27
|
-
|
28
|
-
-
|
29
|
-
-
|
30
|
-
-
|
31
|
-
- Results are persisted and retrievable across runs
|
32
|
-
|
33
|
-
## Development Patterns
|
34
|
-
|
35
|
-
- Prefer composition over inheritance for extensibility
|
36
|
-
- Use dependency injection through context for runtime services
|
37
|
-
- Maintain backward compatibility in public APIs
|
38
|
-
- Task and flow definitions are immutable after creation
|
25
|
+
- Async-first execution model with sync support
|
26
|
+
- Immutable flow/task definitions after creation
|
27
|
+
- State transitions handle retries and caching
|
28
|
+
- Backward compatibility required for public APIs
|
prefect/_build_info.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# Generated by versioningit
|
2
|
-
__version__ = "3.4.7.
|
3
|
-
__build_date__ = "2025-06-
|
4
|
-
__git_commit__ = "
|
2
|
+
__version__ = "3.4.7.dev5"
|
3
|
+
__build_date__ = "2025-06-19 08:09:38.201573+00:00"
|
4
|
+
__git_commit__ = "3636ab85f878dffcfcb3a5f9a67055bd0219e63f"
|
5
5
|
__dirty__ = False
|
@@ -0,0 +1,106 @@
|
|
1
|
+
"""
|
2
|
+
Internal WebSocket proxy utilities for Prefect client connections.
|
3
|
+
|
4
|
+
This module provides shared WebSocket proxy connection logic and SSL configuration
|
5
|
+
to avoid duplication between events and logs clients.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
import ssl
|
10
|
+
from typing import Any, Generator, Optional
|
11
|
+
from urllib.parse import urlparse
|
12
|
+
from urllib.request import proxy_bypass
|
13
|
+
|
14
|
+
import certifi
|
15
|
+
from python_socks.async_.asyncio import Proxy
|
16
|
+
from typing_extensions import Self
|
17
|
+
from websockets.asyncio.client import ClientConnection, connect
|
18
|
+
|
19
|
+
from prefect.settings import get_current_settings
|
20
|
+
|
21
|
+
|
22
|
+
def create_ssl_context_for_websocket(uri: str) -> Optional[ssl.SSLContext]:
|
23
|
+
"""Create SSL context for WebSocket connections based on URI scheme."""
|
24
|
+
u = urlparse(uri)
|
25
|
+
|
26
|
+
if u.scheme != "wss":
|
27
|
+
return None
|
28
|
+
|
29
|
+
if get_current_settings().api.tls_insecure_skip_verify:
|
30
|
+
# Create an unverified context for insecure connections
|
31
|
+
ctx = ssl.create_default_context()
|
32
|
+
ctx.check_hostname = False
|
33
|
+
ctx.verify_mode = ssl.CERT_NONE
|
34
|
+
return ctx
|
35
|
+
else:
|
36
|
+
# Create a verified context with the certificate file
|
37
|
+
cert_file = get_current_settings().api.ssl_cert_file
|
38
|
+
if not cert_file:
|
39
|
+
cert_file = certifi.where()
|
40
|
+
return ssl.create_default_context(cafile=cert_file)
|
41
|
+
|
42
|
+
|
43
|
+
class WebsocketProxyConnect(connect):
|
44
|
+
"""
|
45
|
+
WebSocket connection class with proxy and SSL support.
|
46
|
+
|
47
|
+
Extends the websockets.asyncio.client.connect class to add HTTP/HTTPS
|
48
|
+
proxy support via environment variables, proxy bypass logic, and SSL
|
49
|
+
certificate verification.
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(self: Self, uri: str, **kwargs: Any):
|
53
|
+
# super() is intentionally deferred to the _proxy_connect method
|
54
|
+
# to allow for the socket to be established first
|
55
|
+
|
56
|
+
self.uri = uri
|
57
|
+
self._kwargs = kwargs
|
58
|
+
|
59
|
+
u = urlparse(uri)
|
60
|
+
host = u.hostname
|
61
|
+
|
62
|
+
if not host:
|
63
|
+
raise ValueError(f"Invalid URI {uri}, no hostname found")
|
64
|
+
|
65
|
+
if u.scheme == "ws":
|
66
|
+
port = u.port or 80
|
67
|
+
proxy_url = os.environ.get("HTTP_PROXY")
|
68
|
+
elif u.scheme == "wss":
|
69
|
+
port = u.port or 443
|
70
|
+
proxy_url = os.environ.get("HTTPS_PROXY")
|
71
|
+
kwargs["server_hostname"] = host
|
72
|
+
else:
|
73
|
+
raise ValueError(
|
74
|
+
"Unsupported scheme %s. Expected 'ws' or 'wss'. " % u.scheme
|
75
|
+
)
|
76
|
+
|
77
|
+
self._proxy = (
|
78
|
+
Proxy.from_url(proxy_url) if proxy_url and not proxy_bypass(host) else None
|
79
|
+
)
|
80
|
+
self._host = host
|
81
|
+
self._port = port
|
82
|
+
|
83
|
+
# Configure SSL context for HTTPS connections
|
84
|
+
ssl_context = create_ssl_context_for_websocket(uri)
|
85
|
+
if ssl_context:
|
86
|
+
self._kwargs.setdefault("ssl", ssl_context)
|
87
|
+
|
88
|
+
async def _proxy_connect(self: Self) -> ClientConnection:
|
89
|
+
if self._proxy:
|
90
|
+
sock = await self._proxy.connect(
|
91
|
+
dest_host=self._host,
|
92
|
+
dest_port=self._port,
|
93
|
+
)
|
94
|
+
self._kwargs["sock"] = sock
|
95
|
+
|
96
|
+
super().__init__(self.uri, **self._kwargs)
|
97
|
+
proto = await self.__await_impl__()
|
98
|
+
return proto
|
99
|
+
|
100
|
+
def __await__(self: Self) -> Generator[Any, None, ClientConnection]:
|
101
|
+
return self._proxy_connect().__await__()
|
102
|
+
|
103
|
+
|
104
|
+
def websocket_connect(uri: str, **kwargs: Any) -> WebsocketProxyConnect:
|
105
|
+
"""Create a WebSocket connection with proxy and SSL support."""
|
106
|
+
return WebsocketProxyConnect(uri, **kwargs)
|
@@ -6,6 +6,7 @@ from uuid import UUID
|
|
6
6
|
|
7
7
|
from httpx import HTTPStatusError, RequestError
|
8
8
|
|
9
|
+
from prefect._internal.compatibility.deprecated import deprecated_callable
|
9
10
|
from prefect.client.orchestration.base import BaseAsyncClient, BaseClient
|
10
11
|
from prefect.exceptions import ObjectNotFound
|
11
12
|
|
@@ -167,7 +168,7 @@ class DeploymentClient(BaseClient):
|
|
167
168
|
|
168
169
|
return UUID(deployment_id)
|
169
170
|
|
170
|
-
def
|
171
|
+
def _set_deployment_paused_state(self, deployment_id: UUID, paused: bool) -> None:
|
171
172
|
self.request(
|
172
173
|
"PATCH",
|
173
174
|
"/deployments/{id}",
|
@@ -175,6 +176,72 @@ class DeploymentClient(BaseClient):
|
|
175
176
|
json={"paused": paused},
|
176
177
|
)
|
177
178
|
|
179
|
+
@deprecated_callable(
|
180
|
+
start_date="Jun 2025",
|
181
|
+
help="Use pause_deployment or resume_deployment instead.",
|
182
|
+
)
|
183
|
+
def set_deployment_paused_state(self, deployment_id: UUID, paused: bool) -> None:
|
184
|
+
"""
|
185
|
+
DEPRECATED: Use pause_deployment or resume_deployment instead.
|
186
|
+
|
187
|
+
Set the paused state of a deployment.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
deployment_id: the deployment ID to update
|
191
|
+
paused: whether the deployment should be paused
|
192
|
+
"""
|
193
|
+
self._set_deployment_paused_state(deployment_id, paused)
|
194
|
+
|
195
|
+
def pause_deployment(self, deployment_id: Union[UUID, str]) -> None:
|
196
|
+
"""
|
197
|
+
Pause a deployment by ID.
|
198
|
+
|
199
|
+
Args:
|
200
|
+
deployment_id: The deployment ID of interest (can be a UUID or a string).
|
201
|
+
|
202
|
+
Raises:
|
203
|
+
ObjectNotFound: If request returns 404
|
204
|
+
RequestError: If request fails
|
205
|
+
"""
|
206
|
+
if not isinstance(deployment_id, UUID):
|
207
|
+
try:
|
208
|
+
deployment_id = UUID(deployment_id)
|
209
|
+
except ValueError:
|
210
|
+
raise ValueError(f"Invalid deployment ID: {deployment_id}")
|
211
|
+
|
212
|
+
try:
|
213
|
+
self._set_deployment_paused_state(deployment_id, paused=True)
|
214
|
+
except HTTPStatusError as e:
|
215
|
+
if e.response.status_code == 404:
|
216
|
+
raise ObjectNotFound(http_exc=e) from e
|
217
|
+
else:
|
218
|
+
raise
|
219
|
+
|
220
|
+
def resume_deployment(self, deployment_id: Union[UUID, str]) -> None:
|
221
|
+
"""
|
222
|
+
Resume (unpause) a deployment by ID.
|
223
|
+
|
224
|
+
Args:
|
225
|
+
deployment_id: The deployment ID of interest (can be a UUID or a string).
|
226
|
+
|
227
|
+
Raises:
|
228
|
+
ObjectNotFound: If request returns 404
|
229
|
+
RequestError: If request fails
|
230
|
+
"""
|
231
|
+
if not isinstance(deployment_id, UUID):
|
232
|
+
try:
|
233
|
+
deployment_id = UUID(deployment_id)
|
234
|
+
except ValueError:
|
235
|
+
raise ValueError(f"Invalid deployment ID: {deployment_id}")
|
236
|
+
|
237
|
+
try:
|
238
|
+
self._set_deployment_paused_state(deployment_id, paused=False)
|
239
|
+
except HTTPStatusError as e:
|
240
|
+
if e.response.status_code == 404:
|
241
|
+
raise ObjectNotFound(http_exc=e) from e
|
242
|
+
else:
|
243
|
+
raise
|
244
|
+
|
178
245
|
def update_deployment(
|
179
246
|
self,
|
180
247
|
deployment_id: UUID,
|
@@ -760,7 +827,7 @@ class DeploymentAsyncClient(BaseAsyncClient):
|
|
760
827
|
|
761
828
|
return UUID(deployment_id)
|
762
829
|
|
763
|
-
async def
|
830
|
+
async def _set_deployment_paused_state(
|
764
831
|
self, deployment_id: UUID, paused: bool
|
765
832
|
) -> None:
|
766
833
|
await self.request(
|
@@ -770,6 +837,74 @@ class DeploymentAsyncClient(BaseAsyncClient):
|
|
770
837
|
json={"paused": paused},
|
771
838
|
)
|
772
839
|
|
840
|
+
@deprecated_callable(
|
841
|
+
start_date="Jun 2025",
|
842
|
+
help="Use pause_deployment or resume_deployment instead.",
|
843
|
+
)
|
844
|
+
async def set_deployment_paused_state(
|
845
|
+
self, deployment_id: UUID, paused: bool
|
846
|
+
) -> None:
|
847
|
+
"""
|
848
|
+
DEPRECATED: Use pause_deployment or resume_deployment instead.
|
849
|
+
|
850
|
+
Set the paused state of a deployment.
|
851
|
+
|
852
|
+
Args:
|
853
|
+
deployment_id: the deployment ID to update
|
854
|
+
paused: whether the deployment should be paused
|
855
|
+
"""
|
856
|
+
await self._set_deployment_paused_state(deployment_id, paused)
|
857
|
+
|
858
|
+
async def pause_deployment(self, deployment_id: Union[UUID, str]) -> None:
|
859
|
+
"""
|
860
|
+
Pause a deployment by ID.
|
861
|
+
|
862
|
+
Args:
|
863
|
+
deployment_id: The deployment ID of interest (can be a UUID or a string).
|
864
|
+
|
865
|
+
Raises:
|
866
|
+
ObjectNotFound: If request returns 404
|
867
|
+
RequestError: If request fails
|
868
|
+
"""
|
869
|
+
if not isinstance(deployment_id, UUID):
|
870
|
+
try:
|
871
|
+
deployment_id = UUID(deployment_id)
|
872
|
+
except ValueError:
|
873
|
+
raise ValueError(f"Invalid deployment ID: {deployment_id}")
|
874
|
+
|
875
|
+
try:
|
876
|
+
await self._set_deployment_paused_state(deployment_id, paused=True)
|
877
|
+
except HTTPStatusError as e:
|
878
|
+
if e.response.status_code == 404:
|
879
|
+
raise ObjectNotFound(http_exc=e) from e
|
880
|
+
else:
|
881
|
+
raise
|
882
|
+
|
883
|
+
async def resume_deployment(self, deployment_id: Union[UUID, str]) -> None:
|
884
|
+
"""
|
885
|
+
Resume (unpause) a deployment by ID.
|
886
|
+
|
887
|
+
Args:
|
888
|
+
deployment_id: The deployment ID of interest (can be a UUID or a string).
|
889
|
+
|
890
|
+
Raises:
|
891
|
+
ObjectNotFound: If request returns 404
|
892
|
+
RequestError: If request fails
|
893
|
+
"""
|
894
|
+
if not isinstance(deployment_id, UUID):
|
895
|
+
try:
|
896
|
+
deployment_id = UUID(deployment_id)
|
897
|
+
except ValueError:
|
898
|
+
raise ValueError(f"Invalid deployment ID: {deployment_id}")
|
899
|
+
|
900
|
+
try:
|
901
|
+
await self._set_deployment_paused_state(deployment_id, paused=False)
|
902
|
+
except HTTPStatusError as e:
|
903
|
+
if e.response.status_code == 404:
|
904
|
+
raise ObjectNotFound(http_exc=e) from e
|
905
|
+
else:
|
906
|
+
raise
|
907
|
+
|
773
908
|
async def update_deployment(
|
774
909
|
self,
|
775
910
|
deployment_id: UUID,
|
prefect/events/clients.py
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
import abc
|
2
2
|
import asyncio
|
3
|
-
import os
|
4
|
-
import ssl
|
5
3
|
from datetime import timedelta
|
6
4
|
from types import TracebackType
|
7
5
|
from typing import (
|
@@ -9,7 +7,6 @@ from typing import (
|
|
9
7
|
Any,
|
10
8
|
ClassVar,
|
11
9
|
Dict,
|
12
|
-
Generator,
|
13
10
|
List,
|
14
11
|
MutableMapping,
|
15
12
|
Optional,
|
@@ -17,18 +14,14 @@ from typing import (
|
|
17
14
|
Type,
|
18
15
|
cast,
|
19
16
|
)
|
20
|
-
from urllib.parse import urlparse
|
21
|
-
from urllib.request import proxy_bypass
|
22
17
|
from uuid import UUID
|
23
18
|
|
24
|
-
import certifi
|
25
19
|
import orjson
|
26
20
|
from cachetools import TTLCache
|
27
21
|
from prometheus_client import Counter
|
28
|
-
from python_socks.async_.asyncio import Proxy
|
29
22
|
from typing_extensions import Self
|
30
23
|
from websockets import Subprotocol
|
31
|
-
from websockets.asyncio.client import ClientConnection
|
24
|
+
from websockets.asyncio.client import ClientConnection
|
32
25
|
from websockets.exceptions import (
|
33
26
|
ConnectionClosed,
|
34
27
|
ConnectionClosedError,
|
@@ -36,13 +29,12 @@ from websockets.exceptions import (
|
|
36
29
|
)
|
37
30
|
|
38
31
|
import prefect.types._datetime
|
32
|
+
from prefect._internal.websockets import websocket_connect
|
39
33
|
from prefect.events import Event
|
40
34
|
from prefect.logging import get_logger
|
41
35
|
from prefect.settings import (
|
42
36
|
PREFECT_API_AUTH_STRING,
|
43
37
|
PREFECT_API_KEY,
|
44
|
-
PREFECT_API_SSL_CERT_FILE,
|
45
|
-
PREFECT_API_TLS_INSECURE_SKIP_VERIFY,
|
46
38
|
PREFECT_API_URL,
|
47
39
|
PREFECT_CLOUD_API_URL,
|
48
40
|
PREFECT_DEBUG_MODE,
|
@@ -94,72 +86,6 @@ def events_out_socket_from_api_url(url: str) -> str:
|
|
94
86
|
return http_to_ws(url) + "/events/out"
|
95
87
|
|
96
88
|
|
97
|
-
class WebsocketProxyConnect(connect):
|
98
|
-
def __init__(self: Self, uri: str, **kwargs: Any):
|
99
|
-
# super() is intentionally deferred to the _proxy_connect method
|
100
|
-
# to allow for the socket to be established first
|
101
|
-
|
102
|
-
self.uri = uri
|
103
|
-
self._kwargs = kwargs
|
104
|
-
|
105
|
-
u = urlparse(uri)
|
106
|
-
host = u.hostname
|
107
|
-
|
108
|
-
if not host:
|
109
|
-
raise ValueError(f"Invalid URI {uri}, no hostname found")
|
110
|
-
|
111
|
-
if u.scheme == "ws":
|
112
|
-
port = u.port or 80
|
113
|
-
proxy_url = os.environ.get("HTTP_PROXY")
|
114
|
-
elif u.scheme == "wss":
|
115
|
-
port = u.port or 443
|
116
|
-
proxy_url = os.environ.get("HTTPS_PROXY")
|
117
|
-
kwargs["server_hostname"] = host
|
118
|
-
else:
|
119
|
-
raise ValueError(
|
120
|
-
"Unsupported scheme %s. Expected 'ws' or 'wss'. " % u.scheme
|
121
|
-
)
|
122
|
-
|
123
|
-
self._proxy = (
|
124
|
-
Proxy.from_url(proxy_url) if proxy_url and not proxy_bypass(host) else None
|
125
|
-
)
|
126
|
-
self._host = host
|
127
|
-
self._port = port
|
128
|
-
|
129
|
-
if PREFECT_API_TLS_INSECURE_SKIP_VERIFY and u.scheme == "wss":
|
130
|
-
# Create an unverified context for insecure connections
|
131
|
-
ctx = ssl.create_default_context()
|
132
|
-
ctx.check_hostname = False
|
133
|
-
ctx.verify_mode = ssl.CERT_NONE
|
134
|
-
self._kwargs.setdefault("ssl", ctx)
|
135
|
-
elif u.scheme == "wss":
|
136
|
-
cert_file = PREFECT_API_SSL_CERT_FILE.value()
|
137
|
-
if not cert_file:
|
138
|
-
cert_file = certifi.where()
|
139
|
-
# Create a verified context with the certificate file
|
140
|
-
ctx = ssl.create_default_context(cafile=cert_file)
|
141
|
-
self._kwargs.setdefault("ssl", ctx)
|
142
|
-
|
143
|
-
async def _proxy_connect(self: Self) -> ClientConnection:
|
144
|
-
if self._proxy:
|
145
|
-
sock = await self._proxy.connect(
|
146
|
-
dest_host=self._host,
|
147
|
-
dest_port=self._port,
|
148
|
-
)
|
149
|
-
self._kwargs["sock"] = sock
|
150
|
-
|
151
|
-
super().__init__(self.uri, **self._kwargs)
|
152
|
-
proto = await self.__await_impl__()
|
153
|
-
return proto
|
154
|
-
|
155
|
-
def __await__(self: Self) -> Generator[Any, None, ClientConnection]:
|
156
|
-
return self._proxy_connect().__await__()
|
157
|
-
|
158
|
-
|
159
|
-
def websocket_connect(uri: str, **kwargs: Any) -> WebsocketProxyConnect:
|
160
|
-
return WebsocketProxyConnect(uri, **kwargs)
|
161
|
-
|
162
|
-
|
163
89
|
def get_events_client(
|
164
90
|
reconnection_attempts: int = 10,
|
165
91
|
checkpoint_every: int = 700,
|
@@ -0,0 +1,347 @@
|
|
1
|
+
import asyncio
|
2
|
+
from datetime import timedelta
|
3
|
+
from types import TracebackType
|
4
|
+
from typing import (
|
5
|
+
TYPE_CHECKING,
|
6
|
+
Any,
|
7
|
+
MutableMapping,
|
8
|
+
Optional,
|
9
|
+
Tuple,
|
10
|
+
Type,
|
11
|
+
cast,
|
12
|
+
)
|
13
|
+
from uuid import UUID
|
14
|
+
|
15
|
+
import orjson
|
16
|
+
from cachetools import TTLCache
|
17
|
+
from prometheus_client import Counter
|
18
|
+
from typing_extensions import Self
|
19
|
+
from websockets import Subprotocol
|
20
|
+
from websockets.asyncio.client import ClientConnection
|
21
|
+
from websockets.exceptions import (
|
22
|
+
ConnectionClosed,
|
23
|
+
ConnectionClosedError,
|
24
|
+
ConnectionClosedOK,
|
25
|
+
)
|
26
|
+
|
27
|
+
from prefect._internal.websockets import (
|
28
|
+
create_ssl_context_for_websocket,
|
29
|
+
websocket_connect,
|
30
|
+
)
|
31
|
+
from prefect.client.schemas.objects import Log
|
32
|
+
from prefect.logging import get_logger
|
33
|
+
from prefect.settings import (
|
34
|
+
PREFECT_API_AUTH_STRING,
|
35
|
+
PREFECT_API_KEY,
|
36
|
+
PREFECT_API_URL,
|
37
|
+
PREFECT_CLOUD_API_URL,
|
38
|
+
PREFECT_SERVER_ALLOW_EPHEMERAL_MODE,
|
39
|
+
)
|
40
|
+
from prefect.types._datetime import now
|
41
|
+
|
42
|
+
if TYPE_CHECKING:
|
43
|
+
import logging
|
44
|
+
|
45
|
+
from prefect.client.schemas.filters import LogFilter
|
46
|
+
|
47
|
+
logger: "logging.Logger" = get_logger(__name__)
|
48
|
+
|
49
|
+
LOGS_OBSERVED = Counter(
|
50
|
+
"prefect_logs_observed",
|
51
|
+
"The number of logs observed by Prefect log subscribers",
|
52
|
+
labelnames=["client"],
|
53
|
+
)
|
54
|
+
LOG_WEBSOCKET_CONNECTIONS = Counter(
|
55
|
+
"prefect_log_websocket_connections",
|
56
|
+
(
|
57
|
+
"The number of times Prefect log clients have connected to a log stream, "
|
58
|
+
"broken down by direction (in/out) and connection (initial/reconnect)"
|
59
|
+
),
|
60
|
+
labelnames=["client", "direction", "connection"],
|
61
|
+
)
|
62
|
+
|
63
|
+
SEEN_LOGS_SIZE = 500_000
|
64
|
+
SEEN_LOGS_TTL = 120
|
65
|
+
|
66
|
+
|
67
|
+
def http_to_ws(url: str) -> str:
|
68
|
+
return url.replace("https://", "wss://").replace("http://", "ws://").rstrip("/")
|
69
|
+
|
70
|
+
|
71
|
+
def logs_out_socket_from_api_url(url: str) -> str:
|
72
|
+
return http_to_ws(url) + "/logs/out"
|
73
|
+
|
74
|
+
|
75
|
+
def _get_api_url_and_key(
|
76
|
+
api_url: Optional[str], api_key: Optional[str]
|
77
|
+
) -> Tuple[str, str]:
|
78
|
+
api_url = api_url or PREFECT_API_URL.value()
|
79
|
+
api_key = api_key or PREFECT_API_KEY.value()
|
80
|
+
|
81
|
+
if not api_url or not api_key:
|
82
|
+
raise ValueError(
|
83
|
+
"api_url and api_key must be provided or set in the Prefect configuration"
|
84
|
+
)
|
85
|
+
|
86
|
+
return api_url, api_key
|
87
|
+
|
88
|
+
|
89
|
+
def get_logs_subscriber(
|
90
|
+
filter: Optional["LogFilter"] = None,
|
91
|
+
reconnection_attempts: int = 10,
|
92
|
+
) -> "PrefectLogsSubscriber":
|
93
|
+
"""
|
94
|
+
Get a logs subscriber based on the current Prefect configuration.
|
95
|
+
|
96
|
+
Similar to get_events_subscriber, this automatically detects whether
|
97
|
+
you're using Prefect Cloud or OSS and returns the appropriate subscriber.
|
98
|
+
"""
|
99
|
+
api_url = PREFECT_API_URL.value()
|
100
|
+
|
101
|
+
if isinstance(api_url, str) and api_url.startswith(PREFECT_CLOUD_API_URL.value()):
|
102
|
+
return PrefectCloudLogsSubscriber(
|
103
|
+
filter=filter, reconnection_attempts=reconnection_attempts
|
104
|
+
)
|
105
|
+
elif api_url:
|
106
|
+
return PrefectLogsSubscriber(
|
107
|
+
api_url=api_url,
|
108
|
+
filter=filter,
|
109
|
+
reconnection_attempts=reconnection_attempts,
|
110
|
+
)
|
111
|
+
elif PREFECT_SERVER_ALLOW_EPHEMERAL_MODE:
|
112
|
+
from prefect.server.api.server import SubprocessASGIServer
|
113
|
+
|
114
|
+
server = SubprocessASGIServer()
|
115
|
+
server.start()
|
116
|
+
return PrefectLogsSubscriber(
|
117
|
+
api_url=server.api_url,
|
118
|
+
filter=filter,
|
119
|
+
reconnection_attempts=reconnection_attempts,
|
120
|
+
)
|
121
|
+
else:
|
122
|
+
raise ValueError(
|
123
|
+
"No Prefect API URL provided. Please set PREFECT_API_URL to the address of a running Prefect server."
|
124
|
+
)
|
125
|
+
|
126
|
+
|
127
|
+
class PrefectLogsSubscriber:
|
128
|
+
"""
|
129
|
+
Subscribes to a Prefect logs stream, yielding logs as they occur.
|
130
|
+
|
131
|
+
Example:
|
132
|
+
|
133
|
+
from prefect.logging.clients import PrefectLogsSubscriber
|
134
|
+
from prefect.client.schemas.filters import LogFilter, LogFilterLevel
|
135
|
+
import logging
|
136
|
+
|
137
|
+
filter = LogFilter(level=LogFilterLevel(ge_=logging.INFO))
|
138
|
+
|
139
|
+
async with PrefectLogsSubscriber(filter=filter) as subscriber:
|
140
|
+
async for log in subscriber:
|
141
|
+
print(log.timestamp, log.level, log.message)
|
142
|
+
|
143
|
+
"""
|
144
|
+
|
145
|
+
_websocket: Optional[ClientConnection]
|
146
|
+
_filter: "LogFilter"
|
147
|
+
_seen_logs: MutableMapping[UUID, bool]
|
148
|
+
|
149
|
+
_api_key: Optional[str]
|
150
|
+
_auth_token: Optional[str]
|
151
|
+
|
152
|
+
def __init__(
|
153
|
+
self,
|
154
|
+
api_url: Optional[str] = None,
|
155
|
+
filter: Optional["LogFilter"] = None,
|
156
|
+
reconnection_attempts: int = 10,
|
157
|
+
):
|
158
|
+
"""
|
159
|
+
Args:
|
160
|
+
api_url: The base URL for a Prefect workspace
|
161
|
+
filter: Log filter to apply
|
162
|
+
reconnection_attempts: When the client is disconnected, how many times
|
163
|
+
the client should attempt to reconnect
|
164
|
+
"""
|
165
|
+
self._api_key = None
|
166
|
+
self._auth_token = PREFECT_API_AUTH_STRING.value()
|
167
|
+
|
168
|
+
if not api_url:
|
169
|
+
api_url = cast(str, PREFECT_API_URL.value())
|
170
|
+
|
171
|
+
from prefect.client.schemas.filters import LogFilter
|
172
|
+
|
173
|
+
self._filter = filter or LogFilter() # type: ignore[call-arg]
|
174
|
+
self._seen_logs = TTLCache(maxsize=SEEN_LOGS_SIZE, ttl=SEEN_LOGS_TTL)
|
175
|
+
|
176
|
+
socket_url = logs_out_socket_from_api_url(api_url)
|
177
|
+
|
178
|
+
logger.debug("Connecting to %s", socket_url)
|
179
|
+
|
180
|
+
# Configure SSL context for the connection
|
181
|
+
ssl_context = create_ssl_context_for_websocket(socket_url)
|
182
|
+
connect_kwargs: dict[str, Any] = {"subprotocols": [Subprotocol("prefect")]}
|
183
|
+
if ssl_context:
|
184
|
+
connect_kwargs["ssl"] = ssl_context
|
185
|
+
|
186
|
+
self._connect = websocket_connect(socket_url, **connect_kwargs)
|
187
|
+
self._websocket = None
|
188
|
+
self._reconnection_attempts = reconnection_attempts
|
189
|
+
if self._reconnection_attempts < 0:
|
190
|
+
raise ValueError("reconnection_attempts must be a non-negative integer")
|
191
|
+
|
192
|
+
@property
|
193
|
+
def client_name(self) -> str:
|
194
|
+
return self.__class__.__name__
|
195
|
+
|
196
|
+
async def __aenter__(self) -> Self:
|
197
|
+
# Don't handle any errors in the initial connection, because these are most
|
198
|
+
# likely a permission or configuration issue that should propagate
|
199
|
+
try:
|
200
|
+
await self._reconnect()
|
201
|
+
finally:
|
202
|
+
LOG_WEBSOCKET_CONNECTIONS.labels(self.client_name, "out", "initial").inc()
|
203
|
+
return self
|
204
|
+
|
205
|
+
async def _reconnect(self) -> None:
|
206
|
+
logger.debug("Reconnecting...")
|
207
|
+
if self._websocket:
|
208
|
+
self._websocket = None
|
209
|
+
await self._connect.__aexit__(None, None, None)
|
210
|
+
|
211
|
+
self._websocket = await self._connect.__aenter__()
|
212
|
+
|
213
|
+
# make sure we have actually connected
|
214
|
+
logger.debug(" pinging...")
|
215
|
+
pong = await self._websocket.ping()
|
216
|
+
await pong
|
217
|
+
|
218
|
+
# Send authentication message - logs WebSocket requires auth handshake
|
219
|
+
auth_token = self._api_key or self._auth_token
|
220
|
+
auth_message = {"type": "auth", "token": auth_token}
|
221
|
+
logger.debug(" authenticating...")
|
222
|
+
await self._websocket.send(orjson.dumps(auth_message).decode())
|
223
|
+
|
224
|
+
# Wait for auth response
|
225
|
+
try:
|
226
|
+
message = orjson.loads(await self._websocket.recv())
|
227
|
+
logger.debug(" auth result %s", message)
|
228
|
+
assert message["type"] == "auth_success", message.get("reason", "")
|
229
|
+
except AssertionError as e:
|
230
|
+
raise Exception(
|
231
|
+
"Unable to authenticate to the log stream. Please ensure the "
|
232
|
+
"provided api_key or auth_token you are using is valid for this environment. "
|
233
|
+
f"Reason: {e.args[0]}"
|
234
|
+
)
|
235
|
+
except ConnectionClosedError as e:
|
236
|
+
reason = getattr(e.rcvd, "reason", None)
|
237
|
+
msg = "Unable to authenticate to the log stream. Please ensure the "
|
238
|
+
msg += "provided api_key or auth_token you are using is valid for this environment. "
|
239
|
+
msg += f"Reason: {reason}" if reason else ""
|
240
|
+
raise Exception(msg) from e
|
241
|
+
|
242
|
+
from prefect.client.schemas.filters import LogFilterTimestamp
|
243
|
+
|
244
|
+
current_time = now("UTC")
|
245
|
+
self._filter.timestamp = LogFilterTimestamp(
|
246
|
+
after_=current_time - timedelta(minutes=1), # type: ignore[arg-type]
|
247
|
+
before_=current_time + timedelta(days=365), # type: ignore[arg-type]
|
248
|
+
)
|
249
|
+
|
250
|
+
logger.debug(" filtering logs since %s...", self._filter.timestamp.after_)
|
251
|
+
filter_message = {
|
252
|
+
"type": "filter",
|
253
|
+
"filter": self._filter.model_dump(mode="json"),
|
254
|
+
}
|
255
|
+
await self._websocket.send(orjson.dumps(filter_message).decode())
|
256
|
+
|
257
|
+
async def __aexit__(
|
258
|
+
self,
|
259
|
+
exc_type: Optional[Type[BaseException]],
|
260
|
+
exc_val: Optional[BaseException],
|
261
|
+
exc_tb: Optional[TracebackType],
|
262
|
+
) -> None:
|
263
|
+
self._websocket = None
|
264
|
+
await self._connect.__aexit__(exc_type, exc_val, exc_tb)
|
265
|
+
|
266
|
+
def __aiter__(self) -> Self:
|
267
|
+
return self
|
268
|
+
|
269
|
+
async def __anext__(self) -> Log:
|
270
|
+
assert self._reconnection_attempts >= 0
|
271
|
+
for i in range(self._reconnection_attempts + 1): # pragma: no branch
|
272
|
+
try:
|
273
|
+
# If we're here and the websocket is None, then we've had a failure in a
|
274
|
+
# previous reconnection attempt.
|
275
|
+
#
|
276
|
+
# Otherwise, after the first time through this loop, we're recovering
|
277
|
+
# from a ConnectionClosed, so reconnect now.
|
278
|
+
if not self._websocket or i > 0:
|
279
|
+
try:
|
280
|
+
await self._reconnect()
|
281
|
+
finally:
|
282
|
+
LOG_WEBSOCKET_CONNECTIONS.labels(
|
283
|
+
self.client_name, "out", "reconnect"
|
284
|
+
).inc()
|
285
|
+
assert self._websocket
|
286
|
+
|
287
|
+
while True:
|
288
|
+
message = orjson.loads(await self._websocket.recv())
|
289
|
+
log: Log = Log.model_validate(message["log"])
|
290
|
+
|
291
|
+
if log.id in self._seen_logs:
|
292
|
+
continue
|
293
|
+
self._seen_logs[log.id] = True
|
294
|
+
|
295
|
+
try:
|
296
|
+
return log
|
297
|
+
finally:
|
298
|
+
LOGS_OBSERVED.labels(self.client_name).inc()
|
299
|
+
|
300
|
+
except ConnectionClosedOK:
|
301
|
+
logger.debug('Connection closed with "OK" status')
|
302
|
+
raise StopAsyncIteration
|
303
|
+
except ConnectionClosed:
|
304
|
+
logger.debug(
|
305
|
+
"Connection closed with %s/%s attempts",
|
306
|
+
i + 1,
|
307
|
+
self._reconnection_attempts,
|
308
|
+
)
|
309
|
+
if i == self._reconnection_attempts:
|
310
|
+
# this was our final chance, raise the most recent error
|
311
|
+
raise
|
312
|
+
|
313
|
+
if i > 2:
|
314
|
+
# let the first two attempts happen quickly in case this is just
|
315
|
+
# a standard load balancer timeout, but after that, just take a
|
316
|
+
# beat to let things come back around.
|
317
|
+
await asyncio.sleep(1)
|
318
|
+
raise StopAsyncIteration
|
319
|
+
|
320
|
+
|
321
|
+
class PrefectCloudLogsSubscriber(PrefectLogsSubscriber):
|
322
|
+
"""Logs subscriber for Prefect Cloud"""
|
323
|
+
|
324
|
+
def __init__(
|
325
|
+
self,
|
326
|
+
api_url: Optional[str] = None,
|
327
|
+
api_key: Optional[str] = None,
|
328
|
+
filter: Optional["LogFilter"] = None,
|
329
|
+
reconnection_attempts: int = 10,
|
330
|
+
):
|
331
|
+
"""
|
332
|
+
Args:
|
333
|
+
api_url: The base URL for a Prefect Cloud workspace
|
334
|
+
api_key: The API key of an actor with the see_flows scope
|
335
|
+
filter: Log filter to apply
|
336
|
+
reconnection_attempts: When the client is disconnected, how many times
|
337
|
+
the client should attempt to reconnect
|
338
|
+
"""
|
339
|
+
api_url, api_key = _get_api_url_and_key(api_url, api_key)
|
340
|
+
|
341
|
+
super().__init__(
|
342
|
+
api_url=api_url,
|
343
|
+
filter=filter,
|
344
|
+
reconnection_attempts=reconnection_attempts,
|
345
|
+
)
|
346
|
+
|
347
|
+
self._api_key = api_key
|
prefect/runner/runner.py
CHANGED
@@ -945,7 +945,7 @@ class Runner:
|
|
945
945
|
"""
|
946
946
|
self._logger.info("Pausing all deployments...")
|
947
947
|
for deployment_id in self._deployment_ids:
|
948
|
-
await self._client.
|
948
|
+
await self._client.pause_deployment(deployment_id)
|
949
949
|
self._logger.debug(f"Paused deployment '{deployment_id}'")
|
950
950
|
|
951
951
|
self._logger.info("All deployments have been paused!")
|
prefect/server/api/logs.py
CHANGED
@@ -2,16 +2,20 @@
|
|
2
2
|
Routes for interacting with log objects.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from typing import
|
5
|
+
from typing import Optional, Sequence
|
6
6
|
|
7
7
|
from fastapi import Body, Depends, WebSocket, status
|
8
|
+
from pydantic import TypeAdapter
|
8
9
|
from starlette.status import WS_1002_PROTOCOL_ERROR
|
9
10
|
|
10
11
|
import prefect.server.api.dependencies as dependencies
|
11
12
|
import prefect.server.models as models
|
12
|
-
import prefect.server.schemas as schemas
|
13
13
|
from prefect.server.database import PrefectDBInterface, provide_database_interface
|
14
14
|
from prefect.server.logs import stream
|
15
|
+
from prefect.server.schemas.actions import LogCreate
|
16
|
+
from prefect.server.schemas.core import Log
|
17
|
+
from prefect.server.schemas.filters import LogFilter
|
18
|
+
from prefect.server.schemas.sorting import LogSort
|
15
19
|
from prefect.server.utilities import subscriptions
|
16
20
|
from prefect.server.utilities.server import PrefectRouter
|
17
21
|
|
@@ -20,7 +24,7 @@ router: PrefectRouter = PrefectRouter(prefix="/logs", tags=["Logs"])
|
|
20
24
|
|
21
25
|
@router.post("/", status_code=status.HTTP_201_CREATED)
|
22
26
|
async def create_logs(
|
23
|
-
logs:
|
27
|
+
logs: Sequence[LogCreate],
|
24
28
|
db: PrefectDBInterface = Depends(provide_database_interface),
|
25
29
|
) -> None:
|
26
30
|
"""
|
@@ -33,31 +37,32 @@ async def create_logs(
|
|
33
37
|
await models.logs.create_logs(session=session, logs=batch)
|
34
38
|
|
35
39
|
|
40
|
+
logs_adapter: TypeAdapter[Sequence[Log]] = TypeAdapter(Sequence[Log])
|
41
|
+
|
42
|
+
|
36
43
|
@router.post("/filter")
|
37
44
|
async def read_logs(
|
38
45
|
limit: int = dependencies.LimitBody(),
|
39
46
|
offset: int = Body(0, ge=0),
|
40
|
-
logs:
|
41
|
-
sort:
|
47
|
+
logs: Optional[LogFilter] = None,
|
48
|
+
sort: LogSort = Body(LogSort.TIMESTAMP_ASC),
|
42
49
|
db: PrefectDBInterface = Depends(provide_database_interface),
|
43
|
-
) ->
|
50
|
+
) -> Sequence[Log]:
|
44
51
|
"""
|
45
52
|
Query for logs.
|
46
53
|
"""
|
47
54
|
async with db.session_context() as session:
|
48
|
-
return
|
49
|
-
|
55
|
+
return logs_adapter.validate_python(
|
56
|
+
await models.logs.read_logs(
|
57
|
+
session=session, log_filter=logs, offset=offset, limit=limit, sort=sort
|
58
|
+
)
|
50
59
|
)
|
51
60
|
|
52
61
|
|
53
62
|
@router.websocket("/out")
|
54
|
-
async def stream_logs_out(
|
55
|
-
websocket: WebSocket,
|
56
|
-
) -> None:
|
63
|
+
async def stream_logs_out(websocket: WebSocket) -> None:
|
57
64
|
"""Serve a WebSocket to stream live logs"""
|
58
|
-
websocket = await subscriptions.accept_prefect_socket(
|
59
|
-
websocket,
|
60
|
-
)
|
65
|
+
websocket = await subscriptions.accept_prefect_socket(websocket)
|
61
66
|
if not websocket:
|
62
67
|
return
|
63
68
|
|
@@ -72,7 +77,7 @@ async def stream_logs_out(
|
|
72
77
|
)
|
73
78
|
|
74
79
|
try:
|
75
|
-
filter =
|
80
|
+
filter = LogFilter.model_validate(message["filter"])
|
76
81
|
except Exception as e:
|
77
82
|
return await websocket.close(
|
78
83
|
WS_1002_PROTOCOL_ERROR, reason=f"Invalid filter: {e}"
|
@@ -1,8 +1,8 @@
|
|
1
1
|
prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
|
2
|
-
prefect/AGENTS.md,sha256=
|
2
|
+
prefect/AGENTS.md,sha256=qmCZAuKIF9jQyp5TrW_T8bsM_97-QaiCoQp71A_b2Lg,1008
|
3
3
|
prefect/__init__.py,sha256=iCdcC5ZmeewikCdnPEP6YBAjPNV5dvfxpYCTpw30Hkw,3685
|
4
4
|
prefect/__main__.py,sha256=WFjw3kaYJY6pOTA7WDOgqjsz8zUEUZHCcj3P5wyVa-g,66
|
5
|
-
prefect/_build_info.py,sha256=
|
5
|
+
prefect/_build_info.py,sha256=j5vWqNJWh9LFc4M6XTXV72BgaN0wiTsLUGw4b6gVmco,185
|
6
6
|
prefect/_result_records.py,sha256=S6QmsODkehGVSzbMm6ig022PYbI6gNKz671p_8kBYx4,7789
|
7
7
|
prefect/_versioning.py,sha256=YqR5cxXrY4P6LM1Pmhd8iMo7v_G2KJpGNdsf4EvDFQ0,14132
|
8
8
|
prefect/_waiters.py,sha256=Ia2ITaXdHzevtyWIgJoOg95lrEXQqNEOquHvw3T33UQ,9026
|
@@ -45,6 +45,7 @@ prefect/_internal/integrations.py,sha256=U4cZMDbnilzZSKaMxvzZcSL27a1tzRMjDoTfr2u
|
|
45
45
|
prefect/_internal/pytz.py,sha256=Sy_cD-Hkmo_Yrhx2Jucy7DgTRhvO8ZD0whW1ywbSg_U,13765
|
46
46
|
prefect/_internal/retries.py,sha256=pMHofrTQPDSxbVWclDwXbfhFKaDC6sxe1DkUOWugV6k,3040
|
47
47
|
prefect/_internal/uuid7.py,sha256=yvndhibNDrqnYrG-qUncas4XQp8bKVbmM8XfF7JrjJI,4203
|
48
|
+
prefect/_internal/websockets.py,sha256=CloIdusf2Bbefdit46pT91cVDudeYtztPI-MmqSnuLI,3466
|
48
49
|
prefect/_internal/compatibility/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
49
50
|
prefect/_internal/compatibility/async_dispatch.py,sha256=cUXOqSeseMUaje9oYUzasVPtNttyiHvrqfJl0zK66XI,2949
|
50
51
|
prefect/_internal/compatibility/blocks.py,sha256=SSZXoWVuCMYu1EzjqmTa4lKjDCyxvOFK47XMj6s4hsk,984
|
@@ -106,7 +107,7 @@ prefect/client/orchestration/_blocks_types/client.py,sha256=alA4xD-yp3mycAbzMyRu
|
|
106
107
|
prefect/client/orchestration/_concurrency_limits/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
107
108
|
prefect/client/orchestration/_concurrency_limits/client.py,sha256=r_oyY7hQbgyG1rntwe7WWcsraQHBKhk6MOPFUAHWiVc,23678
|
108
109
|
prefect/client/orchestration/_deployments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
109
|
-
prefect/client/orchestration/_deployments/client.py,sha256=
|
110
|
+
prefect/client/orchestration/_deployments/client.py,sha256=KMA_ldpBiUVqvV2-FSOgARyb3objs8MAAe1uD-RXcaE,48477
|
110
111
|
prefect/client/orchestration/_flow_runs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
111
112
|
prefect/client/orchestration/_flow_runs/client.py,sha256=fjh5J-LG8tsny7BGYEvynbuGuHDAudYHpx-PamL0GYQ,32220
|
112
113
|
prefect/client/orchestration/_flows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -154,7 +155,7 @@ prefect/docker/__init__.py,sha256=z6wdc6UFfiBG2jb9Jk64uCWVM04JKVWeVyDWwuuon8M,52
|
|
154
155
|
prefect/docker/docker_image.py,sha256=bR_pEq5-FDxlwTj8CP_7nwZ_MiGK6KxIi8v7DRjy1Kg,3138
|
155
156
|
prefect/events/__init__.py,sha256=GtKl2bE--pJduTxelH2xy7SadlLJmmis8WR1EYixhuA,2094
|
156
157
|
prefect/events/actions.py,sha256=A7jS8bo4zWGnrt3QfSoQs0uYC1xfKXio3IfU0XtTb5s,9129
|
157
|
-
prefect/events/clients.py,sha256=
|
158
|
+
prefect/events/clients.py,sha256=pvCbvPcehDhaFEJfeu1DzUP6RhBhacKU7L5Z4XPSvIE,25132
|
158
159
|
prefect/events/filters.py,sha256=tnAbA4Z0Npem8Jbin-qqe38K_4a-4YdpU-Oc4u8Y95Q,8697
|
159
160
|
prefect/events/related.py,sha256=CTeexYUmmA93V4gsR33GIFmw-SS-X_ouOpRg-oeq-BU,6672
|
160
161
|
prefect/events/utilities.py,sha256=ww34bTMENCNwcp6RhhgzG0KgXOvKGe0MKmBdSJ8NpZY,3043
|
@@ -182,6 +183,7 @@ prefect/locking/filesystem.py,sha256=PxC9ndDbo59-gBEx9jtKad4T-Jav0srJSM9vYGzvQwE
|
|
182
183
|
prefect/locking/memory.py,sha256=EFQnhAO94jEy4TyS880DbsJ42CHT5WNuNc6Wj8dYrKc,7842
|
183
184
|
prefect/locking/protocol.py,sha256=RsfvlaHTTEJ0YvYWSqFGoZuT2w4FPPxyQlHqjoyNGuE,4240
|
184
185
|
prefect/logging/__init__.py,sha256=DpRZzZeWeiDHFlMDEQdknRzbxpL0ObFh5IqqS9iaZwQ,170
|
186
|
+
prefect/logging/clients.py,sha256=nKEv-Xfzy5QwtFmZvNirBI8G5YraZ9YzuIkKHb4XVXM,11825
|
185
187
|
prefect/logging/configuration.py,sha256=go9lA4W5HMpK6azDz_ez2YqgQ2b3aCFXxJH-AopoHy8,3404
|
186
188
|
prefect/logging/filters.py,sha256=NnRYubh9dMmWcCAjuW32cIVQ37rLxdn8ci26wTtQMyU,1136
|
187
189
|
prefect/logging/formatters.py,sha256=Sum42BmYZ7mns64jSOy4OA_K8KudEZjeG2h7SZcY9mA,4167
|
@@ -191,7 +193,7 @@ prefect/logging/loggers.py,sha256=rwFJv0i3dhdKr25XX-xUkQy4Vv4dy18bTy366jrC0OQ,12
|
|
191
193
|
prefect/logging/logging.yml,sha256=G5hFJ57Vawz40_w8tDdhqq00dp103OvVDVmWrSQeQcQ,3285
|
192
194
|
prefect/runner/__init__.py,sha256=pQBd9wVrUVUDUFJlgiweKSnbahoBZwqnd2O2jkhrULY,158
|
193
195
|
prefect/runner/_observers.py,sha256=PpyXQL5bjp86AnDFEzcFPS5ayL6ExqcYgyuBMMQCO9Q,2183
|
194
|
-
prefect/runner/runner.py,sha256=
|
196
|
+
prefect/runner/runner.py,sha256=bucchmFv7g4lYaxDyPtkRXZgyaBmyRpS23maMUT0AQg,59574
|
195
197
|
prefect/runner/server.py,sha256=5vMIJcgunjiDVzJEig09yOP8EbhcW6s-9zNUt101b44,11994
|
196
198
|
prefect/runner/storage.py,sha256=n-65YoEf7KNVInnmMPeP5TVFJOa2zOS8w9en9MHi6uo,31328
|
197
199
|
prefect/runner/submit.py,sha256=b5n1M12DFQsxo6FazZnDbblRcIE7H3xrpecDMb4CjJY,9512
|
@@ -219,7 +221,7 @@ prefect/server/api/events.py,sha256=mUTv5ZNxiRsEOpzq8fpfCkLpPasjt-ROUAowA5eFbDE,
|
|
219
221
|
prefect/server/api/flow_run_states.py,sha256=lIdxVE9CqLgtDCuH9bTaKkzHNL81FPrr11liPzvONrw,1661
|
220
222
|
prefect/server/api/flow_runs.py,sha256=Lmb165fLbN4DioxjxgDYaAJ5Qxj771iRYaqn-hYq9KM,33744
|
221
223
|
prefect/server/api/flows.py,sha256=Bz0ISh-9oY0W1X3mqA631_8678pQ6tuRGMpSgWAfxOc,7018
|
222
|
-
prefect/server/api/logs.py,sha256=
|
224
|
+
prefect/server/api/logs.py,sha256=O0W9jomHQuWF7XPMPOhW2p5Uidss6ssMqmKKwMGiv7Y,3526
|
223
225
|
prefect/server/api/middleware.py,sha256=WkyuyeJIfo9Q0GAIVU5gO6yIGNVwoHwuBah5AB5oUyw,2733
|
224
226
|
prefect/server/api/root.py,sha256=CeumFYIM_BDvPicJH9ry5PO_02PZTLeMqbLMGGTh90o,942
|
225
227
|
prefect/server/api/run_history.py,sha256=EW-GTPxZAQ5zXiAqHzmS-iAN_Bn6ZSgVQksDT-ZTsyc,5995
|
@@ -327,7 +329,7 @@ prefect/workers/cloud.py,sha256=dPvG1jDGD5HSH7aM2utwtk6RaJ9qg13XjkA0lAIgQmY,287
|
|
327
329
|
prefect/workers/process.py,sha256=Yi5D0U5AQ51wHT86GdwtImXSefe0gJf3LGq4r4z9zwM,11090
|
328
330
|
prefect/workers/server.py,sha256=2pmVeJZiVbEK02SO6BEZaBIvHMsn6G8LzjW8BXyiTtk,1952
|
329
331
|
prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
|
330
|
-
prefect_client-3.4.7.
|
331
|
-
prefect_client-3.4.7.
|
332
|
-
prefect_client-3.4.7.
|
333
|
-
prefect_client-3.4.7.
|
332
|
+
prefect_client-3.4.7.dev5.dist-info/METADATA,sha256=2MOLNYyWyvWP14kqrkU6eHZOXb2soCwenx0Oy3ie_zI,7517
|
333
|
+
prefect_client-3.4.7.dev5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
334
|
+
prefect_client-3.4.7.dev5.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
|
335
|
+
prefect_client-3.4.7.dev5.dist-info/RECORD,,
|
File without changes
|
{prefect_client-3.4.7.dev3.dist-info → prefect_client-3.4.7.dev5.dist-info}/licenses/LICENSE
RENAMED
File without changes
|