prefect-client 3.1.10__py3-none-any.whl → 3.1.12__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.
Files changed (141) hide show
  1. prefect/_experimental/lineage.py +7 -8
  2. prefect/_experimental/sla/__init__.py +0 -0
  3. prefect/_experimental/sla/client.py +66 -0
  4. prefect/_experimental/sla/objects.py +53 -0
  5. prefect/_internal/_logging.py +15 -3
  6. prefect/_internal/compatibility/async_dispatch.py +22 -16
  7. prefect/_internal/compatibility/deprecated.py +42 -18
  8. prefect/_internal/compatibility/migration.py +2 -2
  9. prefect/_internal/concurrency/inspection.py +12 -14
  10. prefect/_internal/concurrency/primitives.py +2 -2
  11. prefect/_internal/concurrency/services.py +154 -80
  12. prefect/_internal/concurrency/waiters.py +13 -9
  13. prefect/_internal/pydantic/annotations/pendulum.py +7 -7
  14. prefect/_internal/pytz.py +4 -3
  15. prefect/_internal/retries.py +10 -5
  16. prefect/_internal/schemas/bases.py +19 -10
  17. prefect/_internal/schemas/validators.py +227 -388
  18. prefect/_version.py +3 -3
  19. prefect/automations.py +236 -30
  20. prefect/blocks/__init__.py +3 -3
  21. prefect/blocks/abstract.py +53 -30
  22. prefect/blocks/core.py +183 -84
  23. prefect/blocks/notifications.py +133 -73
  24. prefect/blocks/redis.py +13 -9
  25. prefect/blocks/system.py +24 -11
  26. prefect/blocks/webhook.py +7 -5
  27. prefect/cache_policies.py +3 -2
  28. prefect/client/orchestration/__init__.py +1957 -0
  29. prefect/client/orchestration/_artifacts/__init__.py +0 -0
  30. prefect/client/orchestration/_artifacts/client.py +239 -0
  31. prefect/client/orchestration/_automations/__init__.py +0 -0
  32. prefect/client/orchestration/_automations/client.py +329 -0
  33. prefect/client/orchestration/_blocks_documents/__init__.py +0 -0
  34. prefect/client/orchestration/_blocks_documents/client.py +334 -0
  35. prefect/client/orchestration/_blocks_schemas/__init__.py +0 -0
  36. prefect/client/orchestration/_blocks_schemas/client.py +200 -0
  37. prefect/client/orchestration/_blocks_types/__init__.py +0 -0
  38. prefect/client/orchestration/_blocks_types/client.py +380 -0
  39. prefect/client/orchestration/_concurrency_limits/__init__.py +0 -0
  40. prefect/client/orchestration/_concurrency_limits/client.py +762 -0
  41. prefect/client/orchestration/_deployments/__init__.py +0 -0
  42. prefect/client/orchestration/_deployments/client.py +1128 -0
  43. prefect/client/orchestration/_flow_runs/__init__.py +0 -0
  44. prefect/client/orchestration/_flow_runs/client.py +903 -0
  45. prefect/client/orchestration/_flows/__init__.py +0 -0
  46. prefect/client/orchestration/_flows/client.py +343 -0
  47. prefect/client/orchestration/_logs/__init__.py +0 -0
  48. prefect/client/orchestration/_logs/client.py +97 -0
  49. prefect/client/orchestration/_variables/__init__.py +0 -0
  50. prefect/client/orchestration/_variables/client.py +157 -0
  51. prefect/client/orchestration/base.py +46 -0
  52. prefect/client/orchestration/routes.py +145 -0
  53. prefect/client/schemas/__init__.py +68 -28
  54. prefect/client/schemas/actions.py +2 -2
  55. prefect/client/schemas/filters.py +5 -0
  56. prefect/client/schemas/objects.py +8 -15
  57. prefect/client/schemas/schedules.py +22 -10
  58. prefect/concurrency/_asyncio.py +87 -0
  59. prefect/concurrency/{events.py → _events.py} +10 -10
  60. prefect/concurrency/asyncio.py +20 -104
  61. prefect/concurrency/context.py +6 -4
  62. prefect/concurrency/services.py +26 -74
  63. prefect/concurrency/sync.py +23 -44
  64. prefect/concurrency/v1/_asyncio.py +63 -0
  65. prefect/concurrency/v1/{events.py → _events.py} +13 -15
  66. prefect/concurrency/v1/asyncio.py +27 -80
  67. prefect/concurrency/v1/context.py +6 -4
  68. prefect/concurrency/v1/services.py +33 -79
  69. prefect/concurrency/v1/sync.py +18 -37
  70. prefect/context.py +66 -45
  71. prefect/deployments/base.py +10 -144
  72. prefect/deployments/flow_runs.py +12 -2
  73. prefect/deployments/runner.py +53 -4
  74. prefect/deployments/steps/pull.py +13 -0
  75. prefect/engine.py +17 -4
  76. prefect/events/clients.py +7 -1
  77. prefect/events/schemas/events.py +3 -2
  78. prefect/filesystems.py +6 -2
  79. prefect/flow_engine.py +101 -85
  80. prefect/flows.py +10 -1
  81. prefect/input/run_input.py +2 -1
  82. prefect/logging/logging.yml +1 -1
  83. prefect/main.py +1 -3
  84. prefect/results.py +2 -307
  85. prefect/runner/runner.py +4 -2
  86. prefect/runner/storage.py +87 -21
  87. prefect/serializers.py +32 -25
  88. prefect/settings/legacy.py +4 -4
  89. prefect/settings/models/api.py +3 -3
  90. prefect/settings/models/cli.py +3 -3
  91. prefect/settings/models/client.py +5 -3
  92. prefect/settings/models/cloud.py +8 -3
  93. prefect/settings/models/deployments.py +3 -3
  94. prefect/settings/models/experiments.py +4 -7
  95. prefect/settings/models/flows.py +3 -3
  96. prefect/settings/models/internal.py +4 -2
  97. prefect/settings/models/logging.py +4 -3
  98. prefect/settings/models/results.py +3 -3
  99. prefect/settings/models/root.py +3 -2
  100. prefect/settings/models/runner.py +4 -4
  101. prefect/settings/models/server/api.py +3 -3
  102. prefect/settings/models/server/database.py +11 -4
  103. prefect/settings/models/server/deployments.py +6 -2
  104. prefect/settings/models/server/ephemeral.py +4 -2
  105. prefect/settings/models/server/events.py +3 -2
  106. prefect/settings/models/server/flow_run_graph.py +6 -2
  107. prefect/settings/models/server/root.py +3 -3
  108. prefect/settings/models/server/services.py +26 -11
  109. prefect/settings/models/server/tasks.py +6 -3
  110. prefect/settings/models/server/ui.py +3 -3
  111. prefect/settings/models/tasks.py +5 -5
  112. prefect/settings/models/testing.py +3 -3
  113. prefect/settings/models/worker.py +5 -3
  114. prefect/settings/profiles.py +15 -2
  115. prefect/states.py +61 -45
  116. prefect/task_engine.py +54 -75
  117. prefect/task_runners.py +56 -55
  118. prefect/task_worker.py +2 -2
  119. prefect/tasks.py +90 -36
  120. prefect/telemetry/bootstrap.py +10 -9
  121. prefect/telemetry/run_telemetry.py +13 -8
  122. prefect/telemetry/services.py +4 -0
  123. prefect/transactions.py +4 -15
  124. prefect/utilities/_git.py +34 -0
  125. prefect/utilities/asyncutils.py +1 -1
  126. prefect/utilities/engine.py +3 -19
  127. prefect/utilities/generics.py +18 -0
  128. prefect/utilities/templating.py +25 -1
  129. prefect/workers/base.py +6 -3
  130. prefect/workers/process.py +1 -1
  131. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/METADATA +2 -2
  132. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/RECORD +135 -109
  133. prefect/client/orchestration.py +0 -4523
  134. prefect/records/__init__.py +0 -1
  135. prefect/records/base.py +0 -235
  136. prefect/records/filesystem.py +0 -213
  137. prefect/records/memory.py +0 -184
  138. prefect/records/result_store.py +0 -70
  139. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/LICENSE +0 -0
  140. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/WHEEL +0 -0
  141. {prefect_client-3.1.10.dist-info → prefect_client-3.1.12.dist-info}/top_level.txt +0 -0
@@ -5,27 +5,21 @@ build system for managing flows and deployments.
5
5
  To get started, follow along with [the deloyments tutorial](/tutorials/deployments/).
6
6
  """
7
7
 
8
- import ast
9
- import asyncio
10
- import math
8
+ from __future__ import annotations
9
+
11
10
  import os
12
- import subprocess
13
- import sys
14
11
  from copy import deepcopy
15
12
  from pathlib import Path
16
13
  from typing import Any, Dict, List, Optional, cast
17
14
 
18
- import anyio
19
15
  import yaml
20
16
  from ruamel.yaml import YAML
21
17
 
22
18
  from prefect.client.schemas.actions import DeploymentScheduleCreate
23
19
  from prefect.client.schemas.objects import ConcurrencyLimitStrategy
24
20
  from prefect.client.schemas.schedules import IntervalSchedule
25
- from prefect.logging import get_logger
26
- from prefect.settings import PREFECT_DEBUG_MODE
27
- from prefect.utilities.asyncutils import LazySemaphore
28
- from prefect.utilities.filesystem import create_default_ignore_file, get_open_file_limit
21
+ from prefect.utilities._git import get_git_branch, get_git_remote_origin_url
22
+ from prefect.utilities.filesystem import create_default_ignore_file
29
23
  from prefect.utilities.templating import apply_values
30
24
 
31
25
 
@@ -146,36 +140,6 @@ def configure_project_by_recipe(recipe: str, **formatting_kwargs) -> dict:
146
140
  return config
147
141
 
148
142
 
149
- def _get_git_remote_origin_url() -> Optional[str]:
150
- """
151
- Returns the git remote origin URL for the current directory.
152
- """
153
- try:
154
- origin_url = subprocess.check_output(
155
- ["git", "config", "--get", "remote.origin.url"],
156
- shell=sys.platform == "win32",
157
- stderr=subprocess.DEVNULL,
158
- )
159
- origin_url = origin_url.decode().strip()
160
- except subprocess.CalledProcessError:
161
- return None
162
-
163
- return origin_url
164
-
165
-
166
- def _get_git_branch() -> Optional[str]:
167
- """
168
- Returns the git branch for the current directory.
169
- """
170
- try:
171
- branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
172
- branch = branch.decode().strip()
173
- except subprocess.CalledProcessError:
174
- return None
175
-
176
- return branch
177
-
178
-
179
143
  def initialize_project(
180
144
  name: Optional[str] = None,
181
145
  recipe: Optional[str] = None,
@@ -198,11 +162,11 @@ def initialize_project(
198
162
  formatting_kwargs = {"directory": str(Path(".").absolute().resolve())}
199
163
  dir_name = os.path.basename(os.getcwd())
200
164
 
201
- remote_url = _get_git_remote_origin_url()
165
+ remote_url = get_git_remote_origin_url()
202
166
  if remote_url:
203
167
  formatting_kwargs["repository"] = remote_url
204
168
  is_git_based = True
205
- branch = _get_git_branch()
169
+ branch = get_git_branch()
206
170
  formatting_kwargs["branch"] = branch or "main"
207
171
 
208
172
  formatting_kwargs["name"] = dir_name
@@ -313,6 +277,7 @@ def _save_deployment_to_prefect_file(
313
277
  push_steps: Optional[List[Dict]] = None,
314
278
  pull_steps: Optional[List[Dict]] = None,
315
279
  triggers: Optional[List[Dict]] = None,
280
+ sla: Optional[list[dict]] = None,
316
281
  prefect_file: Path = Path("prefect.yaml"),
317
282
  ):
318
283
  """
@@ -357,6 +322,9 @@ def _save_deployment_to_prefect_file(
357
322
  if triggers and triggers != parsed_prefect_file_contents.get("triggers"):
358
323
  deployment["triggers"] = triggers
359
324
 
325
+ if sla and sla != parsed_prefect_file_contents.get("sla"):
326
+ deployment["sla"] = sla
327
+
360
328
  deployments = parsed_prefect_file_contents.get("deployments")
361
329
  if deployments is None:
362
330
  parsed_prefect_file_contents["deployments"] = [deployment]
@@ -373,105 +341,3 @@ def _save_deployment_to_prefect_file(
373
341
 
374
342
  with prefect_file.open(mode="w") as f:
375
343
  ryaml.dump(parsed_prefect_file_contents, f)
376
-
377
-
378
- # Only allow half of the open file limit to be open at once to allow for other
379
- # actors to open files.
380
- OPEN_FILE_SEMAPHORE = LazySemaphore(lambda: math.floor(get_open_file_limit() * 0.5))
381
-
382
-
383
- async def _find_flow_functions_in_file(filename: str) -> List[Dict]:
384
- decorator_name = "flow"
385
- decorator_module = "prefect"
386
- decorated_functions = []
387
- async with OPEN_FILE_SEMAPHORE:
388
- try:
389
- async with await anyio.open_file(filename) as f:
390
- try:
391
- tree = ast.parse(await f.read())
392
- except SyntaxError:
393
- if PREFECT_DEBUG_MODE:
394
- get_logger().debug(
395
- f"Could not parse {filename} as a Python file. Skipping."
396
- )
397
- return decorated_functions
398
- except Exception as exc:
399
- if PREFECT_DEBUG_MODE:
400
- get_logger().debug(f"Could not open {filename}: {exc}. Skipping.")
401
- return decorated_functions
402
-
403
- for node in ast.walk(tree):
404
- if isinstance(
405
- node,
406
- (
407
- ast.FunctionDef,
408
- ast.AsyncFunctionDef,
409
- ),
410
- ):
411
- for decorator in node.decorator_list:
412
- # handles @flow
413
- is_name_match = (
414
- isinstance(decorator, ast.Name) and decorator.id == decorator_name
415
- )
416
- # handles @flow()
417
- is_func_name_match = (
418
- isinstance(decorator, ast.Call)
419
- and isinstance(decorator.func, ast.Name)
420
- and decorator.func.id == decorator_name
421
- )
422
- # handles @prefect.flow
423
- is_module_attribute_match = (
424
- isinstance(decorator, ast.Attribute)
425
- and isinstance(decorator.value, ast.Name)
426
- and decorator.value.id == decorator_module
427
- and decorator.attr == decorator_name
428
- )
429
- # handles @prefect.flow()
430
- is_module_attribute_func_match = (
431
- isinstance(decorator, ast.Call)
432
- and isinstance(decorator.func, ast.Attribute)
433
- and decorator.func.attr == decorator_name
434
- and isinstance(decorator.func.value, ast.Name)
435
- and decorator.func.value.id == decorator_module
436
- )
437
- if is_name_match or is_module_attribute_match:
438
- decorated_functions.append(
439
- {
440
- "flow_name": node.name,
441
- "function_name": node.name,
442
- "filepath": str(filename),
443
- }
444
- )
445
- if is_func_name_match or is_module_attribute_func_match:
446
- name_kwarg_node = next(
447
- (kw for kw in decorator.keywords if kw.arg == "name"), None
448
- )
449
- flow_name = (
450
- name_kwarg_node.value.value
451
- if isinstance(name_kwarg_node, ast.Constant)
452
- else node.name
453
- )
454
- decorated_functions.append(
455
- {
456
- "flow_name": flow_name,
457
- "function_name": node.name,
458
- "filepath": str(filename),
459
- }
460
- )
461
- return decorated_functions
462
-
463
-
464
- async def _search_for_flow_functions(directory: str = ".") -> List[Dict]:
465
- """
466
- Search for flow functions in the provided directory. If no directory is provided,
467
- the current working directory is used.
468
-
469
- Returns:
470
- List[Dict]: the flow name, function name, and filepath of all flow functions found
471
- """
472
- path = anyio.Path(directory)
473
- coros = []
474
- async for file in path.rglob("*.py"):
475
- coros.append(_find_flow_functions_in_file(file))
476
-
477
- return [fn for file_fns in await asyncio.gather(*coros) for fn in file_fns]
@@ -10,9 +10,12 @@ from prefect.client.schemas import FlowRun
10
10
  from prefect.client.utilities import inject_client
11
11
  from prefect.context import FlowRunContext, TaskRunContext
12
12
  from prefect.logging import get_logger
13
- from prefect.results import BaseResult, ResultRecordMetadata
13
+ from prefect.results import ResultRecordMetadata
14
14
  from prefect.states import Pending, Scheduled
15
15
  from prefect.tasks import Task
16
+ from prefect.telemetry.run_telemetry import (
17
+ LABELS_TRACEPARENT_KEY,
18
+ )
16
19
  from prefect.utilities.asyncutils import sync_compatible
17
20
  from prefect.utilities.slugify import slugify
18
21
 
@@ -22,7 +25,6 @@ if TYPE_CHECKING:
22
25
 
23
26
  prefect.client.schemas.StateCreate.model_rebuild(
24
27
  _types_namespace={
25
- "BaseResult": BaseResult,
26
28
  "ResultRecordMetadata": ResultRecordMetadata,
27
29
  }
28
30
  )
@@ -156,6 +158,13 @@ async def run_deployment(
156
158
  else:
157
159
  parent_task_run_id = None
158
160
 
161
+ if flow_run_ctx and flow_run_ctx.flow_run:
162
+ traceparent = flow_run_ctx.flow_run.labels.get(LABELS_TRACEPARENT_KEY)
163
+ else:
164
+ traceparent = None
165
+
166
+ trace_labels = {LABELS_TRACEPARENT_KEY: traceparent} if traceparent else {}
167
+
159
168
  flow_run = await client.create_flow_run_from_deployment(
160
169
  deployment.id,
161
170
  parameters=parameters,
@@ -166,6 +175,7 @@ async def run_deployment(
166
175
  parent_task_run_id=parent_task_run_id,
167
176
  work_queue_name=work_queue_name,
168
177
  job_variables=job_variables,
178
+ labels=trace_labels,
169
179
  )
170
180
 
171
181
  flow_run_id = flow_run.id
@@ -33,26 +33,30 @@ import importlib
33
33
  import tempfile
34
34
  from datetime import datetime, timedelta
35
35
  from pathlib import Path
36
- from typing import TYPE_CHECKING, Any, Iterable, List, Optional, Union
36
+ from typing import TYPE_CHECKING, Any, ClassVar, Iterable, List, Optional, Union
37
37
  from uuid import UUID
38
38
 
39
+ from exceptiongroup import ExceptionGroup # novermin
39
40
  from pydantic import (
40
41
  BaseModel,
41
42
  ConfigDict,
42
43
  Field,
43
44
  PrivateAttr,
45
+ field_validator,
44
46
  model_validator,
45
47
  )
46
48
  from rich.console import Console
47
49
  from rich.progress import Progress, SpinnerColumn, TextColumn, track
48
50
  from rich.table import Table
49
51
 
52
+ from prefect._experimental.sla.objects import SlaTypes
50
53
  from prefect._internal.concurrency.api import create_call, from_async
51
54
  from prefect._internal.schemas.validators import (
52
55
  reconcile_paused_deployment,
53
56
  reconcile_schedules_runner,
54
57
  )
55
- from prefect.client.orchestration import get_client
58
+ from prefect.client.base import ServerType
59
+ from prefect.client.orchestration import PrefectClient, get_client
56
60
  from prefect.client.schemas.actions import DeploymentScheduleCreate
57
61
  from prefect.client.schemas.filters import WorkerFilter, WorkerFilterStatus
58
62
  from prefect.client.schemas.objects import (
@@ -127,9 +131,10 @@ class RunnerDeployment(BaseModel):
127
131
  job_variables: Settings used to override the values specified default base job template
128
132
  of the chosen work pool. Refer to the base job template of the chosen work pool for
129
133
  available settings.
134
+ _sla: (Experimental) SLA configuration for the deployment. May be removed or modified at any time. Currently only supported on Prefect Cloud.
130
135
  """
131
136
 
132
- model_config = ConfigDict(arbitrary_types_allowed=True)
137
+ model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
133
138
 
134
139
  name: str = Field(..., description="The name of the deployment.")
135
140
  flow_name: Optional[str] = Field(
@@ -206,6 +211,10 @@ class RunnerDeployment(BaseModel):
206
211
  " a built runner."
207
212
  ),
208
213
  )
214
+ # (Experimental) SLA configuration for the deployment. May be removed or modified at any time. Currently only supported on Prefect Cloud.
215
+ _sla: Optional[Union[SlaTypes, list[SlaTypes]]] = PrivateAttr(
216
+ default=None,
217
+ )
209
218
  _entrypoint_type: EntrypointType = PrivateAttr(
210
219
  default=EntrypointType.FILE_PATH,
211
220
  )
@@ -220,6 +229,13 @@ class RunnerDeployment(BaseModel):
220
229
  def entrypoint_type(self) -> EntrypointType:
221
230
  return self._entrypoint_type
222
231
 
232
+ @field_validator("name", mode="before")
233
+ @classmethod
234
+ def validate_name(cls, value: str) -> str:
235
+ if value.endswith(".py"):
236
+ return Path(value).stem
237
+ return value
238
+
223
239
  @model_validator(mode="after")
224
240
  def validate_automation_names(self):
225
241
  """Ensure that each trigger has a name for its automation if none is provided."""
@@ -343,8 +359,32 @@ class RunnerDeployment(BaseModel):
343
359
  trigger.set_deployment_id(deployment_id)
344
360
  await client.create_automation(trigger.as_automation())
345
361
 
362
+ # We plan to support SLA configuration on the Prefect Server in the future.
363
+ # For now, we only support it on Prefect Cloud.
364
+ if self._sla:
365
+ await self._create_slas(deployment_id, client)
366
+
346
367
  return deployment_id
347
368
 
369
+ async def _create_slas(self, deployment_id: UUID, client: PrefectClient):
370
+ if not isinstance(self._sla, list):
371
+ self._sla = [self._sla]
372
+
373
+ if client.server_type == ServerType.CLOUD:
374
+ exceptions = []
375
+ for sla in self._sla:
376
+ try:
377
+ sla.set_deployment_id(deployment_id)
378
+ await client.create_sla(sla)
379
+ except Exception as e:
380
+ exceptions.append(e)
381
+ if exceptions:
382
+ raise ExceptionGroup("Failed to create SLAs", exceptions) # novermin
383
+ else:
384
+ raise ValueError(
385
+ "SLA configuration is currently only supported on Prefect Cloud."
386
+ )
387
+
348
388
  @staticmethod
349
389
  def _construct_deployment_schedules(
350
390
  interval: Optional[
@@ -459,6 +499,7 @@ class RunnerDeployment(BaseModel):
459
499
  work_queue_name: Optional[str] = None,
460
500
  job_variables: Optional[dict[str, Any]] = None,
461
501
  entrypoint_type: EntrypointType = EntrypointType.FILE_PATH,
502
+ _sla: Optional[Union[SlaTypes, list[SlaTypes]]] = None, # experimental
462
503
  ) -> "RunnerDeployment":
463
504
  """
464
505
  Configure a deployment for a given flow.
@@ -489,6 +530,7 @@ class RunnerDeployment(BaseModel):
489
530
  job_variables: Settings used to override the values specified default base job template
490
531
  of the chosen work pool. Refer to the base job template of the chosen work pool for
491
532
  available settings.
533
+ _sla: (Experimental) SLA configuration for the deployment. May be removed or modified at any time. Currently only supported on Prefect Cloud.
492
534
  """
493
535
  constructed_schedules = cls._construct_deployment_schedules(
494
536
  interval=interval,
@@ -508,7 +550,7 @@ class RunnerDeployment(BaseModel):
508
550
  concurrency_options = None
509
551
 
510
552
  deployment = cls(
511
- name=Path(name).stem,
553
+ name=name,
512
554
  flow_name=flow.name,
513
555
  schedules=constructed_schedules,
514
556
  concurrency_limit=concurrency_limit,
@@ -524,6 +566,7 @@ class RunnerDeployment(BaseModel):
524
566
  work_queue_name=work_queue_name,
525
567
  job_variables=job_variables,
526
568
  )
569
+ deployment._sla = _sla
527
570
 
528
571
  if not deployment.entrypoint:
529
572
  no_file_location_error = (
@@ -599,6 +642,7 @@ class RunnerDeployment(BaseModel):
599
642
  work_pool_name: Optional[str] = None,
600
643
  work_queue_name: Optional[str] = None,
601
644
  job_variables: Optional[dict[str, Any]] = None,
645
+ _sla: Optional[Union[SlaTypes, list[SlaTypes]]] = None, # experimental
602
646
  ) -> "RunnerDeployment":
603
647
  """
604
648
  Configure a deployment for a given flow located at a given entrypoint.
@@ -630,6 +674,7 @@ class RunnerDeployment(BaseModel):
630
674
  job_variables: Settings used to override the values specified default base job template
631
675
  of the chosen work pool. Refer to the base job template of the chosen work pool for
632
676
  available settings.
677
+ _sla: (Experimental) SLA configuration for the deployment. May be removed or modified at any time. Currently only supported on Prefect Cloud.
633
678
  """
634
679
  from prefect.flows import load_flow_from_entrypoint
635
680
 
@@ -669,6 +714,7 @@ class RunnerDeployment(BaseModel):
669
714
  work_queue_name=work_queue_name,
670
715
  job_variables=job_variables,
671
716
  )
717
+ deployment._sla = _sla
672
718
  deployment._path = str(Path.cwd())
673
719
 
674
720
  cls._set_defaults_from_flow(deployment, flow)
@@ -700,6 +746,7 @@ class RunnerDeployment(BaseModel):
700
746
  work_pool_name: Optional[str] = None,
701
747
  work_queue_name: Optional[str] = None,
702
748
  job_variables: Optional[dict[str, Any]] = None,
749
+ _sla: Optional[Union[SlaTypes, list[SlaTypes]]] = None, # experimental
703
750
  ):
704
751
  """
705
752
  Create a RunnerDeployment from a flow located at a given entrypoint and stored in a
@@ -731,6 +778,7 @@ class RunnerDeployment(BaseModel):
731
778
  job_variables: Settings used to override the values specified default base job template
732
779
  of the chosen work pool. Refer to the base job template of the chosen work pool for
733
780
  available settings.
781
+ _sla: (Experimental) SLA configuration for the deployment. May be removed or modified at any time. Currently only supported on Prefect Cloud.
734
782
  """
735
783
  from prefect.flows import load_flow_from_entrypoint
736
784
 
@@ -779,6 +827,7 @@ class RunnerDeployment(BaseModel):
779
827
  work_queue_name=work_queue_name,
780
828
  job_variables=job_variables,
781
829
  )
830
+ deployment._sla = _sla
782
831
  deployment._path = str(storage.destination).replace(
783
832
  tmpdir, "$STORAGE_BASE_PATH"
784
833
  )
@@ -50,6 +50,7 @@ async def agit_clone(
50
50
  include_submodules: bool = False,
51
51
  access_token: Optional[str] = None,
52
52
  credentials: Optional["Block"] = None,
53
+ directories: Optional[list[str]] = None,
53
54
  ) -> dict[str, str]:
54
55
  """
55
56
  Asynchronously clones a git repository into the current working directory.
@@ -81,6 +82,7 @@ async def agit_clone(
81
82
  credentials=_credentials,
82
83
  branch=branch,
83
84
  include_submodules=include_submodules,
85
+ directories=directories,
84
86
  )
85
87
 
86
88
  await _pull_git_repository_with_retries(storage)
@@ -95,6 +97,7 @@ def git_clone(
95
97
  include_submodules: bool = False,
96
98
  access_token: Optional[str] = None,
97
99
  credentials: Optional["Block"] = None,
100
+ directories: Optional[list[str]] = None,
98
101
  ) -> dict[str, str]:
99
102
  """
100
103
  Clones a git repository into the current working directory.
@@ -107,6 +110,7 @@ def git_clone(
107
110
  the repository will be cloned using the default git credentials
108
111
  credentials: a GitHubCredentials, GitLabCredentials, or BitBucketCredentials block can be used to specify the
109
112
  credentials to use for cloning the repository.
113
+ directories: Specify directories you want to be included (uses git sparse-checkout)
110
114
 
111
115
  Returns:
112
116
  dict: a dictionary containing a `directory` key of the new directory that was created
@@ -164,6 +168,14 @@ def git_clone(
164
168
  - prefect.deployments.steps.git_clone:
165
169
  repository: git@github.com:org/repo.git
166
170
  ```
171
+
172
+ Clone a repository using sparse-checkout (allows specific folders of the repository to be checked out)
173
+ ```yaml
174
+ pull:
175
+ - prefect.deployments.steps.git_clone:
176
+ repository: https://github.com/org/repo.git
177
+ directories: ["dir_1", "dir_2", "prefect"]
178
+ ```
167
179
  """
168
180
  if access_token and credentials:
169
181
  raise ValueError(
@@ -177,6 +189,7 @@ def git_clone(
177
189
  credentials=_credentials,
178
190
  branch=branch,
179
191
  include_submodules=include_submodules,
192
+ directories=directories,
180
193
  )
181
194
 
182
195
  run_coro_as_sync(_pull_git_repository_with_retries(storage))
prefect/engine.py CHANGED
@@ -31,16 +31,29 @@ if __name__ == "__main__":
31
31
 
32
32
  try:
33
33
  from prefect.flow_engine import (
34
- load_flow_and_flow_run,
34
+ flow_run_logger,
35
+ load_flow,
36
+ load_flow_run,
35
37
  run_flow,
36
38
  )
37
39
 
38
- flow_run, flow = load_flow_and_flow_run(flow_run_id=flow_run_id)
40
+ flow_run = load_flow_run(flow_run_id=flow_run_id)
41
+ run_logger = flow_run_logger(flow_run=flow_run)
42
+
43
+ try:
44
+ flow = load_flow(flow_run)
45
+ except Exception:
46
+ run_logger.error(
47
+ "Unexpected exception encountered when trying to load flow",
48
+ exc_info=True,
49
+ )
50
+ raise
51
+
39
52
  # run the flow
40
53
  if flow.isasync:
41
- run_coro_as_sync(run_flow(flow, flow_run=flow_run))
54
+ run_coro_as_sync(run_flow(flow, flow_run=flow_run, error_logger=run_logger))
42
55
  else:
43
- run_flow(flow, flow_run=flow_run)
56
+ run_flow(flow, flow_run=flow_run, error_logger=run_logger)
44
57
 
45
58
  except Abort as exc:
46
59
  engine_logger.info(
prefect/events/clients.py CHANGED
@@ -16,6 +16,7 @@ from typing import (
16
16
  cast,
17
17
  )
18
18
  from urllib.parse import urlparse
19
+ from urllib.request import proxy_bypass
19
20
  from uuid import UUID
20
21
 
21
22
  import orjson
@@ -95,6 +96,9 @@ class WebsocketProxyConnect(Connect):
95
96
  u = urlparse(uri)
96
97
  host = u.hostname
97
98
 
99
+ if not host:
100
+ raise ValueError(f"Invalid URI {uri}, no hostname found")
101
+
98
102
  if u.scheme == "ws":
99
103
  port = u.port or 80
100
104
  proxy_url = os.environ.get("HTTP_PROXY")
@@ -107,7 +111,9 @@ class WebsocketProxyConnect(Connect):
107
111
  "Unsupported scheme %s. Expected 'ws' or 'wss'. " % u.scheme
108
112
  )
109
113
 
110
- self._proxy = Proxy.from_url(proxy_url) if proxy_url else None
114
+ self._proxy = (
115
+ Proxy.from_url(proxy_url) if proxy_url and not proxy_bypass(host) else None
116
+ )
111
117
  self._host = host
112
118
  self._port = port
113
119
 
@@ -2,6 +2,7 @@ import copy
2
2
  from collections import defaultdict
3
3
  from typing import (
4
4
  Any,
5
+ ClassVar,
5
6
  Dict,
6
7
  Iterable,
7
8
  List,
@@ -108,7 +109,7 @@ def _validate_related_resources(value) -> List:
108
109
  class Event(PrefectBaseModel):
109
110
  """The client-side view of an event that has happened to a Resource"""
110
111
 
111
- model_config = ConfigDict(extra="ignore")
112
+ model_config: ClassVar[ConfigDict] = ConfigDict(extra="ignore")
112
113
 
113
114
  occurred: DateTime = Field(
114
115
  default_factory=lambda: DateTime.now("UTC"),
@@ -177,7 +178,7 @@ class ReceivedEvent(Event):
177
178
  """The server-side view of an event that has happened to a Resource after it has
178
179
  been received by the server"""
179
180
 
180
- model_config = ConfigDict(from_attributes=True)
181
+ model_config: ClassVar[ConfigDict] = ConfigDict(from_attributes=True)
181
182
 
182
183
  received: DateTime = Field(
183
184
  ...,
prefect/filesystems.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import abc
2
4
  import urllib.parse
3
5
  from pathlib import Path
@@ -92,7 +94,9 @@ class LocalFileSystem(WritableFileSystem, WritableDeploymentStorage):
92
94
  )
93
95
 
94
96
  @field_validator("basepath", mode="before")
95
- def cast_pathlib(cls, value):
97
+ def cast_pathlib(cls, value: str | Path | None) -> str | None:
98
+ if value is None:
99
+ return value
96
100
  return stringify_path(value)
97
101
 
98
102
  def _resolve_path(self, path: str, validate: bool = False) -> Path:
@@ -132,7 +136,7 @@ class LocalFileSystem(WritableFileSystem, WritableDeploymentStorage):
132
136
  Defaults to copying the entire contents of the block's basepath to the current working directory.
133
137
  """
134
138
  if not from_path:
135
- from_path = Path(self.basepath).expanduser().resolve()
139
+ from_path = Path(self.basepath or ".").expanduser().resolve()
136
140
  else:
137
141
  from_path = self._resolve_path(from_path)
138
142