prefect-client 2.16.4__py3-none-any.whl → 2.16.5__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.
@@ -2,6 +2,7 @@
2
2
  Implementation of the `Call` data structure for transport of deferred function calls
3
3
  and low-level management of call execution.
4
4
  """
5
+
5
6
  import abc
6
7
  import asyncio
7
8
  import concurrent.futures
@@ -40,8 +41,8 @@ P = ParamSpec("P")
40
41
  # we already have strong references to the `Call` objects in other places
41
42
  # and b) this is used for performance optimizations where we have fallback
42
43
  # behavior if this weakref is garbage collected. A fix for issue #10952.
43
- current_call: contextvars.ContextVar["weakref.ref[Call]"] = contextvars.ContextVar(
44
- "current_call"
44
+ current_call: contextvars.ContextVar["weakref.ref[Call]"] = ( # novm
45
+ contextvars.ContextVar("current_call")
45
46
  )
46
47
 
47
48
  # Create a strong reference to tasks to prevent destruction during execution errors
@@ -9,3 +9,5 @@
9
9
  from pydantic.version import VERSION as PYDANTIC_VERSION
10
10
 
11
11
  HAS_PYDANTIC_V2 = PYDANTIC_VERSION.startswith("2.")
12
+
13
+ from ._compat import model_dump, IncEx, model_json_schema
@@ -0,0 +1,180 @@
1
+ from typing import Any, Dict, Literal, Optional, Set, Type, Union
2
+
3
+ import typing_extensions
4
+ from pydantic import BaseModel
5
+
6
+ from prefect._internal.pydantic import HAS_PYDANTIC_V2
7
+ from prefect.logging.loggers import get_logger
8
+ from prefect.settings import PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS
9
+
10
+ IncEx: typing_extensions.TypeAlias = (
11
+ "Union[Set[int], Set[str], Dict[int, Any], Dict[str, Any], None]"
12
+ )
13
+
14
+ logger = get_logger("prefect._internal.pydantic")
15
+
16
+ if HAS_PYDANTIC_V2:
17
+ from pydantic.json_schema import GenerateJsonSchema
18
+
19
+
20
+ def is_pydantic_v2_compatible(
21
+ model_instance: Optional[BaseModel] = None, fn_name: Optional[str] = None
22
+ ) -> bool:
23
+ """
24
+ Determines if the current environment is compatible with Pydantic V2 features,
25
+ based on the presence of Pydantic V2 and a global setting that enables V2 functionalities.
26
+
27
+ This function primarily serves to facilitate conditional logic in code that needs to
28
+ operate differently depending on the availability of Pydantic V2 features. It checks
29
+ two conditions: whether Pydantic V2 is installed, and whether the use of V2 features
30
+ is explicitly enabled through a global setting (`PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS`).
31
+
32
+ Parameters:
33
+ -----------
34
+ model_instance : Optional[BaseModel], optional
35
+ An instance of a Pydantic model. This parameter is used to perform a type check
36
+ to ensure the passed object is a Pydantic model instance. If not provided or if
37
+ the object is not a Pydantic model, a TypeError is raised. Defaults to None.
38
+
39
+ fn_name : Optional[str], optional
40
+ The name of the function or feature for which V2 compatibility is being checked.
41
+ This is used for logging purposes to provide more context in debug messages.
42
+ Defaults to None.
43
+
44
+ Returns:
45
+ --------
46
+ bool
47
+ True if the current environment supports Pydantic V2 features and if the global
48
+ setting for enabling V2 features is set to True. False otherwise.
49
+
50
+ Raises:
51
+ -------
52
+ TypeError
53
+ If `model_instance` is provided but is not an instance of a Pydantic BaseModel.
54
+ """
55
+ if model_instance and not isinstance(model_instance, BaseModel):
56
+ raise TypeError(
57
+ f"Expected a Pydantic model, but got {type(model_instance).__name__}"
58
+ )
59
+
60
+ should_dump_as_v2_model = (
61
+ HAS_PYDANTIC_V2 and PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS
62
+ )
63
+
64
+ if should_dump_as_v2_model:
65
+ logger.debug(
66
+ f"Using Pydantic v2 compatibility layer for `{fn_name}`. This will be removed in a future release."
67
+ )
68
+
69
+ return True
70
+
71
+ elif HAS_PYDANTIC_V2:
72
+ logger.debug(
73
+ "Pydantic v2 compatibility layer is disabled. To enable, set `PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS` to `True`."
74
+ )
75
+
76
+ else:
77
+ logger.debug("Pydantic v2 is not installed.")
78
+
79
+ return False
80
+
81
+
82
+ def model_dump(
83
+ model_instance: BaseModel,
84
+ *,
85
+ mode: Union[Literal["json", "python"], str] = "python",
86
+ include: IncEx = None,
87
+ exclude: IncEx = None,
88
+ by_alias: bool = False,
89
+ exclude_unset: bool = False,
90
+ exclude_defaults: bool = False,
91
+ exclude_none: bool = False,
92
+ round_trip: bool = False,
93
+ warnings: bool = True,
94
+ ) -> Dict[str, Any]:
95
+ """
96
+ Generate a dictionary representation of the model, optionally specifying which fields to include or exclude.
97
+
98
+ Args:
99
+ mode: The mode in which `to_python` should run.
100
+ If mode is 'json', the output will only contain JSON serializable types.
101
+ If mode is 'python', the output may contain non-JSON-serializable Python objects.
102
+ include: A list of fields to include in the output.
103
+ exclude: A list of fields to exclude from the output.
104
+ by_alias: Whether to use the field's alias in the dictionary key if defined.
105
+ exclude_unset: Whether to exclude fields that have not been explicitly set.
106
+ exclude_defaults: Whether to exclude fields that are set to their default value.
107
+ exclude_none: Whether to exclude fields that have a value of `None`.
108
+ round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
109
+ warnings: Whether to log warnings when invalid fields are encountered.
110
+
111
+ Returns:
112
+ A dictionary representation of the model.
113
+ """
114
+ if is_pydantic_v2_compatible(model_instance=model_instance, fn_name="model_dump"):
115
+ return model_instance.model_dump(
116
+ mode=mode,
117
+ include=include,
118
+ exclude=exclude,
119
+ by_alias=by_alias,
120
+ exclude_unset=exclude_unset,
121
+ exclude_defaults=exclude_defaults,
122
+ exclude_none=exclude_none,
123
+ round_trip=round_trip,
124
+ warnings=warnings,
125
+ )
126
+
127
+ return model_instance.dict(
128
+ include=include,
129
+ exclude=exclude,
130
+ by_alias=by_alias,
131
+ exclude_unset=exclude_unset,
132
+ exclude_defaults=exclude_defaults,
133
+ exclude_none=exclude_none,
134
+ )
135
+
136
+
137
+ DEFAULT_REF_TEMPLATE = "#/$defs/{model}"
138
+ JsonSchemaMode = Literal["validation", "serialization"]
139
+
140
+
141
+ def model_json_schema(
142
+ model: Type[BaseModel],
143
+ *,
144
+ by_alias: bool = True,
145
+ ref_template: str = DEFAULT_REF_TEMPLATE,
146
+ schema_generator=None,
147
+ mode: JsonSchemaMode = "validation",
148
+ ) -> Dict[str, Any]:
149
+ """
150
+ Generates a JSON schema for a model class.
151
+
152
+ Parameters
153
+ ----------
154
+ by_alias : bool, optional
155
+ Whether to use attribute aliases or not, by default True
156
+ ref_template : str, optional
157
+ The reference template, by default DEFAULT_REF_TEMPLATE
158
+ schema_generator : type[GenerateEmptySchemaForUserClasses], optional
159
+ To override the logic used to generate the JSON schema, as a subclass of GenerateEmptySchemaForUserClasses with your desired modifications, by default GenerateEmptySchemaForUserClasses
160
+ mode : JsonSchemaMode, optional
161
+ The mode in which to generate the schema, by default 'validation'
162
+
163
+ Returns
164
+ -------
165
+ dict[str, Any]
166
+ The JSON schema for the given model class.
167
+ """
168
+ if is_pydantic_v2_compatible(fn_name="model_json_schema"):
169
+ schema_generator = GenerateJsonSchema
170
+ return model.model_json_schema(
171
+ by_alias=by_alias,
172
+ ref_template=ref_template,
173
+ schema_generator=schema_generator,
174
+ mode=mode,
175
+ )
176
+
177
+ return model.schema(
178
+ by_alias=by_alias,
179
+ ref_template=ref_template,
180
+ )
prefect/artifacts.py ADDED
@@ -0,0 +1,185 @@
1
+ """
2
+ Interface for creating and reading artifacts.
3
+ """
4
+
5
+ import json
6
+ import math
7
+ from typing import Any, Dict, List, Optional, Union
8
+ from uuid import UUID
9
+
10
+ from prefect.client.orchestration import PrefectClient
11
+ from prefect.client.schemas.actions import ArtifactCreate
12
+ from prefect.client.utilities import inject_client
13
+ from prefect.context import FlowRunContext, TaskRunContext
14
+ from prefect.utilities.asyncutils import sync_compatible
15
+
16
+ INVALID_TABLE_TYPE_ERROR = (
17
+ "`create_table_artifact` requires a `table` argument of type `dict[list]` or"
18
+ " `list[dict]`."
19
+ )
20
+
21
+
22
+ @inject_client
23
+ async def _create_artifact(
24
+ type: str,
25
+ key: Optional[str] = None,
26
+ description: Optional[str] = None,
27
+ data: Optional[Union[Dict[str, Any], Any]] = None,
28
+ client: Optional[PrefectClient] = None,
29
+ ) -> UUID:
30
+ """
31
+ Helper function to create an artifact.
32
+
33
+ Arguments:
34
+ type: A string identifying the type of artifact.
35
+ key: A user-provided string identifier.
36
+ The key must only contain lowercase letters, numbers, and dashes.
37
+ description: A user-specified description of the artifact.
38
+ data: A JSON payload that allows for a result to be retrieved.
39
+ client: The PrefectClient
40
+
41
+ Returns:
42
+ - The table artifact ID.
43
+ """
44
+ artifact_args = {}
45
+ task_run_ctx = TaskRunContext.get()
46
+ flow_run_ctx = FlowRunContext.get()
47
+
48
+ if task_run_ctx:
49
+ artifact_args["task_run_id"] = task_run_ctx.task_run.id
50
+ artifact_args["flow_run_id"] = task_run_ctx.task_run.flow_run_id
51
+ elif flow_run_ctx:
52
+ artifact_args["flow_run_id"] = flow_run_ctx.flow_run.id
53
+
54
+ if key is not None:
55
+ artifact_args["key"] = key
56
+ if type is not None:
57
+ artifact_args["type"] = type
58
+ if description is not None:
59
+ artifact_args["description"] = description
60
+ if data is not None:
61
+ artifact_args["data"] = data
62
+
63
+ artifact = ArtifactCreate(**artifact_args)
64
+
65
+ return await client.create_artifact(artifact=artifact)
66
+
67
+
68
+ @sync_compatible
69
+ async def create_link_artifact(
70
+ link: str,
71
+ link_text: Optional[str] = None,
72
+ key: Optional[str] = None,
73
+ description: Optional[str] = None,
74
+ ) -> UUID:
75
+ """
76
+ Create a link artifact.
77
+
78
+ Arguments:
79
+ link: The link to create.
80
+ link_text: The link text.
81
+ key: A user-provided string identifier.
82
+ Required for the artifact to show in the Artifacts page in the UI.
83
+ The key must only contain lowercase letters, numbers, and dashes.
84
+ description: A user-specified description of the artifact.
85
+
86
+
87
+ Returns:
88
+ The table artifact ID.
89
+ """
90
+ formatted_link = f"[{link_text}]({link})" if link_text else f"[{link}]({link})"
91
+ artifact = await _create_artifact(
92
+ key=key,
93
+ type="markdown",
94
+ description=description,
95
+ data=formatted_link,
96
+ )
97
+
98
+ return artifact.id
99
+
100
+
101
+ @sync_compatible
102
+ async def create_markdown_artifact(
103
+ markdown: str,
104
+ key: Optional[str] = None,
105
+ description: Optional[str] = None,
106
+ ) -> UUID:
107
+ """
108
+ Create a markdown artifact.
109
+
110
+ Arguments:
111
+ markdown: The markdown to create.
112
+ key: A user-provided string identifier.
113
+ Required for the artifact to show in the Artifacts page in the UI.
114
+ The key must only contain lowercase letters, numbers, and dashes.
115
+ description: A user-specified description of the artifact.
116
+
117
+ Returns:
118
+ The table artifact ID.
119
+ """
120
+ artifact = await _create_artifact(
121
+ key=key,
122
+ type="markdown",
123
+ description=description,
124
+ data=markdown,
125
+ )
126
+
127
+ return artifact.id
128
+
129
+
130
+ @sync_compatible
131
+ async def create_table_artifact(
132
+ table: Union[Dict[str, List[Any]], List[Dict[str, Any]], List[List[Any]]],
133
+ key: Optional[str] = None,
134
+ description: Optional[str] = None,
135
+ ) -> UUID:
136
+ """
137
+ Create a table artifact.
138
+
139
+ Arguments:
140
+ table: The table to create.
141
+ key: A user-provided string identifier.
142
+ Required for the artifact to show in the Artifacts page in the UI.
143
+ The key must only contain lowercase letters, numbers, and dashes.
144
+ description: A user-specified description of the artifact.
145
+
146
+ Returns:
147
+ The table artifact ID.
148
+ """
149
+
150
+ def _sanitize_nan_values(item):
151
+ """
152
+ Sanitize NaN values in a given item. The item can be a dict, list or float.
153
+ """
154
+
155
+ if isinstance(item, list):
156
+ return [_sanitize_nan_values(sub_item) for sub_item in item]
157
+
158
+ elif isinstance(item, dict):
159
+ return {k: _sanitize_nan_values(v) for k, v in item.items()}
160
+
161
+ elif isinstance(item, float) and math.isnan(item):
162
+ return None
163
+
164
+ else:
165
+ return item
166
+
167
+ sanitized_table = _sanitize_nan_values(table)
168
+
169
+ if isinstance(table, dict) and all(isinstance(v, list) for v in table.values()):
170
+ pass
171
+ elif isinstance(table, list) and all(isinstance(v, (list, dict)) for v in table):
172
+ pass
173
+ else:
174
+ raise TypeError(INVALID_TABLE_TYPE_ERROR)
175
+
176
+ formatted_table = json.dumps(sanitized_table)
177
+
178
+ artifact = await _create_artifact(
179
+ key=key,
180
+ type="table",
181
+ description=description,
182
+ data=formatted_table,
183
+ )
184
+
185
+ return artifact.id
prefect/client/base.py CHANGED
@@ -1,9 +1,10 @@
1
1
  import copy
2
2
  import sys
3
3
  import threading
4
+ import uuid
4
5
  from collections import defaultdict
5
6
  from contextlib import asynccontextmanager
6
- from functools import partial
7
+ from datetime import datetime, timezone
7
8
  from typing import (
8
9
  Any,
9
10
  AsyncGenerator,
@@ -11,6 +12,7 @@ from typing import (
11
12
  Callable,
12
13
  Dict,
13
14
  MutableMapping,
15
+ Optional,
14
16
  Protocol,
15
17
  Set,
16
18
  Tuple,
@@ -21,10 +23,11 @@ from typing import (
21
23
  import anyio
22
24
  import httpx
23
25
  from asgi_lifespan import LifespanManager
24
- from httpx import HTTPStatusError, Response
26
+ from httpx import HTTPStatusError, Request, Response
25
27
  from prefect._vendor.starlette import status
26
28
  from typing_extensions import Self
27
29
 
30
+ from prefect.client.schemas.objects import CsrfToken
28
31
  from prefect.exceptions import PrefectHTTPStatusError
29
32
  from prefect.logging import get_logger
30
33
  from prefect.settings import (
@@ -188,9 +191,20 @@ class PrefectHttpxClient(httpx.AsyncClient):
188
191
  [Configuring Cloudflare Rate Limiting](https://support.cloudflare.com/hc/en-us/articles/115001635128-Configuring-Rate-Limiting-from-UI)
189
192
  """
190
193
 
194
+ def __init__(self, *args, enable_csrf_support: bool = False, **kwargs):
195
+ self.enable_csrf_support: bool = enable_csrf_support
196
+ self.csrf_token: Optional[str] = None
197
+ self.csrf_token_expiration: Optional[datetime] = None
198
+ self.csrf_client_id: uuid.UUID = uuid.uuid4()
199
+
200
+ super().__init__(*args, **kwargs)
201
+
191
202
  async def _send_with_retry(
192
203
  self,
193
- request: Callable,
204
+ request: Request,
205
+ send: Callable[[Request], Awaitable[Response]],
206
+ send_args: Tuple,
207
+ send_kwargs: Dict,
194
208
  retry_codes: Set[int] = set(),
195
209
  retry_exceptions: Tuple[Exception, ...] = tuple(),
196
210
  ):
@@ -207,21 +221,34 @@ class PrefectHttpxClient(httpx.AsyncClient):
207
221
  try_count = 0
208
222
  response = None
209
223
 
224
+ is_change_request = request.method.lower() in {"post", "put", "patch", "delete"}
225
+
226
+ if self.enable_csrf_support and is_change_request:
227
+ await self._add_csrf_headers(request=request)
228
+
210
229
  while try_count <= PREFECT_CLIENT_MAX_RETRIES.value():
211
230
  try_count += 1
212
231
  retry_seconds = None
213
232
  exc_info = None
214
233
 
215
234
  try:
216
- response = await request()
235
+ response = await send(request, *send_args, **send_kwargs)
217
236
  except retry_exceptions: # type: ignore
218
237
  if try_count > PREFECT_CLIENT_MAX_RETRIES.value():
219
238
  raise
220
239
  # Otherwise, we will ignore this error but capture the info for logging
221
240
  exc_info = sys.exc_info()
222
241
  else:
223
- # We got a response; return immediately if it is not retryable
224
- if response.status_code not in retry_codes:
242
+ # We got a response; check if it's a CSRF error, otherwise
243
+ # return immediately if it is not retryable
244
+ if (
245
+ response.status_code == status.HTTP_403_FORBIDDEN
246
+ and "Invalid CSRF token" in response.text
247
+ ):
248
+ # We got a CSRF error, clear the token and try again
249
+ self.csrf_token = None
250
+ await self._add_csrf_headers(request)
251
+ elif response.status_code not in retry_codes:
225
252
  return response
226
253
 
227
254
  if "Retry-After" in response.headers:
@@ -268,19 +295,24 @@ class PrefectHttpxClient(httpx.AsyncClient):
268
295
  # We ran out of retries, return the failed response
269
296
  return response
270
297
 
271
- async def send(self, *args, **kwargs) -> Response:
298
+ async def send(self, request: Request, *args, **kwargs) -> Response:
272
299
  """
273
300
  Send a request with automatic retry behavior for the following status codes:
274
301
 
302
+ - 403 Forbidden, if the request failed due to CSRF protection
303
+ - 408 Request Timeout
275
304
  - 429 CloudFlare-style rate limiting
276
305
  - 502 Bad Gateway
277
306
  - 503 Service unavailable
307
+ - Any additional status codes provided in `PREFECT_CLIENT_RETRY_EXTRA_CODES`
278
308
  """
279
309
 
280
- api_request = partial(super().send, *args, **kwargs)
281
-
310
+ super_send = super().send
282
311
  response = await self._send_with_retry(
283
- request=api_request,
312
+ request=request,
313
+ send=super_send,
314
+ send_args=args,
315
+ send_kwargs=kwargs,
284
316
  retry_codes={
285
317
  status.HTTP_429_TOO_MANY_REQUESTS,
286
318
  status.HTTP_503_SERVICE_UNAVAILABLE,
@@ -312,3 +344,41 @@ class PrefectHttpxClient(httpx.AsyncClient):
312
344
  response.raise_for_status()
313
345
 
314
346
  return response
347
+
348
+ async def _add_csrf_headers(self, request: Request):
349
+ now = datetime.now(timezone.utc)
350
+
351
+ if not self.enable_csrf_support:
352
+ return
353
+
354
+ if not self.csrf_token or (
355
+ self.csrf_token_expiration and now > self.csrf_token_expiration
356
+ ):
357
+ token_request = self.build_request(
358
+ "GET", f"/csrf-token?client={self.csrf_client_id}"
359
+ )
360
+
361
+ try:
362
+ token_response = await self.send(token_request)
363
+ except PrefectHTTPStatusError as exc:
364
+ old_server = exc.response.status_code == status.HTTP_404_NOT_FOUND
365
+ unconfigured_server = (
366
+ exc.response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
367
+ and "CSRF protection is disabled." in exc.response.text
368
+ )
369
+
370
+ if old_server or unconfigured_server:
371
+ # The token endpoint is either unavailable, suggesting an
372
+ # older server, or CSRF protection is disabled. In either
373
+ # case we should disable CSRF support.
374
+ self.enable_csrf_support = False
375
+ return
376
+
377
+ raise
378
+
379
+ token: CsrfToken = CsrfToken.parse_obj(token_response.json())
380
+ self.csrf_token = token.token
381
+ self.csrf_token_expiration = token.expiration
382
+
383
+ request.headers["Prefect-Csrf-Token"] = self.csrf_token
384
+ request.headers["Prefect-Csrf-Client"] = str(self.csrf_client_id)
prefect/client/cloud.py CHANGED
@@ -72,7 +72,7 @@ class CloudClient:
72
72
  httpx_settings.setdefault("base_url", host)
73
73
  if not PREFECT_UNIT_TEST_MODE.value():
74
74
  httpx_settings.setdefault("follow_redirects", True)
75
- self._client = PrefectHttpxClient(**httpx_settings)
75
+ self._client = PrefectHttpxClient(**httpx_settings, enable_csrf_support=False)
76
76
 
77
77
  async def api_healthcheck(self):
78
78
  """
@@ -15,6 +15,7 @@ from typing import (
15
15
  )
16
16
  from uuid import UUID, uuid4
17
17
 
18
+ import certifi
18
19
  import httpcore
19
20
  import httpx
20
21
  import pendulum
@@ -134,8 +135,10 @@ from prefect.settings import (
134
135
  PREFECT_API_ENABLE_HTTP2,
135
136
  PREFECT_API_KEY,
136
137
  PREFECT_API_REQUEST_TIMEOUT,
138
+ PREFECT_API_SSL_CERT_FILE,
137
139
  PREFECT_API_TLS_INSECURE_SKIP_VERIFY,
138
140
  PREFECT_API_URL,
141
+ PREFECT_CLIENT_CSRF_SUPPORT_ENABLED,
139
142
  PREFECT_CLOUD_API_URL,
140
143
  PREFECT_UNIT_TEST_MODE,
141
144
  )
@@ -220,6 +223,11 @@ class PrefectClient:
220
223
 
221
224
  if PREFECT_API_TLS_INSECURE_SKIP_VERIFY:
222
225
  httpx_settings.setdefault("verify", False)
226
+ else:
227
+ cert_file = PREFECT_API_SSL_CERT_FILE.value()
228
+ if not cert_file:
229
+ cert_file = certifi.where()
230
+ httpx_settings.setdefault("verify", cert_file)
223
231
 
224
232
  if api_version is None:
225
233
  api_version = SERVER_API_VERSION
@@ -316,7 +324,15 @@ class PrefectClient:
316
324
 
317
325
  if not PREFECT_UNIT_TEST_MODE:
318
326
  httpx_settings.setdefault("follow_redirects", True)
319
- self._client = PrefectHttpxClient(**httpx_settings)
327
+
328
+ enable_csrf_support = (
329
+ self.server_type != ServerType.CLOUD
330
+ and PREFECT_CLIENT_CSRF_SUPPORT_ENABLED.value()
331
+ )
332
+
333
+ self._client = PrefectHttpxClient(
334
+ **httpx_settings, enable_csrf_support=enable_csrf_support
335
+ )
320
336
  self._loop = None
321
337
 
322
338
  # See https://www.python-httpx.org/advanced/#custom-transports
@@ -1632,3 +1632,16 @@ class GlobalConcurrencyLimit(ObjectBaseModel):
1632
1632
  " is used as a rate limit."
1633
1633
  ),
1634
1634
  )
1635
+
1636
+
1637
+ class CsrfToken(ObjectBaseModel):
1638
+ token: str = Field(
1639
+ default=...,
1640
+ description="The CSRF token",
1641
+ )
1642
+ client: str = Field(
1643
+ default=..., description="The client id associated with the CSRF token"
1644
+ )
1645
+ expiration: DateTimeTZ = Field(
1646
+ default=..., description="The expiration time of the CSRF token"
1647
+ )
@@ -74,9 +74,9 @@ class Subscription(Generic[S]):
74
74
  auth: Dict[str, Any] = orjson.loads(await websocket.recv())
75
75
  assert auth["type"] == "auth_success", auth.get("message")
76
76
 
77
- message = {"type": "subscribe", "keys": self.keys} | {
78
- **(dict(client_id=self.client_id) if self.client_id else {})
79
- }
77
+ message = {"type": "subscribe", "keys": self.keys}
78
+ if self.client_id:
79
+ message.update({"client_id": self.client_id})
80
80
 
81
81
  await websocket.send(orjson.dumps(message).decode())
82
82
  except (
prefect/engine.py CHANGED
@@ -2002,10 +2002,14 @@ async def orchestrate_task_run(
2002
2002
  )
2003
2003
 
2004
2004
  # Emit an event to capture that the task run was in the `PENDING` state.
2005
- last_event = _emit_task_run_state_change_event(
2005
+ last_event = emit_task_run_state_change_event(
2006
2006
  task_run=task_run, initial_state=None, validated_state=task_run.state
2007
2007
  )
2008
- last_state = task_run.state
2008
+ last_state = (
2009
+ Pending()
2010
+ if flow_run_context and flow_run_context.autonomous_task_run
2011
+ else task_run.state
2012
+ )
2009
2013
 
2010
2014
  # Completed states with persisted results should have result data. If it's missing,
2011
2015
  # this could be a manual state transition, so we should use the Unknown result type
@@ -2094,7 +2098,7 @@ async def orchestrate_task_run(
2094
2098
  break
2095
2099
 
2096
2100
  # Emit an event to capture the result of proposing a `RUNNING` state.
2097
- last_event = _emit_task_run_state_change_event(
2101
+ last_event = emit_task_run_state_change_event(
2098
2102
  task_run=task_run,
2099
2103
  initial_state=last_state,
2100
2104
  validated_state=state,
@@ -2187,7 +2191,7 @@ async def orchestrate_task_run(
2187
2191
  await _check_task_failure_retriable(task, task_run, terminal_state)
2188
2192
  )
2189
2193
  state = await propose_state(client, terminal_state, task_run_id=task_run.id)
2190
- last_event = _emit_task_run_state_change_event(
2194
+ last_event = emit_task_run_state_change_event(
2191
2195
  task_run=task_run,
2192
2196
  initial_state=last_state,
2193
2197
  validated_state=state,
@@ -2220,7 +2224,7 @@ async def orchestrate_task_run(
2220
2224
  )
2221
2225
  # Attempt to enter a running state again
2222
2226
  state = await propose_state(client, Running(), task_run_id=task_run.id)
2223
- last_event = _emit_task_run_state_change_event(
2227
+ last_event = emit_task_run_state_change_event(
2224
2228
  task_run=task_run,
2225
2229
  initial_state=last_state,
2226
2230
  validated_state=state,
@@ -2896,7 +2900,7 @@ async def check_api_reachable(client: PrefectClient, fail_message: str):
2896
2900
  API_HEALTHCHECKS[api_url] = get_deadline(60 * 10)
2897
2901
 
2898
2902
 
2899
- def _emit_task_run_state_change_event(
2903
+ def emit_task_run_state_change_event(
2900
2904
  task_run: TaskRun,
2901
2905
  initial_state: Optional[State],
2902
2906
  validated_state: State,
prefect/events/related.py CHANGED
@@ -74,7 +74,7 @@ async def related_resources_from_run_context(
74
74
  if flow_run_id is None:
75
75
  return []
76
76
 
77
- related_objects: list[ResourceCacheEntry] = []
77
+ related_objects: List[ResourceCacheEntry] = []
78
78
 
79
79
  async with get_client() as client:
80
80
 
prefect/events/schemas.py CHANGED
@@ -372,9 +372,53 @@ class MetricTrigger(ResourceTrigger):
372
372
  )
373
373
 
374
374
 
375
- TriggerTypes: TypeAlias = Union[EventTrigger, MetricTrigger]
375
+ class CompositeTrigger(Trigger, abc.ABC):
376
+ """
377
+ Requires some number of triggers to have fired within the given time period.
378
+ """
379
+
380
+ type: Literal["compound", "sequence"]
381
+ triggers: List["TriggerTypes"]
382
+ within: Optional[timedelta]
383
+
384
+
385
+ class CompoundTrigger(CompositeTrigger):
386
+ """A composite trigger that requires some number of triggers to have
387
+ fired within the given time period"""
388
+
389
+ type: Literal["compound"] = "compound"
390
+ require: Union[int, Literal["any", "all"]]
391
+
392
+ @root_validator
393
+ def validate_require(cls, values: Dict[str, Any]) -> Dict[str, Any]:
394
+ require = values.get("require")
395
+
396
+ if isinstance(require, int):
397
+ if require < 1:
398
+ raise ValueError("required must be at least 1")
399
+ if require > len(values["triggers"]):
400
+ raise ValueError(
401
+ "required must be less than or equal to the number of triggers"
402
+ )
403
+
404
+ return values
405
+
406
+
407
+ class SequenceTrigger(CompositeTrigger):
408
+ """A composite trigger that requires some number of triggers to have fired
409
+ within the given time period in a specific order"""
410
+
411
+ type: Literal["sequence"] = "sequence"
412
+
413
+
414
+ TriggerTypes: TypeAlias = Union[
415
+ EventTrigger, MetricTrigger, CompoundTrigger, SequenceTrigger
416
+ ]
376
417
  """The union of all concrete trigger types that a user may actually create"""
377
418
 
419
+ CompoundTrigger.update_forward_refs()
420
+ SequenceTrigger.update_forward_refs()
421
+
378
422
 
379
423
  class Automation(PrefectBaseModel):
380
424
  """Defines an action a user wants to take when a certain number of events
prefect/flows.py CHANGED
@@ -1345,8 +1345,12 @@ def flow(
1345
1345
  result_serializer: Optional[ResultSerializer] = None,
1346
1346
  cache_result_in_memory: bool = True,
1347
1347
  log_prints: Optional[bool] = None,
1348
- on_completion: Optional[List[Callable[[FlowSchema, FlowRun, State], None]]] = None,
1349
- on_failure: Optional[List[Callable[[FlowSchema, FlowRun, State], None]]] = None,
1348
+ on_completion: Optional[
1349
+ List[Callable[[FlowSchema, FlowRun, State], Union[Awaitable[None], None]]]
1350
+ ] = None,
1351
+ on_failure: Optional[
1352
+ List[Callable[[FlowSchema, FlowRun, State], Union[Awaitable[None], None]]]
1353
+ ] = None,
1350
1354
  on_cancellation: Optional[
1351
1355
  List[Callable[[FlowSchema, FlowRun, State], None]]
1352
1356
  ] = None,
@@ -1373,8 +1377,12 @@ def flow(
1373
1377
  result_serializer: Optional[ResultSerializer] = None,
1374
1378
  cache_result_in_memory: bool = True,
1375
1379
  log_prints: Optional[bool] = None,
1376
- on_completion: Optional[List[Callable[[FlowSchema, FlowRun, State], None]]] = None,
1377
- on_failure: Optional[List[Callable[[FlowSchema, FlowRun, State], None]]] = None,
1380
+ on_completion: Optional[
1381
+ List[Callable[[FlowSchema, FlowRun, State], Union[Awaitable[None], None]]]
1382
+ ] = None,
1383
+ on_failure: Optional[
1384
+ List[Callable[[FlowSchema, FlowRun, State], Union[Awaitable[None], None]]]
1385
+ ] = None,
1378
1386
  on_cancellation: Optional[
1379
1387
  List[Callable[[FlowSchema, FlowRun, State], None]]
1380
1388
  ] = None,
@@ -912,8 +912,7 @@
912
912
  "metadata": {
913
913
  "name": "{{ name }}",
914
914
  "annotations": {
915
- "run.googleapis.com/launch-stage": "BETA",
916
- "run.googleapis.com/vpc-access-connector": "{{ vpc_connector_name }}"
915
+ "run.googleapis.com/launch-stage": "BETA"
917
916
  }
918
917
  },
919
918
  "spec": {
@@ -941,6 +940,11 @@
941
940
  "serviceAccountName": "{{ service_account_name }}"
942
941
  }
943
942
  }
943
+ },
944
+ "metadata": {
945
+ "annotations": {
946
+ "run.googleapis.com/vpc-access-connector": "{{ vpc_connector_name }}"
947
+ }
944
948
  }
945
949
  }
946
950
  }
@@ -1093,8 +1097,10 @@
1093
1097
  "launchStage": "{{ launch_stage }}",
1094
1098
  "template": {
1095
1099
  "template": {
1100
+ "serviceAccount": "{{ service_account_name }}",
1096
1101
  "maxRetries": "{{ max_retries }}",
1097
1102
  "timeout": "{{ timeout }}",
1103
+ "vpcAccess": "{{ vpc_connector_name }}",
1098
1104
  "containers": [
1099
1105
  {
1100
1106
  "env": [],
@@ -1229,6 +1235,12 @@
1229
1235
  "title": "VPC Connector Name",
1230
1236
  "description": "The name of the VPC connector to use for the Cloud Run job.",
1231
1237
  "type": "string"
1238
+ },
1239
+ "service_account_name": {
1240
+ "title": "Service Account Name",
1241
+ "description": "The name of the service account to use for the task execution of Cloud Run Job. By default Cloud Run jobs run as the default Compute Engine Service Account.",
1242
+ "example": "service-account@example.iam.gserviceaccount.com",
1243
+ "type": "string"
1232
1244
  }
1233
1245
  },
1234
1246
  "definitions": {
prefect/settings.py CHANGED
@@ -102,7 +102,10 @@ T = TypeVar("T")
102
102
 
103
103
  DEFAULT_PROFILES_PATH = Path(__file__).parent.joinpath("profiles.toml")
104
104
 
105
- REMOVED_EXPERIMENTAL_FLAGS = {"PREFECT_EXPERIMENTAL_ENABLE_ENHANCED_SCHEDULING_UI"}
105
+ REMOVED_EXPERIMENTAL_FLAGS = {
106
+ "PREFECT_EXPERIMENTAL_ENABLE_ENHANCED_SCHEDULING_UI",
107
+ "PREFECT_EXPERIMENTAL_ENABLE_ENHANCED_DEPLOYMENT_PARAMETERS",
108
+ }
106
109
 
107
110
 
108
111
  class Setting(Generic[T]):
@@ -592,6 +595,16 @@ PREFECT_API_TLS_INSECURE_SKIP_VERIFY = Setting(
592
595
  This is recommended only during development, e.g. when using self-signed certificates.
593
596
  """
594
597
 
598
+ PREFECT_API_SSL_CERT_FILE = Setting(
599
+ str,
600
+ default=os.environ.get("SSL_CERT_FILE"),
601
+ )
602
+ """
603
+ This configuration settings option specifies the path to an SSL certificate file.
604
+ When set, it allows the application to use the specified certificate for secure communication.
605
+ If left unset, the setting will default to the value provided by the `SSL_CERT_FILE` environment variable.
606
+ """
607
+
595
608
  PREFECT_API_URL = Setting(
596
609
  str,
597
610
  default=None,
@@ -657,6 +670,21 @@ A comma-separated list of extra HTTP status codes to retry on. Defaults to an em
657
670
  may result in unexpected behavior.
658
671
  """
659
672
 
673
+ PREFECT_CLIENT_CSRF_SUPPORT_ENABLED = Setting(bool, default=True)
674
+ """
675
+ Determines if CSRF token handling is active in the Prefect client for API
676
+ requests.
677
+
678
+ When enabled (`True`), the client automatically manages CSRF tokens by
679
+ retrieving, storing, and including them in applicable state-changing requests
680
+ (POST, PUT, PATCH, DELETE) to the API.
681
+
682
+ Disabling this setting (`False`) means the client will not handle CSRF tokens,
683
+ which might be suitable for environments where CSRF protection is disabled.
684
+
685
+ Defaults to `True`, ensuring CSRF protection is enabled by default.
686
+ """
687
+
660
688
  PREFECT_CLOUD_API_URL = Setting(
661
689
  str,
662
690
  default="https://api.prefect.cloud/api",
@@ -1207,6 +1235,33 @@ Note this setting only applies when calling `prefect server start`; if hosting t
1207
1235
  API with another tool you will need to configure this there instead.
1208
1236
  """
1209
1237
 
1238
+ PREFECT_SERVER_CSRF_PROTECTION_ENABLED = Setting(bool, default=False)
1239
+ """
1240
+ Controls the activation of CSRF protection for the Prefect server API.
1241
+
1242
+ When enabled (`True`), the server enforces CSRF validation checks on incoming
1243
+ state-changing requests (POST, PUT, PATCH, DELETE), requiring a valid CSRF
1244
+ token to be included in the request headers or body. This adds a layer of
1245
+ security by preventing unauthorized or malicious sites from making requests on
1246
+ behalf of authenticated users.
1247
+
1248
+ It is recommended to enable this setting in production environments where the
1249
+ API is exposed to web clients to safeguard against CSRF attacks.
1250
+
1251
+ Note: Enabling this setting requires corresponding support in the client for
1252
+ CSRF token management. See PREFECT_CLIENT_CSRF_SUPPORT_ENABLED for more.
1253
+ """
1254
+
1255
+ PREFECT_SERVER_CSRF_TOKEN_EXPIRATION = Setting(timedelta, default=timedelta(hours=1))
1256
+ """
1257
+ Specifies the duration for which a CSRF token remains valid after being issued
1258
+ by the server.
1259
+
1260
+ The default expiration time is set to 1 hour, which offers a reasonable
1261
+ compromise. Adjust this setting based on your specific security requirements
1262
+ and usage patterns.
1263
+ """
1264
+
1210
1265
  PREFECT_UI_ENABLED = Setting(
1211
1266
  bool,
1212
1267
  default=True,
@@ -1292,12 +1347,12 @@ PREFECT_API_MAX_FLOW_RUN_GRAPH_ARTIFACTS = Setting(int, default=10000)
1292
1347
  The maximum number of artifacts to show on a flow run graph on the v2 API
1293
1348
  """
1294
1349
 
1295
- PREFECT_EXPERIMENTAL_ENABLE_ARTIFACTS_ON_FLOW_RUN_GRAPH = Setting(bool, default=False)
1350
+ PREFECT_EXPERIMENTAL_ENABLE_ARTIFACTS_ON_FLOW_RUN_GRAPH = Setting(bool, default=True)
1296
1351
  """
1297
1352
  Whether or not to enable artifacts on the flow run graph.
1298
1353
  """
1299
1354
 
1300
- PREFECT_EXPERIMENTAL_ENABLE_STATES_ON_FLOW_RUN_GRAPH = Setting(bool, default=False)
1355
+ PREFECT_EXPERIMENTAL_ENABLE_STATES_ON_FLOW_RUN_GRAPH = Setting(bool, default=True)
1301
1356
  """
1302
1357
  Whether or not to enable flow run states on the flow run graph.
1303
1358
  """
@@ -1342,11 +1397,6 @@ PREFECT_EXPERIMENTAL_ENABLE_ENHANCED_CANCELLATION = Setting(bool, default=True)
1342
1397
  Whether or not to enable experimental enhanced flow run cancellation.
1343
1398
  """
1344
1399
 
1345
- PREFECT_EXPERIMENTAL_ENABLE_ENHANCED_DEPLOYMENT_PARAMETERS = Setting(bool, default=True)
1346
- """
1347
- Whether or not to enable enhanced deployment parameters.
1348
- """
1349
-
1350
1400
  PREFECT_EXPERIMENTAL_WARN_ENHANCED_CANCELLATION = Setting(bool, default=False)
1351
1401
  """
1352
1402
  Whether or not to warn when experimental enhanced flow run cancellation is used.
@@ -1525,6 +1575,10 @@ PREFECT_EXPERIMENTAL_ENABLE_WORK_QUEUE_STATUS = Setting(bool, default=True)
1525
1575
  Whether or not to enable experimental work queue status in-place of work queue health.
1526
1576
  """
1527
1577
 
1578
+ PREFECT_EXPERIMENTAL_ENABLE_PYDANTIC_V2_INTERNALS = Setting(bool, default=False)
1579
+ """
1580
+ Whether or not to enable internal experimental Pydantic v2 behavior.
1581
+ """
1528
1582
 
1529
1583
  # Defaults -----------------------------------------------------------------------------
1530
1584
 
prefect/task_server.py CHANGED
@@ -6,7 +6,7 @@ import socket
6
6
  import sys
7
7
  from contextlib import AsyncExitStack
8
8
  from functools import partial
9
- from typing import Optional, Type
9
+ from typing import List, Optional, Type
10
10
 
11
11
  import anyio
12
12
  from websockets.exceptions import InvalidStatusCode
@@ -15,7 +15,7 @@ from prefect import Task, get_client
15
15
  from prefect._internal.concurrency.api import create_call, from_sync
16
16
  from prefect.client.schemas.objects import TaskRun
17
17
  from prefect.client.subscriptions import Subscription
18
- from prefect.engine import propose_state
18
+ from prefect.engine import emit_task_run_state_change_event, propose_state
19
19
  from prefect.logging.loggers import get_logger
20
20
  from prefect.results import ResultFactory
21
21
  from prefect.settings import (
@@ -72,7 +72,7 @@ class TaskServer:
72
72
  *tasks: Task,
73
73
  task_runner: Optional[Type[BaseTaskRunner]] = None,
74
74
  ):
75
- self.tasks: list[Task] = tasks
75
+ self.tasks: List[Task] = tasks
76
76
 
77
77
  self.task_runner: BaseTaskRunner = task_runner or ConcurrentTaskRunner()
78
78
  self.started: bool = False
@@ -205,6 +205,12 @@ class TaskServer:
205
205
  " Task run may have already begun execution."
206
206
  )
207
207
 
208
+ emit_task_run_state_change_event(
209
+ task_run=task_run,
210
+ initial_state=task_run.state,
211
+ validated_state=state,
212
+ )
213
+
208
214
  self._runs_task_group.start_soon(
209
215
  partial(
210
216
  submit_autonomous_task_run_to_engine,
prefect/tasks.py CHANGED
@@ -283,13 +283,14 @@ class Task(Generic[P, R]):
283
283
  if not hasattr(self.fn, "__qualname__"):
284
284
  self.task_key = to_qualified_name(type(self.fn))
285
285
  else:
286
- if self.fn.__module__ == "__main__":
287
- task_definition_path = inspect.getsourcefile(self.fn)
288
- self.task_key = hash_objects(
289
- self.name, os.path.abspath(task_definition_path)
286
+ try:
287
+ task_origin_hash = hash_objects(
288
+ self.name, os.path.abspath(inspect.getsourcefile(self.fn))
290
289
  )
291
- else:
292
- self.task_key = to_qualified_name(self.fn)
290
+ except TypeError:
291
+ task_origin_hash = "unknown-source-file"
292
+
293
+ self.task_key = f"{self.fn.__qualname__}-{task_origin_hash}"
293
294
 
294
295
  self.cache_key_fn = cache_key_fn
295
296
  self.cache_expiration = cache_expiration
@@ -390,8 +391,12 @@ class Task(Generic[P, R]):
390
391
  timeout_seconds: Union[int, float] = None,
391
392
  log_prints: Optional[bool] = NotSet,
392
393
  refresh_cache: Optional[bool] = NotSet,
393
- on_completion: Optional[List[Callable[["Task", TaskRun, State], None]]] = None,
394
- on_failure: Optional[List[Callable[["Task", TaskRun, State], None]]] = None,
394
+ on_completion: Optional[
395
+ List[Callable[["Task", TaskRun, State], Union[Awaitable[None], None]]]
396
+ ] = None,
397
+ on_failure: Optional[
398
+ List[Callable[["Task", TaskRun, State], Union[Awaitable[None], None]]]
399
+ ] = None,
395
400
  retry_condition_fn: Optional[Callable[["Task", TaskRun, State], bool]] = None,
396
401
  viz_return_value: Optional[Any] = None,
397
402
  ):
@@ -1,6 +1,7 @@
1
1
  """
2
2
  Utilities for working with Python callables.
3
3
  """
4
+
4
5
  import inspect
5
6
  from functools import partial
6
7
  from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
@@ -312,8 +313,9 @@ def parameter_schema(fn: Callable) -> ParameterSchema:
312
313
  ParameterSchema: the argument schema
313
314
  """
314
315
  try:
315
- signature = inspect.signature(fn, eval_str=True)
316
+ signature = inspect.signature(fn, eval_str=True) # novm
316
317
  except (NameError, TypeError):
318
+ # `eval_str` is not available in Python < 3.10
317
319
  signature = inspect.signature(fn)
318
320
 
319
321
  model_fields = {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: prefect-client
3
- Version: 2.16.4
3
+ Version: 2.16.5
4
4
  Summary: Workflow orchestration and management.
5
5
  Home-page: https://www.prefect.io
6
6
  Author: Prefect Technologies, Inc.
@@ -2,12 +2,13 @@ prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
2
2
  prefect/__init__.py,sha256=FqZ2FacBZq5wr-RnLQphwqXN0atLdu9bDC2YtmZi-AU,5456
3
3
  prefect/_version.py,sha256=fQguBh1dzT7Baahj504O5RrsLlSyg3Zrx42OpgdPnFc,22378
4
4
  prefect/agent.py,sha256=ZHRiQo13SC8F_flWaoWskVopM1DZKgZVwx9kkg_z0A0,27791
5
+ prefect/artifacts.py,sha256=QLnFkVaBpMQp9fLWkHlayZOUCp2OI6lPmAkUbT-NMLo,5274
5
6
  prefect/context.py,sha256=QK_U3ym-h2i1Y_EOSr4BQeeMN0AIOpG81LQS7k1RiRA,18103
6
- prefect/engine.py,sha256=ZWu-4oyOeoJ-CWe-Yz3SVNg7GkoMVfVjPu72ZAFLvx4,110024
7
+ prefect/engine.py,sha256=kWU4jJQm3ApiJiuq3rZysyZZC2zBQc3DdJuYfpydmIE,110127
7
8
  prefect/exceptions.py,sha256=84rpsDLp0cn_v2gE1TnK_NZXh27NJtzgZQtARVKyVEE,10953
8
9
  prefect/filesystems.py,sha256=HkBczs0r69yJQRWsPUVJiU2JKK6NPrkPvSSVIUrvMpQ,36444
9
10
  prefect/flow_runs.py,sha256=mFHLavZk1yZ62H3UazuNDBZWAF7AqKttA4rMcHgsVSw,3119
10
- prefect/flows.py,sha256=B4W5kAX3jNjBoYmZQYnZ6XC3_th3F8IKTc5MUiZ8_YI,70123
11
+ prefect/flows.py,sha256=CvQ_sGsJNA7zs4i9l4jaZpReon_RznSeK8vZ4TbpSY0,70275
11
12
  prefect/futures.py,sha256=RaWfYIXtH7RsWxQ5QWTTlAzwtVV8XWpXaZT_hLq35vQ,12590
12
13
  prefect/manifests.py,sha256=xfwEEozSEqPK2Lro4dfgdTnjVbQx-aCECNBnf7vO7ZQ,808
13
14
  prefect/plugins.py,sha256=0C-D3-dKi06JZ44XEGmLjCiAkefbE_lKX-g3urzdbQ4,4163
@@ -15,12 +16,12 @@ prefect/profiles.toml,sha256=1Tz7nKBDTDXL_6KPJSeB7ok0Vx_aQJ_p0AUmbnzDLzw,39
15
16
  prefect/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
17
  prefect/results.py,sha256=FgudRagwoNKVKR5590I4AN0mxgYoyXG_7Q1HVoMXdaU,24731
17
18
  prefect/serializers.py,sha256=sSbe40Ipj-d6VuzBae5k2ao9lkMUZpIXcLtD7f2a7cE,10852
18
- prefect/settings.py,sha256=4YgZ6LxyWr-WIj5B50K3eXJrtnsbYdlAz9mPDJ4cVCc,68890
19
+ prefect/settings.py,sha256=3xU5DQQglwa7MFnYQ6GqhWHbdIis81Ehnj1fMa7d30M,71067
19
20
  prefect/states.py,sha256=-Ud4AUom3Qu-HQ4hOLvfVZuuF-b_ibaqtzmL7V949Ac,20839
20
21
  prefect/task_engine.py,sha256=_2I7XLwoT_nNhpzTMa_52aQKjsDoaW6WpzwIHYEWZS0,2598
21
22
  prefect/task_runners.py,sha256=HXUg5UqhZRN2QNBqMdGE1lKhwFhT8TaRN75ScgLbnw8,11012
22
- prefect/task_server.py,sha256=Fga_2DOhPgXIO2f-R4u6pOtMxxJfqHyjX9r627-5a78,10331
23
- prefect/tasks.py,sha256=pwTZcyv7meEomogREofh1r3gDcaG-VSz8vO1_F3QBcg,50390
23
+ prefect/task_server.py,sha256=6sJAQ6re5ahAHF9IjYkr05mUgefUB6Ga0BeKicgH84A,10532
24
+ prefect/tasks.py,sha256=AFDCyb0p0r8mamiFMu220-DfGMLSjq-uRi4vL6oxQOE,50477
24
25
  prefect/variables.py,sha256=sk3pfwfPY5lKLt4Qi7OQJPeYipzYip3gidgA9gydcpI,978
25
26
  prefect/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
26
27
  prefect/_internal/_logging.py,sha256=HvNHY-8P469o5u4LYEDBTem69XZEt1QUeUaLToijpak,810
@@ -30,7 +31,7 @@ prefect/_internal/compatibility/deprecated.py,sha256=5vd4iIzpeGftFdtVaP6PCKNQRiN
30
31
  prefect/_internal/compatibility/experimental.py,sha256=bQ2ia6MjgIu1SAWpkGVza87wSz5aTo58X_z3JawqleQ,7442
31
32
  prefect/_internal/concurrency/__init__.py,sha256=ncKwi1NhE3umSFGSKRk9wEVKzN1z1ZD-fmY4EDZHH_U,2142
32
33
  prefect/_internal/concurrency/api.py,sha256=I6OHx53rP7f8GI_O-VHLook1wJfM5Wbe6i2OlAcEcjs,8765
33
- prefect/_internal/concurrency/calls.py,sha256=AC7SW8T97RZKQvqD_4IwhlDzIq3aem8bdKDTAOonY6E,15553
34
+ prefect/_internal/concurrency/calls.py,sha256=SVMR1yPTQJtBX095WfRk6cMTq4YKf_L6G77qtaTyN3I,15564
34
35
  prefect/_internal/concurrency/cancellation.py,sha256=eiVsdG5BE_2HCvkQGzIcZ6Gw4ANmchcPRzHsI9wtP3Y,18201
35
36
  prefect/_internal/concurrency/event_loop.py,sha256=rOxUa7e95xP4ionH3o0gRpUzzG6aZMQUituLpMTvTFo,2596
36
37
  prefect/_internal/concurrency/inspection.py,sha256=GWFoSzgs8bZZGNN-Im9sQ-0t0Dqdn8EbwPR1UY3Mhro,3452
@@ -38,7 +39,8 @@ prefect/_internal/concurrency/primitives.py,sha256=kxCPD9yLtCeqt-JIHjevL4Zt5FvrF
38
39
  prefect/_internal/concurrency/services.py,sha256=aggJd4IUSB6ufppRYdRT-36daEg1JSpJCvK635R8meg,11951
39
40
  prefect/_internal/concurrency/threads.py,sha256=-tReWZL9_XMkRS35SydAfeePH2vqCqb1CGM8lgrKT1I,7846
40
41
  prefect/_internal/concurrency/waiters.py,sha256=DXTD_bbVEUhcTplYQFX8mGmL6nsqJGEDfvS0TmHmIQk,9475
41
- prefect/_internal/pydantic/__init__.py,sha256=ZTFHClaJIxIHpFFIHrZD1rZbWrY-1yyvAvLvwy1IQCk,423
42
+ prefect/_internal/pydantic/__init__.py,sha256=zhbVYT051zywa0rF7Q62jaVFH2D2no3CTCJ1ZXktmR8,482
43
+ prefect/_internal/pydantic/_compat.py,sha256=YTRAmOTTYybXKJtwsPjee40shpWCtAYlI7RZbPADVO0,6532
42
44
  prefect/_internal/pydantic/schemas.py,sha256=tsRKq5yEIgiRbWMl3BPnbfNaKyDN6pq8WSs0M8SQMm4,452
43
45
  prefect/_internal/pydantic/v2_schema.py,sha256=fySqjMCFoJpRs7wN6c5qoVKePbDbWcXYUoYOs5eFzL0,3485
44
46
  prefect/_internal/pydantic/v2_validated_func.py,sha256=44I4o8jjiS7TYep-E6UYMwjpYH5F1WwJFajW81A3wts,3823
@@ -137,17 +139,17 @@ prefect/blocks/notifications.py,sha256=gtr2irqxlvQ5aJTUioG1VfsdSL1xu5e8pWxAYzf49
137
139
  prefect/blocks/system.py,sha256=Nlp-3315Hye3FJ5uhDovSPGBIEKi5UbCkAcy3hDxhKk,3057
138
140
  prefect/blocks/webhook.py,sha256=hhyWck7mAPfD_12bl40dJedNC9HIaqs7z13iYcZZ14o,2005
139
141
  prefect/client/__init__.py,sha256=yJ5FRF9RxNUio2V_HmyKCKw5G6CZO0h8cv6xA_Hkpcc,477
140
- prefect/client/base.py,sha256=yUIiMH3qvm0esS5H22LwZGXZO2Vv-9MdbRu1yqMNOwg,12225
141
- prefect/client/cloud.py,sha256=jResyTjMNt69x_VoqUyoY6K0kmO9yZL6PyAvxbsJuYk,3971
142
+ prefect/client/base.py,sha256=VsJWgaSEyIbHo2MfIkBuErahYwXnU68P3R-n83jx2LI,15211
143
+ prefect/client/cloud.py,sha256=rrxwmYE9yH4HIewu-xG0HY4P7rwP9gFNitBMYQybcvE,3998
142
144
  prefect/client/collections.py,sha256=I9EgbTg4Fn57gn8vwP_WdDmgnATbx9gfkm2jjhCORjw,1037
143
145
  prefect/client/constants.py,sha256=Z_GG8KF70vbbXxpJuqW5pLnwzujTVeHbcYYRikNmGH0,29
144
- prefect/client/orchestration.py,sha256=OAVaxD_oFgYxri8OjmB67w5kwqX81jKxVPIAQsViX1U,110691
145
- prefect/client/subscriptions.py,sha256=Z505esj6hnCLUuiVUd8eNdyWVsU0oHn_2STPGDHq4ts,3374
146
+ prefect/client/orchestration.py,sha256=HnBg-i5vrbzhn0KaXgJDbGST5KDsmEv5oJFyWDemHZ0,111199
147
+ prefect/client/subscriptions.py,sha256=3kqPH3F-CwyrR5wygCpJMjRjM_gcQjd54Qjih6FcLlA,3372
146
148
  prefect/client/utilities.py,sha256=oGU8dJIq7ExEF4WFt-0aSPNX0JP7uH6NmfRlNhfJu00,2660
147
149
  prefect/client/schemas/__init__.py,sha256=KlyqFV-hMulMkNstBn_0ijoHoIwJZaBj6B1r07UmgvE,607
148
150
  prefect/client/schemas/actions.py,sha256=hoJ6q10z6lS-GA2osURjBp-rD0lOzJA2qGu6Opnonjo,25937
149
151
  prefect/client/schemas/filters.py,sha256=r6gnxZREnmE8Glt2SF6vPxHr0SIeiFBjTrrN32cw-Mo,35514
150
- prefect/client/schemas/objects.py,sha256=6JjKbjHf_N3bjqLN-ORCCFc48ieR3YscWN_rMJWkba0,55211
152
+ prefect/client/schemas/objects.py,sha256=U3rNUzQspoenwxpb_1b5PcVsX5s2KSNPW9wl1EbGqAE,55562
151
153
  prefect/client/schemas/responses.py,sha256=hErSClfLjt3Ys18YZyZS6dyjVf3eLkiAF6mjEgF4LGg,9344
152
154
  prefect/client/schemas/schedules.py,sha256=ncGWmmBzZvf5G4AL27E0kWGiJxGX-haR2_-GUNvFlv4,14829
153
155
  prefect/client/schemas/sorting.py,sha256=Y-ea8k_vTUKAPKIxqGebwLSXM7x1s5mJ_4-sDd1Ivi8,2276
@@ -179,8 +181,8 @@ prefect/events/actions.py,sha256=wYc52xin_CLrNZaou05FdGdLZ5VEhT2lKM_k-MQEJ34,139
179
181
  prefect/events/clients.py,sha256=_4_QV6TWnG-dOIXWaudMprxmdjUaqMc8BgZHYdnGuHU,13975
180
182
  prefect/events/filters.py,sha256=vSWHGDCCsi_znQs3gZomCxh-Q498ukn_QHJ7H8q16do,6922
181
183
  prefect/events/instrument.py,sha256=uNiD7AnkfuiwTsCMgNyJURmY9H2tXNfLCb3EC5FL0Qw,3805
182
- prefect/events/related.py,sha256=N0o19kTlos1V4L4AgO79Z_k06ZW9bfjSH8Xa9h7lugg,6746
183
- prefect/events/schemas.py,sha256=OR_fqi1jgRddgz00AL1tIVVvlN58VVSyaHK7yi2fpO4,18163
184
+ prefect/events/related.py,sha256=jMsCL6VKgMmMcVF4TXdJxQQRT5sxCuAu6piAxSOJxxs,6746
185
+ prefect/events/schemas.py,sha256=x1Wy7btsI6RZ5FSBmdMVmU4WD8ZPANGoJ8uDJgW9VJc,19475
184
186
  prefect/events/utilities.py,sha256=gUEJA_kVuYASCqDpGX0HwDW0yczMX0AdgmxXbxhzWbM,2452
185
187
  prefect/events/worker.py,sha256=Z6MZmcCyXZtWi4vEtnFyvnzIEBW7HD14lEH1Crye3gY,2716
186
188
  prefect/infrastructure/__init__.py,sha256=Fm1Rhc4I7ZfJePpUAl1F4iNEtcDugoT650WXXt6xoCM,770
@@ -214,7 +216,7 @@ prefect/runtime/__init__.py,sha256=iYmfK1HmXiXXCQK77wDloOqZmY7SFF5iyr37jRzuf-c,4
214
216
  prefect/runtime/deployment.py,sha256=UWNXH-3-NNVxLCl5XnDKiofo4a5j8w_42ns1OSQMixg,4751
215
217
  prefect/runtime/flow_run.py,sha256=aFM3e9xqpeZQ4WkvZQXD0lmXu2fNVVVA1etSN3ZI9aE,8444
216
218
  prefect/runtime/task_run.py,sha256=_np3pjBHWkvEtSe-QElEAGwUct629vVx_sahPr-H8gM,3402
217
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json,sha256=MfSqJCoUe1lEdSqFT8fy4qJAaO9jauZoE-vZ_9re0IU,77799
219
+ prefect/server/api/collections_data/views/aggregate-worker-metadata.json,sha256=Gwv5t_RXgqI77MRk5EV0cWVXRrxZ3_wMWxypV1F2ypI,78423
218
220
  prefect/server/api/static/prefect-logo-mark-gradient.png,sha256=ylRjJkI_JHCw8VbQasNnXQHwZW-sH-IQiUGSD3aWP1E,73430
219
221
  prefect/software/__init__.py,sha256=cn7Hesmkv3unA3NynEiyB0Cj2jAzV17yfwjVsS5Ecso,106
220
222
  prefect/software/base.py,sha256=GV6a5RrLx3JaOg1RI44jZTsI_qbqNWbWF3uVO5csnHM,1464
@@ -224,7 +226,7 @@ prefect/software/python.py,sha256=reuEJFZPJ5PrDMfK3BuPpYieHNkOXJAyCAaopQcjDqE,17
224
226
  prefect/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
225
227
  prefect/utilities/annotations.py,sha256=p33yhh1Zx8BZUlTtl8gKRbpwWU9FVnZ8cfYrcd5KxDI,3103
226
228
  prefect/utilities/asyncutils.py,sha256=dNVKZKLVdNOhQObPf-224l3uWyKnt1jSFpReMpWFwT4,15674
227
- prefect/utilities/callables.py,sha256=zev1GNU_VHjVb_r_vAzTJ4oSXfWODPPnj7yDtaPipFY,11539
229
+ prefect/utilities/callables.py,sha256=5G2K_ZAnNoWoY7DqESKpbf4ltF5fkGRlJUvDwBGD7t0,11603
228
230
  prefect/utilities/collections.py,sha256=D_DT489rTCwyzZb021i0xp8osBkkQgSW9XLOoLBzgkg,15436
229
231
  prefect/utilities/compat.py,sha256=mNQZDnzyKaOqy-OV-DnmH_dc7CNF5nQgW_EsA4xMr7g,906
230
232
  prefect/utilities/context.py,sha256=nb_Kui1q9cYK5fLy84baoBzko5-mOToQkd1AnZhwyq8,418
@@ -253,8 +255,8 @@ prefect/workers/block.py,sha256=lvKlaWdA-DCCXDX23HHK9M5urEq4x2wmpKtU9ft3a7k,7767
253
255
  prefect/workers/process.py,sha256=Kxj_eZYh6R8t8253LYIIafiG7dodCF8RZABwd3Ng_R0,10253
254
256
  prefect/workers/server.py,sha256=WVZJxR8nTMzK0ov0BD0xw5OyQpT26AxlXbsGQ1OrxeQ,1551
255
257
  prefect/workers/utilities.py,sha256=VfPfAlGtTuDj0-Kb8WlMgAuOfgXCdrGAnKMapPSBrwc,2483
256
- prefect_client-2.16.4.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
257
- prefect_client-2.16.4.dist-info/METADATA,sha256=MBARaKE2rL8fAortKLSdgE-l952gLz1g4Q7N6-H95W0,7349
258
- prefect_client-2.16.4.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
259
- prefect_client-2.16.4.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
260
- prefect_client-2.16.4.dist-info/RECORD,,
258
+ prefect_client-2.16.5.dist-info/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
259
+ prefect_client-2.16.5.dist-info/METADATA,sha256=A58u55jaNnt77urHhD1faDHXMlL8cClM78JMOFrLwhw,7349
260
+ prefect_client-2.16.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
261
+ prefect_client-2.16.5.dist-info/top_level.txt,sha256=MJZYJgFdbRc2woQCeB4vM6T33tr01TmkEhRcns6H_H4,8
262
+ prefect_client-2.16.5.dist-info/RECORD,,