prefect-client 2.20.4__py3-none-any.whl → 3.0.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 (288) hide show
  1. prefect/__init__.py +74 -110
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/compatibility/migration.py +166 -0
  5. prefect/_internal/concurrency/__init__.py +2 -2
  6. prefect/_internal/concurrency/api.py +1 -35
  7. prefect/_internal/concurrency/calls.py +0 -6
  8. prefect/_internal/concurrency/cancellation.py +0 -3
  9. prefect/_internal/concurrency/event_loop.py +0 -20
  10. prefect/_internal/concurrency/inspection.py +3 -3
  11. prefect/_internal/concurrency/primitives.py +1 -0
  12. prefect/_internal/concurrency/services.py +23 -0
  13. prefect/_internal/concurrency/threads.py +35 -0
  14. prefect/_internal/concurrency/waiters.py +0 -28
  15. prefect/_internal/integrations.py +7 -0
  16. prefect/_internal/pydantic/__init__.py +0 -45
  17. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  18. prefect/_internal/pydantic/v1_schema.py +21 -22
  19. prefect/_internal/pydantic/v2_schema.py +0 -2
  20. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  21. prefect/_internal/pytz.py +1 -1
  22. prefect/_internal/retries.py +61 -0
  23. prefect/_internal/schemas/bases.py +45 -177
  24. prefect/_internal/schemas/fields.py +1 -43
  25. prefect/_internal/schemas/validators.py +47 -233
  26. prefect/agent.py +3 -695
  27. prefect/artifacts.py +173 -14
  28. prefect/automations.py +39 -4
  29. prefect/blocks/abstract.py +1 -1
  30. prefect/blocks/core.py +405 -153
  31. prefect/blocks/fields.py +2 -57
  32. prefect/blocks/notifications.py +43 -28
  33. prefect/blocks/redis.py +168 -0
  34. prefect/blocks/system.py +67 -20
  35. prefect/blocks/webhook.py +2 -9
  36. prefect/cache_policies.py +239 -0
  37. prefect/client/__init__.py +4 -0
  38. prefect/client/base.py +33 -27
  39. prefect/client/cloud.py +65 -20
  40. prefect/client/collections.py +1 -1
  41. prefect/client/orchestration.py +650 -442
  42. prefect/client/schemas/actions.py +115 -100
  43. prefect/client/schemas/filters.py +46 -52
  44. prefect/client/schemas/objects.py +228 -178
  45. prefect/client/schemas/responses.py +18 -36
  46. prefect/client/schemas/schedules.py +55 -36
  47. prefect/client/schemas/sorting.py +2 -0
  48. prefect/client/subscriptions.py +8 -7
  49. prefect/client/types/flexible_schedule_list.py +11 -0
  50. prefect/client/utilities.py +9 -6
  51. prefect/concurrency/asyncio.py +60 -11
  52. prefect/concurrency/context.py +24 -0
  53. prefect/concurrency/events.py +2 -2
  54. prefect/concurrency/services.py +46 -16
  55. prefect/concurrency/sync.py +51 -7
  56. prefect/concurrency/v1/asyncio.py +143 -0
  57. prefect/concurrency/v1/context.py +27 -0
  58. prefect/concurrency/v1/events.py +61 -0
  59. prefect/concurrency/v1/services.py +116 -0
  60. prefect/concurrency/v1/sync.py +92 -0
  61. prefect/context.py +246 -149
  62. prefect/deployments/__init__.py +33 -18
  63. prefect/deployments/base.py +10 -15
  64. prefect/deployments/deployments.py +2 -1048
  65. prefect/deployments/flow_runs.py +178 -0
  66. prefect/deployments/runner.py +72 -173
  67. prefect/deployments/schedules.py +31 -25
  68. prefect/deployments/steps/__init__.py +0 -1
  69. prefect/deployments/steps/core.py +7 -0
  70. prefect/deployments/steps/pull.py +15 -21
  71. prefect/deployments/steps/utility.py +2 -1
  72. prefect/docker/__init__.py +20 -0
  73. prefect/docker/docker_image.py +82 -0
  74. prefect/engine.py +15 -2475
  75. prefect/events/actions.py +17 -23
  76. prefect/events/cli/automations.py +20 -7
  77. prefect/events/clients.py +142 -80
  78. prefect/events/filters.py +14 -18
  79. prefect/events/related.py +74 -75
  80. prefect/events/schemas/__init__.py +0 -5
  81. prefect/events/schemas/automations.py +55 -46
  82. prefect/events/schemas/deployment_triggers.py +7 -197
  83. prefect/events/schemas/events.py +46 -65
  84. prefect/events/schemas/labelling.py +10 -14
  85. prefect/events/utilities.py +4 -5
  86. prefect/events/worker.py +23 -8
  87. prefect/exceptions.py +15 -0
  88. prefect/filesystems.py +30 -529
  89. prefect/flow_engine.py +827 -0
  90. prefect/flow_runs.py +379 -7
  91. prefect/flows.py +470 -360
  92. prefect/futures.py +382 -331
  93. prefect/infrastructure/__init__.py +5 -26
  94. prefect/infrastructure/base.py +3 -320
  95. prefect/infrastructure/provisioners/__init__.py +5 -3
  96. prefect/infrastructure/provisioners/cloud_run.py +13 -8
  97. prefect/infrastructure/provisioners/container_instance.py +14 -9
  98. prefect/infrastructure/provisioners/ecs.py +10 -8
  99. prefect/infrastructure/provisioners/modal.py +8 -5
  100. prefect/input/__init__.py +4 -0
  101. prefect/input/actions.py +2 -4
  102. prefect/input/run_input.py +9 -9
  103. prefect/logging/formatters.py +2 -4
  104. prefect/logging/handlers.py +9 -14
  105. prefect/logging/loggers.py +5 -5
  106. prefect/main.py +72 -0
  107. prefect/plugins.py +2 -64
  108. prefect/profiles.toml +16 -2
  109. prefect/records/__init__.py +1 -0
  110. prefect/records/base.py +223 -0
  111. prefect/records/filesystem.py +207 -0
  112. prefect/records/memory.py +178 -0
  113. prefect/records/result_store.py +64 -0
  114. prefect/results.py +577 -504
  115. prefect/runner/runner.py +117 -47
  116. prefect/runner/server.py +32 -34
  117. prefect/runner/storage.py +3 -12
  118. prefect/runner/submit.py +2 -10
  119. prefect/runner/utils.py +2 -2
  120. prefect/runtime/__init__.py +1 -0
  121. prefect/runtime/deployment.py +1 -0
  122. prefect/runtime/flow_run.py +40 -5
  123. prefect/runtime/task_run.py +1 -0
  124. prefect/serializers.py +28 -39
  125. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  126. prefect/settings.py +209 -332
  127. prefect/states.py +160 -63
  128. prefect/task_engine.py +1478 -57
  129. prefect/task_runners.py +383 -287
  130. prefect/task_runs.py +240 -0
  131. prefect/task_worker.py +463 -0
  132. prefect/tasks.py +684 -374
  133. prefect/transactions.py +410 -0
  134. prefect/types/__init__.py +72 -86
  135. prefect/types/entrypoint.py +13 -0
  136. prefect/utilities/annotations.py +4 -3
  137. prefect/utilities/asyncutils.py +227 -148
  138. prefect/utilities/callables.py +137 -45
  139. prefect/utilities/collections.py +134 -86
  140. prefect/utilities/dispatch.py +27 -14
  141. prefect/utilities/dockerutils.py +11 -4
  142. prefect/utilities/engine.py +186 -32
  143. prefect/utilities/filesystem.py +4 -5
  144. prefect/utilities/importtools.py +26 -27
  145. prefect/utilities/pydantic.py +128 -38
  146. prefect/utilities/schema_tools/hydration.py +18 -1
  147. prefect/utilities/schema_tools/validation.py +30 -0
  148. prefect/utilities/services.py +35 -9
  149. prefect/utilities/templating.py +12 -2
  150. prefect/utilities/timeout.py +20 -5
  151. prefect/utilities/urls.py +195 -0
  152. prefect/utilities/visualization.py +1 -0
  153. prefect/variables.py +78 -59
  154. prefect/workers/__init__.py +0 -1
  155. prefect/workers/base.py +237 -244
  156. prefect/workers/block.py +5 -226
  157. prefect/workers/cloud.py +6 -0
  158. prefect/workers/process.py +265 -12
  159. prefect/workers/server.py +29 -11
  160. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
  163. prefect/_internal/pydantic/_base_model.py +0 -51
  164. prefect/_internal/pydantic/_compat.py +0 -82
  165. prefect/_internal/pydantic/_flags.py +0 -20
  166. prefect/_internal/pydantic/_types.py +0 -8
  167. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  168. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  169. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  170. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  171. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  172. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  173. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  174. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  175. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  176. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  177. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  178. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  179. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  180. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  181. prefect/_vendor/fastapi/__init__.py +0 -25
  182. prefect/_vendor/fastapi/applications.py +0 -946
  183. prefect/_vendor/fastapi/background.py +0 -3
  184. prefect/_vendor/fastapi/concurrency.py +0 -44
  185. prefect/_vendor/fastapi/datastructures.py +0 -58
  186. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  187. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  188. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  189. prefect/_vendor/fastapi/encoders.py +0 -177
  190. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  191. prefect/_vendor/fastapi/exceptions.py +0 -46
  192. prefect/_vendor/fastapi/logger.py +0 -3
  193. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  194. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  195. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  196. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  197. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  198. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  199. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  200. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  201. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  202. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  203. prefect/_vendor/fastapi/openapi/models.py +0 -480
  204. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  205. prefect/_vendor/fastapi/param_functions.py +0 -340
  206. prefect/_vendor/fastapi/params.py +0 -453
  207. prefect/_vendor/fastapi/py.typed +0 -0
  208. prefect/_vendor/fastapi/requests.py +0 -4
  209. prefect/_vendor/fastapi/responses.py +0 -40
  210. prefect/_vendor/fastapi/routing.py +0 -1331
  211. prefect/_vendor/fastapi/security/__init__.py +0 -15
  212. prefect/_vendor/fastapi/security/api_key.py +0 -98
  213. prefect/_vendor/fastapi/security/base.py +0 -6
  214. prefect/_vendor/fastapi/security/http.py +0 -172
  215. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  216. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  217. prefect/_vendor/fastapi/security/utils.py +0 -10
  218. prefect/_vendor/fastapi/staticfiles.py +0 -1
  219. prefect/_vendor/fastapi/templating.py +0 -3
  220. prefect/_vendor/fastapi/testclient.py +0 -1
  221. prefect/_vendor/fastapi/types.py +0 -3
  222. prefect/_vendor/fastapi/utils.py +0 -235
  223. prefect/_vendor/fastapi/websockets.py +0 -7
  224. prefect/_vendor/starlette/__init__.py +0 -1
  225. prefect/_vendor/starlette/_compat.py +0 -28
  226. prefect/_vendor/starlette/_exception_handler.py +0 -80
  227. prefect/_vendor/starlette/_utils.py +0 -88
  228. prefect/_vendor/starlette/applications.py +0 -261
  229. prefect/_vendor/starlette/authentication.py +0 -159
  230. prefect/_vendor/starlette/background.py +0 -43
  231. prefect/_vendor/starlette/concurrency.py +0 -59
  232. prefect/_vendor/starlette/config.py +0 -151
  233. prefect/_vendor/starlette/convertors.py +0 -87
  234. prefect/_vendor/starlette/datastructures.py +0 -707
  235. prefect/_vendor/starlette/endpoints.py +0 -130
  236. prefect/_vendor/starlette/exceptions.py +0 -60
  237. prefect/_vendor/starlette/formparsers.py +0 -276
  238. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  239. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  240. prefect/_vendor/starlette/middleware/base.py +0 -220
  241. prefect/_vendor/starlette/middleware/cors.py +0 -176
  242. prefect/_vendor/starlette/middleware/errors.py +0 -265
  243. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  244. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  245. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  246. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  247. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  248. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  249. prefect/_vendor/starlette/py.typed +0 -0
  250. prefect/_vendor/starlette/requests.py +0 -328
  251. prefect/_vendor/starlette/responses.py +0 -347
  252. prefect/_vendor/starlette/routing.py +0 -933
  253. prefect/_vendor/starlette/schemas.py +0 -154
  254. prefect/_vendor/starlette/staticfiles.py +0 -248
  255. prefect/_vendor/starlette/status.py +0 -199
  256. prefect/_vendor/starlette/templating.py +0 -231
  257. prefect/_vendor/starlette/testclient.py +0 -804
  258. prefect/_vendor/starlette/types.py +0 -30
  259. prefect/_vendor/starlette/websockets.py +0 -193
  260. prefect/blocks/kubernetes.py +0 -119
  261. prefect/deprecated/__init__.py +0 -0
  262. prefect/deprecated/data_documents.py +0 -350
  263. prefect/deprecated/packaging/__init__.py +0 -12
  264. prefect/deprecated/packaging/base.py +0 -96
  265. prefect/deprecated/packaging/docker.py +0 -146
  266. prefect/deprecated/packaging/file.py +0 -92
  267. prefect/deprecated/packaging/orion.py +0 -80
  268. prefect/deprecated/packaging/serializers.py +0 -171
  269. prefect/events/instrument.py +0 -135
  270. prefect/infrastructure/container.py +0 -824
  271. prefect/infrastructure/kubernetes.py +0 -920
  272. prefect/infrastructure/process.py +0 -289
  273. prefect/manifests.py +0 -20
  274. prefect/new_flow_engine.py +0 -449
  275. prefect/new_task_engine.py +0 -423
  276. prefect/pydantic/__init__.py +0 -76
  277. prefect/pydantic/main.py +0 -39
  278. prefect/software/__init__.py +0 -2
  279. prefect/software/base.py +0 -50
  280. prefect/software/conda.py +0 -199
  281. prefect/software/pip.py +0 -122
  282. prefect/software/python.py +0 -52
  283. prefect/task_server.py +0 -322
  284. prefect_client-2.20.4.dist-info/RECORD +0 -294
  285. /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
  286. /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
  287. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,7 @@ from tempfile import TemporaryDirectory
9
9
  from types import TracebackType
10
10
  from typing import (
11
11
  TYPE_CHECKING,
12
+ Any,
12
13
  Generator,
13
14
  Iterable,
14
15
  List,
@@ -27,6 +28,10 @@ import prefect
27
28
  from prefect.utilities.importtools import lazy_import
28
29
  from prefect.utilities.slugify import slugify
29
30
 
31
+ CONTAINER_LABELS = {
32
+ "io.prefect.version": prefect.__version__,
33
+ }
34
+
30
35
 
31
36
  def python_version_minor() -> str:
32
37
  return f"{sys.version_info.major}.{sys.version_info.minor}"
@@ -37,7 +42,9 @@ def python_version_micro() -> str:
37
42
 
38
43
 
39
44
  def get_prefect_image_name(
40
- prefect_version: str = None, python_version: str = None, flavor: str = None
45
+ prefect_version: Optional[str] = None,
46
+ python_version: Optional[str] = None,
47
+ flavor: Optional[str] = None,
41
48
  ) -> str:
42
49
  """
43
50
  Get the Prefect image name matching the current Prefect and Python versions.
@@ -134,9 +141,9 @@ def build_image(
134
141
  dockerfile: str = "Dockerfile",
135
142
  tag: Optional[str] = None,
136
143
  pull: bool = False,
137
- platform: str = None,
144
+ platform: Optional[str] = None,
138
145
  stream_progress_to: Optional[TextIO] = None,
139
- **kwargs,
146
+ **kwargs: Any,
140
147
  ) -> str:
141
148
  """Builds a Docker image, returning the image ID
142
149
 
@@ -205,7 +212,7 @@ class ImageBuilder:
205
212
  self,
206
213
  base_image: str,
207
214
  base_directory: Path = None,
208
- platform: str = None,
215
+ platform: Optional[str] = None,
209
216
  context: Path = None,
210
217
  ):
211
218
  """Create an ImageBuilder
@@ -4,13 +4,12 @@ import inspect
4
4
  import os
5
5
  import signal
6
6
  import time
7
- from contextlib import contextmanager
8
7
  from functools import partial
9
8
  from typing import (
9
+ TYPE_CHECKING,
10
10
  Any,
11
11
  Callable,
12
12
  Dict,
13
- Generator,
14
13
  Iterable,
15
14
  Optional,
16
15
  Set,
@@ -20,14 +19,12 @@ from typing import (
20
19
  from uuid import UUID, uuid4
21
20
 
22
21
  import anyio
23
- from exceptiongroup import BaseExceptionGroup # novermin
24
22
  from typing_extensions import Literal
25
23
 
26
24
  import prefect
27
25
  import prefect.context
28
26
  import prefect.plugins
29
27
  from prefect._internal.concurrency.cancellation import get_deadline
30
- from prefect.client.orchestration import PrefectClient, SyncPrefectClient
31
28
  from prefect.client.schemas import OrchestrationResult, TaskRun
32
29
  from prefect.client.schemas.objects import (
33
30
  StateType,
@@ -47,6 +44,7 @@ from prefect.exceptions import (
47
44
  )
48
45
  from prefect.flows import Flow
49
46
  from prefect.futures import PrefectFuture
47
+ from prefect.futures import PrefectFuture as NewPrefectFuture
50
48
  from prefect.logging.loggers import (
51
49
  get_logger,
52
50
  task_run_logger,
@@ -58,17 +56,19 @@ from prefect.settings import (
58
56
  from prefect.states import (
59
57
  State,
60
58
  get_state_exception,
61
- is_state,
62
59
  )
63
60
  from prefect.tasks import Task
64
61
  from prefect.utilities.annotations import allow_failure, quote
65
62
  from prefect.utilities.asyncutils import (
66
63
  gather,
67
- run_sync,
64
+ run_coro_as_sync,
68
65
  )
69
66
  from prefect.utilities.collections import StopVisiting, visit_collection
70
67
  from prefect.utilities.text import truncated_to
71
68
 
69
+ if TYPE_CHECKING:
70
+ from prefect.client.orchestration import PrefectClient, SyncPrefectClient
71
+
72
72
  API_HEALTHCHECKS = {}
73
73
  UNTRACKABLE_TYPES = {bool, type(None), type(...), type(NotImplemented)}
74
74
  engine_logger = get_logger("engine")
@@ -96,7 +96,7 @@ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> Set[TaskRun
96
96
  # We need to wait for futures to be submitted before we can get the task
97
97
  # run id but we want to do so asynchronously
98
98
  futures.add(obj)
99
- elif is_state(obj):
99
+ elif isinstance(obj, State):
100
100
  if obj.state_details.task_run_id:
101
101
  inputs.add(TaskRunResult(id=obj.state_details.task_run_id))
102
102
  # Expressions inside quotes should not be traversed
@@ -121,8 +121,49 @@ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> Set[TaskRun
121
121
  return inputs
122
122
 
123
123
 
124
+ def collect_task_run_inputs_sync(
125
+ expr: Any, future_cls: Any = NewPrefectFuture, max_depth: int = -1
126
+ ) -> Set[TaskRunInput]:
127
+ """
128
+ This function recurses through an expression to generate a set of any discernible
129
+ task run inputs it finds in the data structure. It produces a set of all inputs
130
+ found.
131
+
132
+ Examples:
133
+ >>> task_inputs = {
134
+ >>> k: collect_task_run_inputs(v) for k, v in parameters.items()
135
+ >>> }
136
+ """
137
+ # TODO: This function needs to be updated to detect parameters and constants
138
+
139
+ inputs = set()
140
+
141
+ def add_futures_and_states_to_inputs(obj):
142
+ if isinstance(obj, future_cls) and hasattr(obj, "task_run_id"):
143
+ inputs.add(TaskRunResult(id=obj.task_run_id))
144
+ elif isinstance(obj, State):
145
+ if obj.state_details.task_run_id:
146
+ inputs.add(TaskRunResult(id=obj.state_details.task_run_id))
147
+ # Expressions inside quotes should not be traversed
148
+ elif isinstance(obj, quote):
149
+ raise StopVisiting
150
+ else:
151
+ state = get_state_for_result(obj)
152
+ if state and state.state_details.task_run_id:
153
+ inputs.add(TaskRunResult(id=state.state_details.task_run_id))
154
+
155
+ visit_collection(
156
+ expr,
157
+ visit_fn=add_futures_and_states_to_inputs,
158
+ return_data=False,
159
+ max_depth=max_depth,
160
+ )
161
+
162
+ return inputs
163
+
164
+
124
165
  async def wait_for_task_runs_and_report_crashes(
125
- task_run_futures: Iterable[PrefectFuture], client: PrefectClient
166
+ task_run_futures: Iterable[PrefectFuture], client: "PrefectClient"
126
167
  ) -> Literal[True]:
127
168
  crash_exceptions = []
128
169
 
@@ -228,7 +269,7 @@ async def resolve_inputs(
228
269
 
229
270
  if isinstance(expr, PrefectFuture):
230
271
  futures.add(expr)
231
- if is_state(expr):
272
+ if isinstance(expr, State):
232
273
  states.add(expr)
233
274
 
234
275
  return expr
@@ -267,7 +308,7 @@ async def resolve_inputs(
267
308
 
268
309
  if isinstance(expr, PrefectFuture):
269
310
  state = expr._final_state
270
- elif is_state(expr):
311
+ elif isinstance(expr, State):
271
312
  state = expr
272
313
  else:
273
314
  return expr
@@ -314,7 +355,7 @@ async def resolve_inputs(
314
355
 
315
356
 
316
357
  async def propose_state(
317
- client: PrefectClient,
358
+ client: "PrefectClient",
318
359
  state: State[object],
319
360
  force: bool = False,
320
361
  task_run_id: Optional[UUID] = None,
@@ -415,7 +456,7 @@ async def propose_state(
415
456
 
416
457
 
417
458
  def propose_state_sync(
418
- client: SyncPrefectClient,
459
+ client: "SyncPrefectClient",
419
460
  state: State[object],
420
461
  force: bool = False,
421
462
  task_run_id: Optional[UUID] = None,
@@ -462,7 +503,7 @@ def propose_state_sync(
462
503
  # the purpose of disabling `cache_result_in_memory`
463
504
  result = state.result(raise_on_failure=False, fetch=True)
464
505
  if inspect.isawaitable(result):
465
- result = run_sync(result)
506
+ result = run_coro_as_sync(result)
466
507
  else:
467
508
  result = state.data
468
509
 
@@ -517,8 +558,14 @@ def propose_state_sync(
517
558
  )
518
559
 
519
560
 
520
- def _dynamic_key_for_task_run(context: FlowRunContext, task: Task) -> int:
521
- if context.flow_run is None: # this is an autonomous task run
561
+ def _dynamic_key_for_task_run(
562
+ context: FlowRunContext, task: Task, stable: bool = True
563
+ ) -> Union[int, str]:
564
+ if (
565
+ stable is False or context.detached
566
+ ): # this task is running on remote infrastructure
567
+ return str(uuid4())
568
+ elif context.flow_run is None: # this is an autonomous task run
522
569
  context.task_run_dynamic_keys[task.task_key] = getattr(
523
570
  task, "dynamic_key", str(uuid4())
524
571
  )
@@ -531,14 +578,6 @@ def _dynamic_key_for_task_run(context: FlowRunContext, task: Task) -> int:
531
578
  return context.task_run_dynamic_keys[task.task_key]
532
579
 
533
580
 
534
- def _observed_flow_pauses(context: FlowRunContext) -> int:
535
- if "counter" not in context.observed_flow_pauses:
536
- context.observed_flow_pauses["counter"] = 1
537
- else:
538
- context.observed_flow_pauses["counter"] += 1
539
- return context.observed_flow_pauses["counter"]
540
-
541
-
542
581
  def get_state_for_result(obj: Any) -> Optional[State]:
543
582
  """
544
583
  Get the state related to a result object.
@@ -667,7 +706,7 @@ def _get_hook_name(hook: Callable) -> str:
667
706
  )
668
707
 
669
708
 
670
- async def check_api_reachable(client: PrefectClient, fail_message: str):
709
+ async def check_api_reachable(client: "PrefectClient", fail_message: str):
671
710
  # Do not perform a healthcheck if it exists and is not expired
672
711
  api_url = str(client.api_url)
673
712
  if api_url in API_HEALTHCHECKS:
@@ -709,6 +748,12 @@ def emit_task_run_state_change_event(
709
748
  "message": truncated_to(
710
749
  state_message_truncation_length, initial_state.message
711
750
  ),
751
+ "state_details": initial_state.state_details.model_dump(
752
+ mode="json",
753
+ exclude_none=True,
754
+ exclude_unset=True,
755
+ exclude={"flow_run_id", "task_run_id"},
756
+ ),
712
757
  }
713
758
  if initial_state
714
759
  else None
@@ -719,7 +764,33 @@ def emit_task_run_state_change_event(
719
764
  "message": truncated_to(
720
765
  state_message_truncation_length, validated_state.message
721
766
  ),
767
+ "state_details": validated_state.state_details.model_dump(
768
+ mode="json",
769
+ exclude_none=True,
770
+ exclude_unset=True,
771
+ exclude={"flow_run_id", "task_run_id"},
772
+ ),
773
+ "data": validated_state.data.model_dump(mode="json")
774
+ if isinstance(validated_state.data, BaseResult)
775
+ else None,
722
776
  },
777
+ "task_run": task_run.model_dump(
778
+ mode="json",
779
+ exclude_none=True,
780
+ exclude={
781
+ "id",
782
+ "created",
783
+ "updated",
784
+ "flow_run_id",
785
+ "state_id",
786
+ "state_type",
787
+ "state_name",
788
+ "state",
789
+ # server materialized fields
790
+ "estimated_start_time_delta",
791
+ "estimated_run_time",
792
+ },
793
+ ),
723
794
  },
724
795
  resource={
725
796
  "prefect.resource.id": f"prefect.task-run.{task_run.id}",
@@ -734,17 +805,100 @@ def emit_task_run_state_change_event(
734
805
  else ""
735
806
  ),
736
807
  "prefect.state-type": str(validated_state.type.value),
808
+ "prefect.orchestration": "client",
737
809
  },
738
810
  follows=follows,
739
811
  )
740
812
 
741
813
 
742
- @contextmanager
743
- def collapse_excgroups() -> Generator[None, None, None]:
744
- try:
745
- yield
746
- except BaseException as exc:
747
- while isinstance(exc, BaseExceptionGroup) and len(exc.exceptions) == 1:
748
- exc = exc.exceptions[0]
814
+ def resolve_to_final_result(expr, context):
815
+ """
816
+ Resolve any `PrefectFuture`, or `State` types nested in parameters into
817
+ data. Designed to be use with `visit_collection`.
818
+ """
819
+ state = None
820
+
821
+ # Expressions inside quotes should not be modified
822
+ if isinstance(context.get("annotation"), quote):
823
+ raise StopVisiting()
824
+
825
+ if isinstance(expr, NewPrefectFuture):
826
+ upstream_task_run = context.get("current_task_run")
827
+ upstream_task = context.get("current_task")
828
+ if (
829
+ upstream_task
830
+ and upstream_task_run
831
+ and expr.task_run_id == upstream_task_run.id
832
+ ):
833
+ raise ValueError(
834
+ f"Discovered a task depending on itself. Raising to avoid a deadlock. Please inspect the inputs and dependencies of {upstream_task.name}."
835
+ )
836
+
837
+ expr.wait()
838
+ state = expr.state
839
+ elif isinstance(expr, State):
840
+ state = expr
841
+ else:
842
+ return expr
843
+
844
+ assert state
845
+
846
+ # Do not allow uncompleted upstreams except failures when `allow_failure` has
847
+ # been used
848
+ if not state.is_completed() and not (
849
+ # TODO: Note that the contextual annotation here is only at the current level
850
+ # if `allow_failure` is used then another annotation is used, this will
851
+ # incorrectly evaluate to false — to resolve this, we must track all
852
+ # annotations wrapping the current expression but this is not yet
853
+ # implemented.
854
+ isinstance(context.get("annotation"), allow_failure) and state.is_failed()
855
+ ):
856
+ raise UpstreamTaskError(
857
+ f"Upstream task run '{state.state_details.task_run_id}' did not reach a"
858
+ " 'COMPLETED' state."
859
+ )
860
+
861
+ _result = state.result(raise_on_failure=False, fetch=True)
862
+ if inspect.isawaitable(_result):
863
+ _result = run_coro_as_sync(_result)
864
+ return _result
865
+
866
+
867
+ def resolve_inputs_sync(
868
+ parameters: Dict[str, Any], return_data: bool = True, max_depth: int = -1
869
+ ) -> Dict[str, Any]:
870
+ """
871
+ Resolve any `Quote`, `PrefectFuture`, or `State` types nested in parameters into
872
+ data.
873
+
874
+ Returns:
875
+ A copy of the parameters with resolved data
876
+
877
+ Raises:
878
+ UpstreamTaskError: If any of the upstream states are not `COMPLETED`
879
+ """
749
880
 
750
- raise exc
881
+ if not parameters:
882
+ return {}
883
+
884
+ resolved_parameters = {}
885
+ for parameter, value in parameters.items():
886
+ try:
887
+ resolved_parameters[parameter] = visit_collection(
888
+ value,
889
+ visit_fn=resolve_to_final_result,
890
+ return_data=return_data,
891
+ max_depth=max_depth,
892
+ remove_annotations=True,
893
+ context={},
894
+ )
895
+ except UpstreamTaskError:
896
+ raise
897
+ except Exception as exc:
898
+ raise PrefectException(
899
+ f"Failed to resolve inputs in parameter {parameter!r}. If your"
900
+ " parameter type is not supported, consider using the `quote`"
901
+ " annotation to skip resolution of inputs."
902
+ ) from exc
903
+
904
+ return resolved_parameters
@@ -1,12 +1,13 @@
1
1
  """
2
2
  Utilities for working with file systems
3
3
  """
4
+
4
5
  import os
5
6
  import pathlib
6
7
  import threading
7
8
  from contextlib import contextmanager
8
9
  from pathlib import Path, PureWindowsPath
9
- from typing import Union
10
+ from typing import Optional, Union
10
11
 
11
12
  import fsspec
12
13
  import pathspec
@@ -32,7 +33,7 @@ def create_default_ignore_file(path: str) -> bool:
32
33
 
33
34
 
34
35
  def filter_files(
35
- root: str = ".", ignore_patterns: list = None, include_dirs: bool = True
36
+ root: str = ".", ignore_patterns: Optional[list] = None, include_dirs: bool = True
36
37
  ) -> set:
37
38
  """
38
39
  This function accepts a root directory path and a list of file patterns to ignore, and returns
@@ -40,9 +41,7 @@ def filter_files(
40
41
 
41
42
  The specification matches that of [.gitignore files](https://git-scm.com/docs/gitignore).
42
43
  """
43
- if ignore_patterns is None:
44
- ignore_patterns = []
45
- spec = pathspec.PathSpec.from_lines("gitwildmatch", ignore_patterns)
44
+ spec = pathspec.PathSpec.from_lines("gitwildmatch", ignore_patterns or [])
46
45
  ignored_files = {p.path for p in spec.match_tree_entries(root)}
47
46
  if include_dirs:
48
47
  all_files = {p.path for p in pathspec.util.iter_tree_entries(root)}
@@ -1,10 +1,10 @@
1
1
  import ast
2
2
  import importlib
3
3
  import importlib.util
4
- import inspect
5
4
  import os
6
5
  import runpy
7
6
  import sys
7
+ import warnings
8
8
  from importlib.abc import Loader, MetaPathFinder
9
9
  from importlib.machinery import ModuleSpec
10
10
  from pathlib import Path
@@ -228,24 +228,18 @@ class DelayedImportErrorModule(ModuleType):
228
228
  [1]: https://github.com/scientific-python/lazy_loader
229
229
  """
230
230
 
231
- def __init__(self, frame_data, help_message, *args, **kwargs):
232
- self.__frame_data = frame_data
231
+ def __init__(self, error_message, help_message, *args, **kwargs):
232
+ self.__error_message = error_message
233
233
  self.__help_message = (
234
234
  help_message or "Import errors for this module are only reported when used."
235
235
  )
236
236
  super().__init__(*args, **kwargs)
237
237
 
238
238
  def __getattr__(self, attr):
239
- if attr in ("__class__", "__file__", "__frame_data", "__help_message"):
239
+ if attr in ("__class__", "__file__", "__help_message"):
240
240
  super().__getattr__(attr)
241
241
  else:
242
- fd = self.__frame_data
243
- raise ModuleNotFoundError(
244
- f"No module named '{fd['spec']}'\n\nThis module was originally imported"
245
- f" at:\n File \"{fd['filename']}\", line {fd['lineno']}, in"
246
- f" {fd['function']}\n\n {''.join(fd['code_context']).strip()}\n"
247
- + self.__help_message
248
- )
242
+ raise ModuleNotFoundError(self.__error_message)
249
243
 
250
244
 
251
245
  def lazy_import(
@@ -256,6 +250,13 @@ def lazy_import(
256
250
  Use this to retain module-level imports for libraries that we don't want to
257
251
  actually import until they are needed.
258
252
 
253
+ NOTE: Lazy-loading a subpackage can cause the subpackage to be imported
254
+ twice if another non-lazy import also imports the subpackage. For example,
255
+ using both `lazy_import("docker.errors")` and `import docker.errors` in the
256
+ same codebase will import `docker.errors` twice and can lead to unexpected
257
+ behavior, e.g. type check failures and import-time side effects running
258
+ twice.
259
+
259
260
  Adapted from the [Python documentation][1] and [lazy_loader][2]
260
261
 
261
262
  [1]: https://docs.python.org/3/library/importlib.html#implementing-lazy-imports
@@ -267,25 +268,23 @@ def lazy_import(
267
268
  except KeyError:
268
269
  pass
269
270
 
271
+ if "." in name:
272
+ warnings.warn(
273
+ "Lazy importing subpackages can lead to unexpected behavior.",
274
+ RuntimeWarning,
275
+ )
276
+
270
277
  spec = importlib.util.find_spec(name)
278
+
271
279
  if spec is None:
280
+ import_error_message = f"No module named '{name}'.\n{help_message}"
281
+
272
282
  if error_on_import:
273
- raise ModuleNotFoundError(f"No module named '{name}'.\n{help_message}")
274
- else:
275
- try:
276
- parent = inspect.stack()[1]
277
- frame_data = {
278
- "spec": name,
279
- "filename": parent.filename,
280
- "lineno": parent.lineno,
281
- "function": parent.function,
282
- "code_context": parent.code_context,
283
- }
284
- return DelayedImportErrorModule(
285
- frame_data, help_message, "DelayedImportErrorModule"
286
- )
287
- finally:
288
- del parent
283
+ raise ModuleNotFoundError(import_error_message)
284
+
285
+ return DelayedImportErrorModule(
286
+ import_error_message, help_message, "DelayedImportErrorModule"
287
+ )
289
288
 
290
289
  module = importlib.util.module_from_spec(spec)
291
290
  sys.modules[name] = module