prefect-client 3.1.14__py3-none-any.whl → 3.2.0__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 (98) hide show
  1. prefect/__main__.py +4 -0
  2. prefect/_experimental/lineage.py +40 -22
  3. prefect/_experimental/sla/objects.py +29 -1
  4. prefect/_internal/compatibility/deprecated.py +4 -4
  5. prefect/_internal/compatibility/migration.py +1 -1
  6. prefect/_internal/concurrency/calls.py +1 -2
  7. prefect/_internal/concurrency/cancellation.py +2 -4
  8. prefect/_internal/concurrency/services.py +1 -1
  9. prefect/_internal/concurrency/threads.py +3 -3
  10. prefect/_internal/schemas/bases.py +3 -11
  11. prefect/_internal/schemas/validators.py +36 -60
  12. prefect/_result_records.py +235 -0
  13. prefect/_version.py +3 -3
  14. prefect/agent.py +1 -0
  15. prefect/artifacts.py +408 -105
  16. prefect/automations.py +4 -8
  17. prefect/blocks/core.py +1 -1
  18. prefect/blocks/notifications.py +13 -8
  19. prefect/cache_policies.py +2 -0
  20. prefect/client/base.py +7 -8
  21. prefect/client/collections.py +3 -6
  22. prefect/client/orchestration/__init__.py +15 -263
  23. prefect/client/orchestration/_deployments/client.py +14 -6
  24. prefect/client/orchestration/_flow_runs/client.py +10 -6
  25. prefect/client/orchestration/_work_pools/__init__.py +0 -0
  26. prefect/client/orchestration/_work_pools/client.py +598 -0
  27. prefect/client/orchestration/base.py +9 -2
  28. prefect/client/schemas/actions.py +77 -3
  29. prefect/client/schemas/objects.py +22 -50
  30. prefect/client/schemas/schedules.py +11 -22
  31. prefect/client/types/flexible_schedule_list.py +2 -1
  32. prefect/context.py +2 -3
  33. prefect/deployments/base.py +13 -16
  34. prefect/deployments/flow_runs.py +1 -1
  35. prefect/deployments/runner.py +236 -47
  36. prefect/deployments/schedules.py +7 -1
  37. prefect/engine.py +4 -9
  38. prefect/events/clients.py +39 -0
  39. prefect/events/schemas/automations.py +4 -2
  40. prefect/events/utilities.py +15 -13
  41. prefect/exceptions.py +1 -1
  42. prefect/flow_engine.py +119 -0
  43. prefect/flow_runs.py +4 -8
  44. prefect/flows.py +282 -31
  45. prefect/infrastructure/__init__.py +1 -0
  46. prefect/infrastructure/base.py +1 -0
  47. prefect/infrastructure/provisioners/__init__.py +3 -6
  48. prefect/infrastructure/provisioners/coiled.py +3 -3
  49. prefect/infrastructure/provisioners/container_instance.py +1 -0
  50. prefect/infrastructure/provisioners/ecs.py +6 -6
  51. prefect/infrastructure/provisioners/modal.py +3 -3
  52. prefect/input/run_input.py +5 -7
  53. prefect/locking/filesystem.py +4 -3
  54. prefect/main.py +1 -1
  55. prefect/results.py +42 -249
  56. prefect/runner/runner.py +9 -4
  57. prefect/runner/server.py +5 -5
  58. prefect/runner/storage.py +12 -10
  59. prefect/runner/submit.py +2 -4
  60. prefect/runtime/task_run.py +37 -9
  61. prefect/schedules.py +231 -0
  62. prefect/serializers.py +5 -5
  63. prefect/settings/__init__.py +2 -1
  64. prefect/settings/base.py +3 -3
  65. prefect/settings/models/root.py +4 -0
  66. prefect/settings/models/server/services.py +50 -9
  67. prefect/settings/sources.py +4 -4
  68. prefect/states.py +42 -11
  69. prefect/task_engine.py +10 -10
  70. prefect/task_runners.py +11 -22
  71. prefect/task_worker.py +9 -9
  72. prefect/tasks.py +28 -45
  73. prefect/telemetry/bootstrap.py +4 -6
  74. prefect/telemetry/services.py +2 -4
  75. prefect/types/__init__.py +2 -1
  76. prefect/types/_datetime.py +28 -1
  77. prefect/utilities/_engine.py +0 -1
  78. prefect/utilities/asyncutils.py +4 -8
  79. prefect/utilities/collections.py +13 -22
  80. prefect/utilities/dispatch.py +2 -4
  81. prefect/utilities/dockerutils.py +6 -6
  82. prefect/utilities/importtools.py +1 -68
  83. prefect/utilities/names.py +1 -1
  84. prefect/utilities/processutils.py +3 -6
  85. prefect/utilities/pydantic.py +4 -6
  86. prefect/utilities/render_swagger.py +1 -1
  87. prefect/utilities/schema_tools/hydration.py +6 -5
  88. prefect/utilities/templating.py +21 -8
  89. prefect/utilities/visualization.py +2 -4
  90. prefect/workers/base.py +3 -3
  91. prefect/workers/block.py +1 -0
  92. prefect/workers/cloud.py +1 -0
  93. prefect/workers/process.py +1 -0
  94. {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/METADATA +1 -1
  95. {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/RECORD +98 -93
  96. {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/LICENSE +0 -0
  97. {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/WHEEL +0 -0
  98. {prefect_client-3.1.14.dist-info → prefect_client-3.2.0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from datetime import timedelta
2
- from typing import Any, Dict, List, Optional, Union
4
+ from typing import Any
3
5
  from uuid import UUID
4
6
 
5
- import pendulum
7
+ from prefect.types import DateTime
6
8
 
7
9
  from .clients import (
8
10
  AssertingEventsClient,
@@ -18,16 +20,16 @@ TIGHT_TIMING = timedelta(minutes=5)
18
20
 
19
21
  def emit_event(
20
22
  event: str,
21
- resource: Dict[str, str],
22
- occurred: Optional[pendulum.DateTime] = None,
23
- related: Optional[Union[List[Dict[str, str]], List[RelatedResource]]] = None,
24
- payload: Optional[Dict[str, Any]] = None,
25
- id: Optional[UUID] = None,
26
- follows: Optional[Event] = None,
27
- **kwargs: Optional[Dict[str, Any]],
28
- ) -> Optional[Event]:
23
+ resource: dict[str, str],
24
+ occurred: DateTime | None = None,
25
+ related: list[dict[str, str]] | list[RelatedResource] | None = None,
26
+ payload: dict[str, Any] | None = None,
27
+ id: UUID | None = None,
28
+ follows: Event | None = None,
29
+ **kwargs: dict[str, Any] | None,
30
+ ) -> Event | None:
29
31
  """
30
- Send an event to Prefect Cloud.
32
+ Send an event to Prefect.
31
33
 
32
34
  Args:
33
35
  event: The name of the event that happened.
@@ -60,14 +62,14 @@ def emit_event(
60
62
  if worker_instance.client_type not in operational_clients:
61
63
  return None
62
64
 
63
- event_kwargs: Dict[str, Any] = {
65
+ event_kwargs: dict[str, Any] = {
64
66
  "event": event,
65
67
  "resource": resource,
66
68
  **kwargs,
67
69
  }
68
70
 
69
71
  if occurred is None:
70
- occurred = pendulum.now("UTC")
72
+ occurred = DateTime.now("UTC")
71
73
  event_kwargs["occurred"] = occurred
72
74
 
73
75
  if related is not None:
prefect/exceptions.py CHANGED
@@ -161,7 +161,7 @@ class ParameterTypeError(PrefectException):
161
161
  @classmethod
162
162
  def from_validation_error(cls, exc: ValidationError) -> Self:
163
163
  bad_params = [
164
- f'{".".join(str(item) for item in err["loc"])}: {err["msg"]}'
164
+ f"{'.'.join(str(item) for item in err['loc'])}: {err['msg']}"
165
165
  for err in exc.errors()
166
166
  ]
167
167
  msg = "Flow run received invalid parameters:\n - " + "\n - ".join(bad_params)
prefect/flow_engine.py CHANGED
@@ -2,10 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import multiprocessing
6
+ import multiprocessing.context
5
7
  import os
6
8
  import time
7
9
  from contextlib import ExitStack, asynccontextmanager, contextmanager, nullcontext
8
10
  from dataclasses import dataclass, field
11
+ from functools import wraps
9
12
  from typing import (
10
13
  Any,
11
14
  AsyncGenerator,
@@ -37,9 +40,12 @@ from prefect.concurrency.v1.context import ConcurrencyContext as ConcurrencyCont
37
40
  from prefect.context import (
38
41
  AsyncClientContext,
39
42
  FlowRunContext,
43
+ SettingsContext,
40
44
  SyncClientContext,
41
45
  TagsContext,
46
+ get_settings_context,
42
47
  hydrated_context,
48
+ serialize_context,
43
49
  )
44
50
  from prefect.exceptions import (
45
51
  Abort,
@@ -62,6 +68,8 @@ from prefect.results import (
62
68
  should_persist_result,
63
69
  )
64
70
  from prefect.settings import PREFECT_DEBUG_MODE
71
+ from prefect.settings.context import get_current_settings
72
+ from prefect.settings.models.root import Settings
65
73
  from prefect.states import (
66
74
  Failed,
67
75
  Pending,
@@ -83,6 +91,7 @@ from prefect.utilities.annotations import NotSet
83
91
  from prefect.utilities.asyncutils import run_coro_as_sync
84
92
  from prefect.utilities.callables import (
85
93
  call_with_parameters,
94
+ cloudpickle_wrapped_call,
86
95
  get_call_parameters,
87
96
  parameters_to_args_kwargs,
88
97
  )
@@ -1509,6 +1518,8 @@ def run_flow(
1509
1518
  ret_val = run_flow_async(**kwargs)
1510
1519
  else:
1511
1520
  ret_val = run_flow_sync(**kwargs)
1521
+ except (Abort, Pause):
1522
+ raise
1512
1523
  except:
1513
1524
  if error_logger:
1514
1525
  error_logger.error(
@@ -1533,3 +1544,111 @@ def _flow_parameters(
1533
1544
  parameters = flow_run.parameters if flow_run else {}
1534
1545
  call_args, call_kwargs = parameters_to_args_kwargs(flow.fn, parameters)
1535
1546
  return get_call_parameters(flow.fn, call_args, call_kwargs)
1547
+
1548
+
1549
+ def run_flow_in_subprocess(
1550
+ flow: "Flow[..., Any]",
1551
+ flow_run: "FlowRun | None" = None,
1552
+ parameters: dict[str, Any] | None = None,
1553
+ wait_for: Iterable[PrefectFuture[Any]] | None = None,
1554
+ context: dict[str, Any] | None = None,
1555
+ ) -> multiprocessing.context.SpawnProcess:
1556
+ """
1557
+ Run a flow in a subprocess.
1558
+
1559
+ Note the result of the flow will only be accessible if the flow is configured to
1560
+ persist its result.
1561
+
1562
+ Args:
1563
+ flow: The flow to run.
1564
+ flow_run: The flow run object containing run metadata.
1565
+ parameters: The parameters to use when invoking the flow.
1566
+ wait_for: The futures to wait for before starting the flow.
1567
+ context: A serialized context to hydrate before running the flow. If not provided,
1568
+ the current context will be used. A serialized context should be provided if
1569
+ this function is called in a separate memory space from the parent run (e.g.
1570
+ in a subprocess or on another machine).
1571
+
1572
+ Returns:
1573
+ A multiprocessing.context.SpawnProcess representing the process that is running the flow.
1574
+ """
1575
+ from prefect.flow_engine import run_flow
1576
+
1577
+ @wraps(run_flow)
1578
+ def run_flow_with_env(
1579
+ *args: Any,
1580
+ env: dict[str, str] | None = None,
1581
+ **kwargs: Any,
1582
+ ):
1583
+ """
1584
+ Wrapper function to update environment variables and settings before running the flow.
1585
+ """
1586
+ engine_logger = logging.getLogger("prefect.engine")
1587
+
1588
+ os.environ.update(env or {})
1589
+ settings_context = get_settings_context()
1590
+ # Create a new settings context with a new settings object to pick up the updated
1591
+ # environment variables
1592
+ with SettingsContext(
1593
+ profile=settings_context.profile,
1594
+ settings=Settings(),
1595
+ ):
1596
+ try:
1597
+ maybe_coro = run_flow(*args, **kwargs)
1598
+ if asyncio.iscoroutine(maybe_coro):
1599
+ # This is running in a brand new process, so there won't be an existing
1600
+ # event loop.
1601
+ asyncio.run(maybe_coro)
1602
+ except Abort:
1603
+ if flow_run:
1604
+ msg = f"Execution of flow run '{flow_run.id}' aborted by orchestrator."
1605
+ else:
1606
+ msg = "Execution aborted by orchestrator."
1607
+ engine_logger.info(msg)
1608
+ exit(0)
1609
+ except Pause:
1610
+ if flow_run:
1611
+ msg = f"Execution of flow run '{flow_run.id}' is paused."
1612
+ else:
1613
+ msg = "Execution is paused."
1614
+ engine_logger.info(msg)
1615
+ exit(0)
1616
+ except Exception:
1617
+ if flow_run:
1618
+ msg = f"Execution of flow run '{flow_run.id}' exited with unexpected exception"
1619
+ else:
1620
+ msg = "Execution exited with unexpected exception"
1621
+ engine_logger.error(msg, exc_info=True)
1622
+ exit(1)
1623
+ except BaseException:
1624
+ if flow_run:
1625
+ msg = f"Execution of flow run '{flow_run.id}' interrupted by base exception"
1626
+ else:
1627
+ msg = "Execution interrupted by base exception"
1628
+ engine_logger.error(msg, exc_info=True)
1629
+ # Let the exit code be determined by the base exception type
1630
+ raise
1631
+
1632
+ ctx = multiprocessing.get_context("spawn")
1633
+
1634
+ context = context or serialize_context()
1635
+
1636
+ process = ctx.Process(
1637
+ target=cloudpickle_wrapped_call(
1638
+ run_flow_with_env,
1639
+ env=get_current_settings().to_environment_variables(exclude_unset=True)
1640
+ | os.environ
1641
+ | {
1642
+ # TODO: make this a thing we can pass into the engine
1643
+ "PREFECT__ENABLE_CANCELLATION_AND_CRASHED_HOOKS": "false",
1644
+ },
1645
+ flow=flow,
1646
+ flow_run=flow_run,
1647
+ parameters=parameters,
1648
+ wait_for=wait_for,
1649
+ context=context,
1650
+ ),
1651
+ )
1652
+ process.start()
1653
+
1654
+ return process
prefect/flow_runs.py CHANGED
@@ -139,8 +139,7 @@ async def pause_flow_run(
139
139
  timeout: int = 3600,
140
140
  poll_interval: int = 10,
141
141
  key: Optional[str] = None,
142
- ) -> None:
143
- ...
142
+ ) -> None: ...
144
143
 
145
144
 
146
145
  @overload
@@ -149,8 +148,7 @@ async def pause_flow_run(
149
148
  timeout: int = 3600,
150
149
  poll_interval: int = 10,
151
150
  key: Optional[str] = None,
152
- ) -> T:
153
- ...
151
+ ) -> T: ...
154
152
 
155
153
 
156
154
  @sync_compatible
@@ -308,8 +306,7 @@ async def suspend_flow_run(
308
306
  timeout: Optional[int] = 3600,
309
307
  key: Optional[str] = None,
310
308
  client: Optional[PrefectClient] = None,
311
- ) -> None:
312
- ...
309
+ ) -> None: ...
313
310
 
314
311
 
315
312
  @overload
@@ -319,8 +316,7 @@ async def suspend_flow_run(
319
316
  timeout: Optional[int] = 3600,
320
317
  key: Optional[str] = None,
321
318
  client: Optional[PrefectClient] = None,
322
- ) -> T:
323
- ...
319
+ ) -> T: ...
324
320
 
325
321
 
326
322
  @sync_compatible