prefect-client 3.1.5__py3-none-any.whl → 3.1.7__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 (114) hide show
  1. prefect/__init__.py +3 -0
  2. prefect/_experimental/__init__.py +0 -0
  3. prefect/_experimental/lineage.py +181 -0
  4. prefect/_internal/compatibility/async_dispatch.py +38 -9
  5. prefect/_internal/compatibility/migration.py +1 -1
  6. prefect/_internal/concurrency/api.py +52 -52
  7. prefect/_internal/concurrency/calls.py +59 -35
  8. prefect/_internal/concurrency/cancellation.py +34 -18
  9. prefect/_internal/concurrency/event_loop.py +7 -6
  10. prefect/_internal/concurrency/threads.py +41 -33
  11. prefect/_internal/concurrency/waiters.py +28 -21
  12. prefect/_internal/pydantic/v1_schema.py +2 -2
  13. prefect/_internal/pydantic/v2_schema.py +10 -9
  14. prefect/_internal/pydantic/v2_validated_func.py +15 -10
  15. prefect/_internal/retries.py +15 -6
  16. prefect/_internal/schemas/bases.py +11 -8
  17. prefect/_internal/schemas/validators.py +7 -5
  18. prefect/_version.py +3 -3
  19. prefect/automations.py +53 -47
  20. prefect/blocks/abstract.py +12 -10
  21. prefect/blocks/core.py +148 -19
  22. prefect/blocks/system.py +2 -1
  23. prefect/cache_policies.py +11 -11
  24. prefect/client/__init__.py +3 -1
  25. prefect/client/base.py +36 -37
  26. prefect/client/cloud.py +26 -19
  27. prefect/client/collections.py +2 -2
  28. prefect/client/orchestration.py +430 -273
  29. prefect/client/schemas/__init__.py +24 -0
  30. prefect/client/schemas/actions.py +128 -121
  31. prefect/client/schemas/filters.py +1 -1
  32. prefect/client/schemas/objects.py +114 -85
  33. prefect/client/schemas/responses.py +19 -20
  34. prefect/client/schemas/schedules.py +136 -93
  35. prefect/client/subscriptions.py +30 -15
  36. prefect/client/utilities.py +46 -36
  37. prefect/concurrency/asyncio.py +6 -9
  38. prefect/concurrency/sync.py +35 -5
  39. prefect/context.py +40 -32
  40. prefect/deployments/flow_runs.py +6 -8
  41. prefect/deployments/runner.py +14 -14
  42. prefect/deployments/steps/core.py +3 -1
  43. prefect/deployments/steps/pull.py +60 -12
  44. prefect/docker/__init__.py +1 -1
  45. prefect/events/clients.py +55 -4
  46. prefect/events/filters.py +1 -1
  47. prefect/events/related.py +2 -1
  48. prefect/events/schemas/events.py +26 -21
  49. prefect/events/utilities.py +3 -2
  50. prefect/events/worker.py +8 -0
  51. prefect/filesystems.py +3 -3
  52. prefect/flow_engine.py +87 -87
  53. prefect/flow_runs.py +7 -5
  54. prefect/flows.py +218 -176
  55. prefect/logging/configuration.py +1 -1
  56. prefect/logging/highlighters.py +1 -2
  57. prefect/logging/loggers.py +30 -20
  58. prefect/main.py +17 -24
  59. prefect/results.py +43 -22
  60. prefect/runner/runner.py +43 -21
  61. prefect/runner/server.py +30 -32
  62. prefect/runner/storage.py +3 -3
  63. prefect/runner/submit.py +3 -6
  64. prefect/runner/utils.py +6 -6
  65. prefect/runtime/flow_run.py +7 -0
  66. prefect/serializers.py +28 -24
  67. prefect/settings/constants.py +2 -2
  68. prefect/settings/legacy.py +1 -1
  69. prefect/settings/models/experiments.py +5 -0
  70. prefect/settings/models/server/events.py +10 -0
  71. prefect/task_engine.py +87 -26
  72. prefect/task_runners.py +2 -2
  73. prefect/task_worker.py +43 -25
  74. prefect/tasks.py +148 -142
  75. prefect/telemetry/bootstrap.py +15 -2
  76. prefect/telemetry/instrumentation.py +1 -1
  77. prefect/telemetry/processors.py +10 -7
  78. prefect/telemetry/run_telemetry.py +231 -0
  79. prefect/transactions.py +14 -14
  80. prefect/types/__init__.py +5 -5
  81. prefect/utilities/_engine.py +96 -0
  82. prefect/utilities/annotations.py +25 -18
  83. prefect/utilities/asyncutils.py +126 -140
  84. prefect/utilities/callables.py +87 -78
  85. prefect/utilities/collections.py +278 -117
  86. prefect/utilities/compat.py +13 -21
  87. prefect/utilities/context.py +6 -5
  88. prefect/utilities/dispatch.py +23 -12
  89. prefect/utilities/dockerutils.py +33 -32
  90. prefect/utilities/engine.py +126 -239
  91. prefect/utilities/filesystem.py +18 -15
  92. prefect/utilities/hashing.py +10 -11
  93. prefect/utilities/importtools.py +40 -27
  94. prefect/utilities/math.py +9 -5
  95. prefect/utilities/names.py +3 -3
  96. prefect/utilities/processutils.py +121 -57
  97. prefect/utilities/pydantic.py +41 -36
  98. prefect/utilities/render_swagger.py +22 -12
  99. prefect/utilities/schema_tools/__init__.py +2 -1
  100. prefect/utilities/schema_tools/hydration.py +50 -43
  101. prefect/utilities/schema_tools/validation.py +52 -42
  102. prefect/utilities/services.py +13 -12
  103. prefect/utilities/templating.py +45 -45
  104. prefect/utilities/text.py +2 -1
  105. prefect/utilities/timeout.py +4 -4
  106. prefect/utilities/urls.py +9 -4
  107. prefect/utilities/visualization.py +46 -24
  108. prefect/variables.py +136 -27
  109. prefect/workers/base.py +15 -8
  110. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/METADATA +5 -2
  111. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/RECORD +114 -110
  112. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/LICENSE +0 -0
  113. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/WHEEL +0 -0
  114. {prefect_client-3.1.5.dist-info → prefect_client-3.1.7.dist-info}/top_level.txt +0 -0
prefect/flow_engine.py CHANGED
@@ -2,7 +2,7 @@ import asyncio
2
2
  import logging
3
3
  import os
4
4
  import time
5
- from contextlib import ExitStack, asynccontextmanager, contextmanager
5
+ from contextlib import ExitStack, asynccontextmanager, contextmanager, nullcontext
6
6
  from dataclasses import dataclass, field
7
7
  from typing import (
8
8
  Any,
@@ -23,11 +23,9 @@ from typing import (
23
23
  from uuid import UUID
24
24
 
25
25
  from anyio import CancelScope
26
- from opentelemetry import trace
27
- from opentelemetry.trace import Tracer, get_tracer
26
+ from opentelemetry import propagate, trace
28
27
  from typing_extensions import ParamSpec
29
28
 
30
- import prefect
31
29
  from prefect import Task
32
30
  from prefect.client.orchestration import PrefectClient, SyncPrefectClient, get_client
33
31
  from prefect.client.schemas import FlowRun, TaskRun
@@ -72,6 +70,14 @@ from prefect.states import (
72
70
  exception_to_failed_state,
73
71
  return_value_to_state,
74
72
  )
73
+ from prefect.telemetry.run_telemetry import (
74
+ LABELS_TRACEPARENT_KEY,
75
+ TRACEPARENT_KEY,
76
+ OTELSetter,
77
+ RunTelemetry,
78
+ )
79
+ from prefect.types import KeyValueLabels
80
+ from prefect.utilities._engine import get_hook_name, resolve_custom_flow_run_name
75
81
  from prefect.utilities.annotations import NotSet
76
82
  from prefect.utilities.asyncutils import run_coro_as_sync
77
83
  from prefect.utilities.callables import (
@@ -81,8 +87,6 @@ from prefect.utilities.callables import (
81
87
  )
82
88
  from prefect.utilities.collections import visit_collection
83
89
  from prefect.utilities.engine import (
84
- _get_hook_name,
85
- _resolve_custom_flow_run_name,
86
90
  capture_sigterm,
87
91
  link_state_to_result,
88
92
  propose_state,
@@ -133,10 +137,7 @@ class BaseFlowRunEngine(Generic[P, R]):
133
137
  _is_started: bool = False
134
138
  short_circuit: bool = False
135
139
  _flow_run_name_set: bool = False
136
- _tracer: Tracer = field(
137
- default_factory=lambda: get_tracer("prefect", prefect.__version__)
138
- )
139
- _span: Optional[trace.Span] = None
140
+ _telemetry: RunTelemetry = field(default_factory=RunTelemetry)
140
141
 
141
142
  def __post_init__(self):
142
143
  if self.flow is None and self.flow_run_id is None:
@@ -149,21 +150,6 @@ class BaseFlowRunEngine(Generic[P, R]):
149
150
  def state(self) -> State:
150
151
  return self.flow_run.state # type: ignore
151
152
 
152
- def _end_span_on_success(self):
153
- if not self._span:
154
- return
155
- self._span.set_status(trace.Status(trace.StatusCode.OK))
156
- self._span.end(time.time_ns())
157
- self._span = None
158
-
159
- def _end_span_on_error(self, exc: BaseException, description: Optional[str]):
160
- if not self._span:
161
- return
162
- self._span.record_exception(exc)
163
- self._span.set_status(trace.Status(trace.StatusCode.ERROR, description))
164
- self._span.end(time.time_ns())
165
- self._span = None
166
-
167
153
  def is_running(self) -> bool:
168
154
  if getattr(self, "flow_run", None) is None:
169
155
  return False
@@ -178,6 +164,39 @@ class BaseFlowRunEngine(Generic[P, R]):
178
164
  if hasattr(self.flow.task_runner, "cancel_all"):
179
165
  self.flow.task_runner.cancel_all() # type: ignore
180
166
 
167
+ def _update_otel_labels(
168
+ self, span: trace.Span, client: Union[SyncPrefectClient, PrefectClient]
169
+ ):
170
+ parent_flow_run_ctx = FlowRunContext.get()
171
+
172
+ if parent_flow_run_ctx and parent_flow_run_ctx.flow_run:
173
+ if traceparent := parent_flow_run_ctx.flow_run.labels.get(
174
+ LABELS_TRACEPARENT_KEY
175
+ ):
176
+ carrier: KeyValueLabels = {TRACEPARENT_KEY: traceparent}
177
+ propagate.get_global_textmap().inject(
178
+ carrier={TRACEPARENT_KEY: traceparent},
179
+ setter=OTELSetter(),
180
+ )
181
+
182
+ else:
183
+ carrier: KeyValueLabels = {}
184
+ propagate.get_global_textmap().inject(
185
+ carrier,
186
+ context=trace.set_span_in_context(span),
187
+ setter=OTELSetter(),
188
+ )
189
+ if carrier.get(TRACEPARENT_KEY):
190
+ if self.flow_run:
191
+ client.update_flow_run_labels(
192
+ flow_run_id=self.flow_run.id,
193
+ labels={LABELS_TRACEPARENT_KEY: carrier[TRACEPARENT_KEY]},
194
+ )
195
+ else:
196
+ self.logger.info(
197
+ f"Tried to set traceparent {carrier[TRACEPARENT_KEY]} for flow run, but None was found"
198
+ )
199
+
181
200
 
182
201
  @dataclass
183
202
  class FlowRunEngine(BaseFlowRunEngine[P, R]):
@@ -281,16 +300,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
281
300
  self.flow_run.state_name = state.name # type: ignore
282
301
  self.flow_run.state_type = state.type # type: ignore
283
302
 
284
- if self._span:
285
- self._span.add_event(
286
- state.name,
287
- {
288
- "prefect.state.message": state.message or "",
289
- "prefect.state.type": state.type,
290
- "prefect.state.name": state.name or state.type,
291
- "prefect.state.id": str(state.id),
292
- },
293
- )
303
+ self._telemetry.update_state(state)
294
304
  return state
295
305
 
296
306
  def result(self, raise_on_failure: bool = True) -> "Union[R, State, None]":
@@ -340,7 +350,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
340
350
  self.set_state(terminal_state)
341
351
  self._return_value = resolved_result
342
352
 
343
- self._end_span_on_success()
353
+ self._telemetry.end_span_on_success()
344
354
 
345
355
  return result
346
356
 
@@ -372,8 +382,8 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
372
382
  )
373
383
  state = self.set_state(Running())
374
384
  self._raised = exc
375
-
376
- self._end_span_on_error(exc, state.message)
385
+ self._telemetry.record_exception(exc)
386
+ self._telemetry.end_span_on_failure(state.message)
377
387
 
378
388
  return state
379
389
 
@@ -392,8 +402,8 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
392
402
  )
393
403
  self.set_state(state)
394
404
  self._raised = exc
395
-
396
- self._end_span_on_error(exc, message)
405
+ self._telemetry.record_exception(exc)
406
+ self._telemetry.end_span_on_failure(message)
397
407
 
398
408
  def handle_crash(self, exc: BaseException) -> None:
399
409
  state = run_coro_as_sync(exception_to_crashed_state(exc))
@@ -401,8 +411,8 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
401
411
  self.logger.debug("Crash details:", exc_info=exc)
402
412
  self.set_state(state, force=True)
403
413
  self._raised = exc
404
-
405
- self._end_span_on_error(exc, state.message)
414
+ self._telemetry.record_exception(exc)
415
+ self._telemetry.end_span_on_failure(state.message if state else None)
406
416
 
407
417
  def load_subflow_run(
408
418
  self,
@@ -537,7 +547,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
537
547
  hooks = None
538
548
 
539
549
  for hook in hooks or []:
540
- hook_name = _get_hook_name(hook)
550
+ hook_name = get_hook_name(hook)
541
551
 
542
552
  try:
543
553
  self.logger.info(
@@ -600,7 +610,7 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
600
610
 
601
611
  # update the flow run name if necessary
602
612
  if not self._flow_run_name_set and self.flow.flow_run_name:
603
- flow_run_name = _resolve_custom_flow_run_name(
613
+ flow_run_name = resolve_custom_flow_run_name(
604
614
  flow=self.flow, parameters=self.parameters
605
615
  )
606
616
  self.client.set_flow_run_name(
@@ -647,15 +657,11 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
647
657
  empirical_policy=self.flow_run.empirical_policy,
648
658
  )
649
659
 
650
- self._span = self._tracer.start_span(
651
- name=self.flow_run.name,
652
- attributes={
653
- **self.flow_run.labels,
654
- "prefect.run.type": "flow",
655
- "prefect.run.id": str(self.flow_run.id),
656
- "prefect.tags": self.flow_run.tags,
657
- "prefect.flow.name": self.flow.name,
658
- },
660
+ self._telemetry.start_span(
661
+ name=self.flow.name,
662
+ run=self.flow_run,
663
+ client=self.client,
664
+ parameters=self.parameters,
659
665
  )
660
666
 
661
667
  try:
@@ -698,12 +704,15 @@ class FlowRunEngine(BaseFlowRunEngine[P, R]):
698
704
 
699
705
  @contextmanager
700
706
  def start(self) -> Generator[None, None, None]:
701
- with self.initialize_run(), trace.use_span(self._span):
702
- self.begin_run()
707
+ with self.initialize_run():
708
+ with trace.use_span(
709
+ self._telemetry.span
710
+ ) if self._telemetry.span else nullcontext():
711
+ self.begin_run()
703
712
 
704
- if self.state.is_running():
705
- self.call_hooks()
706
- yield
713
+ if self.state.is_running():
714
+ self.call_hooks()
715
+ yield
707
716
 
708
717
  @contextmanager
709
718
  def run_context(self):
@@ -854,16 +863,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
854
863
  self.flow_run.state_name = state.name # type: ignore
855
864
  self.flow_run.state_type = state.type # type: ignore
856
865
 
857
- if self._span:
858
- self._span.add_event(
859
- state.name,
860
- {
861
- "prefect.state.message": state.message or "",
862
- "prefect.state.type": state.type,
863
- "prefect.state.name": state.name or state.type,
864
- "prefect.state.id": str(state.id),
865
- },
866
- )
866
+ self._telemetry.update_state(state)
867
867
  return state
868
868
 
869
869
  async def result(self, raise_on_failure: bool = True) -> "Union[R, State, None]":
@@ -911,7 +911,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
911
911
  await self.set_state(terminal_state)
912
912
  self._return_value = resolved_result
913
913
 
914
- self._end_span_on_success()
914
+ self._telemetry.end_span_on_success()
915
915
 
916
916
  return result
917
917
 
@@ -941,8 +941,8 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
941
941
  )
942
942
  state = await self.set_state(Running())
943
943
  self._raised = exc
944
-
945
- self._end_span_on_error(exc, state.message)
944
+ self._telemetry.record_exception(exc)
945
+ self._telemetry.end_span_on_failure(state.message)
946
946
 
947
947
  return state
948
948
 
@@ -962,7 +962,8 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
962
962
  await self.set_state(state)
963
963
  self._raised = exc
964
964
 
965
- self._end_span_on_error(exc, message)
965
+ self._telemetry.record_exception(exc)
966
+ self._telemetry.end_span_on_failure(message)
966
967
 
967
968
  async def handle_crash(self, exc: BaseException) -> None:
968
969
  # need to shield from asyncio cancellation to ensure we update the state
@@ -974,7 +975,8 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
974
975
  await self.set_state(state, force=True)
975
976
  self._raised = exc
976
977
 
977
- self._end_span_on_error(exc, state.message)
978
+ self._telemetry.record_exception(exc)
979
+ self._telemetry.end_span_on_failure(state.message)
978
980
 
979
981
  async def load_subflow_run(
980
982
  self,
@@ -1107,7 +1109,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
1107
1109
  hooks = None
1108
1110
 
1109
1111
  for hook in hooks or []:
1110
- hook_name = _get_hook_name(hook)
1112
+ hook_name = get_hook_name(hook)
1111
1113
 
1112
1114
  try:
1113
1115
  self.logger.info(
@@ -1170,7 +1172,7 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
1170
1172
 
1171
1173
  # update the flow run name if necessary
1172
1174
  if not self._flow_run_name_set and self.flow.flow_run_name:
1173
- flow_run_name = _resolve_custom_flow_run_name(
1175
+ flow_run_name = resolve_custom_flow_run_name(
1174
1176
  flow=self.flow, parameters=self.parameters
1175
1177
  )
1176
1178
  await self.client.set_flow_run_name(
@@ -1217,15 +1219,11 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
1217
1219
  empirical_policy=self.flow_run.empirical_policy,
1218
1220
  )
1219
1221
 
1220
- self._span = self._tracer.start_span(
1221
- name=self.flow_run.name,
1222
- attributes={
1223
- **self.flow_run.labels,
1224
- "prefect.run.type": "flow",
1225
- "prefect.run.id": str(self.flow_run.id),
1226
- "prefect.tags": self.flow_run.tags,
1227
- "prefect.flow.name": self.flow.name,
1228
- },
1222
+ await self._telemetry.async_start_span(
1223
+ name=self.flow.name,
1224
+ run=self.flow_run,
1225
+ client=self.client,
1226
+ parameters=self.parameters,
1229
1227
  )
1230
1228
 
1231
1229
  try:
@@ -1269,7 +1267,9 @@ class AsyncFlowRunEngine(BaseFlowRunEngine[P, R]):
1269
1267
  @asynccontextmanager
1270
1268
  async def start(self) -> AsyncGenerator[None, None]:
1271
1269
  async with self.initialize_run():
1272
- with trace.use_span(self._span):
1270
+ with trace.use_span(
1271
+ self._telemetry.span
1272
+ ) if self._telemetry.span else nullcontext():
1273
1273
  await self.begin_run()
1274
1274
 
1275
1275
  if self.state.is_running():
@@ -1392,7 +1392,7 @@ async def run_generator_flow_async(
1392
1392
  flow: Flow[P, R],
1393
1393
  flow_run: Optional[FlowRun] = None,
1394
1394
  parameters: Optional[Dict[str, Any]] = None,
1395
- wait_for: Optional[Iterable[PrefectFuture]] = None,
1395
+ wait_for: Optional[Iterable[PrefectFuture[R]]] = None,
1396
1396
  return_type: Literal["state", "result"] = "result",
1397
1397
  ) -> AsyncGenerator[R, None]:
1398
1398
  if return_type != "result":
@@ -1430,7 +1430,7 @@ def run_flow(
1430
1430
  flow: Flow[P, R],
1431
1431
  flow_run: Optional[FlowRun] = None,
1432
1432
  parameters: Optional[Dict[str, Any]] = None,
1433
- wait_for: Optional[Iterable[PrefectFuture]] = None,
1433
+ wait_for: Optional[Iterable[PrefectFuture[R]]] = None,
1434
1434
  return_type: Literal["state", "result"] = "result",
1435
1435
  ) -> Union[R, State, None]:
1436
1436
  kwargs = dict(
prefect/flow_runs.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from typing import (
2
2
  TYPE_CHECKING,
3
- Dict,
3
+ Any,
4
4
  Optional,
5
5
  Type,
6
6
  TypeVar,
@@ -307,7 +307,7 @@ async def suspend_flow_run(
307
307
  flow_run_id: Optional[UUID] = None,
308
308
  timeout: Optional[int] = 3600,
309
309
  key: Optional[str] = None,
310
- client: PrefectClient = None,
310
+ client: Optional[PrefectClient] = None,
311
311
  ) -> None:
312
312
  ...
313
313
 
@@ -318,7 +318,7 @@ async def suspend_flow_run(
318
318
  flow_run_id: Optional[UUID] = None,
319
319
  timeout: Optional[int] = 3600,
320
320
  key: Optional[str] = None,
321
- client: PrefectClient = None,
321
+ client: Optional[PrefectClient] = None,
322
322
  ) -> T:
323
323
  ...
324
324
 
@@ -330,7 +330,7 @@ async def suspend_flow_run(
330
330
  flow_run_id: Optional[UUID] = None,
331
331
  timeout: Optional[int] = 3600,
332
332
  key: Optional[str] = None,
333
- client: PrefectClient = None,
333
+ client: Optional[PrefectClient] = None,
334
334
  ) -> Optional[T]:
335
335
  """
336
336
  Suspends a flow run by stopping code execution until resumed.
@@ -430,7 +430,9 @@ async def suspend_flow_run(
430
430
 
431
431
 
432
432
  @sync_compatible
433
- async def resume_flow_run(flow_run_id, run_input: Optional[Dict] = None):
433
+ async def resume_flow_run(
434
+ flow_run_id: UUID, run_input: Optional[dict[str, Any]] = None
435
+ ) -> None:
434
436
  """
435
437
  Resumes a paused flow.
436
438