prefect-client 2.19.3__py3-none-any.whl → 3.0.0rc1__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 (239) hide show
  1. prefect/__init__.py +8 -56
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/concurrency/api.py +0 -34
  5. prefect/_internal/concurrency/calls.py +0 -6
  6. prefect/_internal/concurrency/cancellation.py +0 -3
  7. prefect/_internal/concurrency/event_loop.py +0 -20
  8. prefect/_internal/concurrency/inspection.py +3 -3
  9. prefect/_internal/concurrency/threads.py +35 -0
  10. prefect/_internal/concurrency/waiters.py +0 -28
  11. prefect/_internal/pydantic/__init__.py +0 -45
  12. prefect/_internal/pydantic/v1_schema.py +21 -22
  13. prefect/_internal/pydantic/v2_schema.py +0 -2
  14. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  15. prefect/_internal/schemas/bases.py +44 -177
  16. prefect/_internal/schemas/fields.py +1 -43
  17. prefect/_internal/schemas/validators.py +60 -158
  18. prefect/artifacts.py +161 -14
  19. prefect/automations.py +39 -4
  20. prefect/blocks/abstract.py +1 -1
  21. prefect/blocks/core.py +268 -148
  22. prefect/blocks/fields.py +2 -57
  23. prefect/blocks/kubernetes.py +8 -12
  24. prefect/blocks/notifications.py +40 -20
  25. prefect/blocks/system.py +22 -11
  26. prefect/blocks/webhook.py +2 -9
  27. prefect/client/base.py +4 -4
  28. prefect/client/cloud.py +8 -13
  29. prefect/client/orchestration.py +347 -341
  30. prefect/client/schemas/actions.py +92 -86
  31. prefect/client/schemas/filters.py +20 -40
  32. prefect/client/schemas/objects.py +147 -145
  33. prefect/client/schemas/responses.py +16 -24
  34. prefect/client/schemas/schedules.py +47 -35
  35. prefect/client/subscriptions.py +2 -2
  36. prefect/client/utilities.py +5 -2
  37. prefect/concurrency/asyncio.py +3 -1
  38. prefect/concurrency/events.py +1 -1
  39. prefect/concurrency/services.py +6 -3
  40. prefect/context.py +195 -27
  41. prefect/deployments/__init__.py +5 -6
  42. prefect/deployments/base.py +7 -5
  43. prefect/deployments/flow_runs.py +185 -0
  44. prefect/deployments/runner.py +50 -45
  45. prefect/deployments/schedules.py +28 -23
  46. prefect/deployments/steps/__init__.py +0 -1
  47. prefect/deployments/steps/core.py +1 -0
  48. prefect/deployments/steps/pull.py +7 -21
  49. prefect/engine.py +12 -2422
  50. prefect/events/actions.py +17 -23
  51. prefect/events/cli/automations.py +19 -6
  52. prefect/events/clients.py +14 -37
  53. prefect/events/filters.py +14 -18
  54. prefect/events/related.py +2 -2
  55. prefect/events/schemas/__init__.py +0 -5
  56. prefect/events/schemas/automations.py +55 -46
  57. prefect/events/schemas/deployment_triggers.py +7 -197
  58. prefect/events/schemas/events.py +34 -65
  59. prefect/events/schemas/labelling.py +10 -14
  60. prefect/events/utilities.py +2 -3
  61. prefect/events/worker.py +2 -3
  62. prefect/filesystems.py +6 -517
  63. prefect/{new_flow_engine.py → flow_engine.py} +313 -72
  64. prefect/flow_runs.py +377 -5
  65. prefect/flows.py +248 -165
  66. prefect/futures.py +186 -345
  67. prefect/infrastructure/__init__.py +0 -27
  68. prefect/infrastructure/provisioners/__init__.py +5 -3
  69. prefect/infrastructure/provisioners/cloud_run.py +11 -6
  70. prefect/infrastructure/provisioners/container_instance.py +11 -7
  71. prefect/infrastructure/provisioners/ecs.py +6 -4
  72. prefect/infrastructure/provisioners/modal.py +8 -5
  73. prefect/input/actions.py +2 -4
  74. prefect/input/run_input.py +5 -7
  75. prefect/logging/formatters.py +0 -2
  76. prefect/logging/handlers.py +3 -11
  77. prefect/logging/loggers.py +2 -2
  78. prefect/manifests.py +2 -1
  79. prefect/records/__init__.py +1 -0
  80. prefect/records/result_store.py +42 -0
  81. prefect/records/store.py +9 -0
  82. prefect/results.py +43 -39
  83. prefect/runner/runner.py +9 -9
  84. prefect/runner/server.py +6 -10
  85. prefect/runner/storage.py +3 -8
  86. prefect/runner/submit.py +2 -2
  87. prefect/runner/utils.py +2 -2
  88. prefect/serializers.py +24 -35
  89. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  90. prefect/settings.py +70 -133
  91. prefect/states.py +17 -47
  92. prefect/task_engine.py +697 -58
  93. prefect/task_runners.py +269 -301
  94. prefect/task_server.py +53 -34
  95. prefect/tasks.py +327 -337
  96. prefect/transactions.py +220 -0
  97. prefect/types/__init__.py +61 -82
  98. prefect/utilities/asyncutils.py +195 -136
  99. prefect/utilities/callables.py +121 -41
  100. prefect/utilities/collections.py +23 -38
  101. prefect/utilities/dispatch.py +11 -3
  102. prefect/utilities/dockerutils.py +4 -0
  103. prefect/utilities/engine.py +140 -20
  104. prefect/utilities/importtools.py +26 -27
  105. prefect/utilities/pydantic.py +128 -38
  106. prefect/utilities/schema_tools/hydration.py +5 -1
  107. prefect/utilities/templating.py +12 -2
  108. prefect/variables.py +78 -61
  109. prefect/workers/__init__.py +0 -1
  110. prefect/workers/base.py +15 -17
  111. prefect/workers/process.py +3 -8
  112. prefect/workers/server.py +2 -2
  113. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
  114. prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
  115. prefect/_internal/pydantic/_base_model.py +0 -51
  116. prefect/_internal/pydantic/_compat.py +0 -82
  117. prefect/_internal/pydantic/_flags.py +0 -20
  118. prefect/_internal/pydantic/_types.py +0 -8
  119. prefect/_internal/pydantic/utilities/__init__.py +0 -0
  120. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  121. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  122. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  123. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  124. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  125. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  126. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  127. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  128. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  129. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  130. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  131. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  132. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  133. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  134. prefect/_vendor/__init__.py +0 -0
  135. prefect/_vendor/fastapi/__init__.py +0 -25
  136. prefect/_vendor/fastapi/applications.py +0 -946
  137. prefect/_vendor/fastapi/background.py +0 -3
  138. prefect/_vendor/fastapi/concurrency.py +0 -44
  139. prefect/_vendor/fastapi/datastructures.py +0 -58
  140. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  141. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  142. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  143. prefect/_vendor/fastapi/encoders.py +0 -177
  144. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  145. prefect/_vendor/fastapi/exceptions.py +0 -46
  146. prefect/_vendor/fastapi/logger.py +0 -3
  147. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  148. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  149. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  150. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  151. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  152. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  153. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  154. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  155. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  156. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  157. prefect/_vendor/fastapi/openapi/models.py +0 -480
  158. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  159. prefect/_vendor/fastapi/param_functions.py +0 -340
  160. prefect/_vendor/fastapi/params.py +0 -453
  161. prefect/_vendor/fastapi/requests.py +0 -4
  162. prefect/_vendor/fastapi/responses.py +0 -40
  163. prefect/_vendor/fastapi/routing.py +0 -1331
  164. prefect/_vendor/fastapi/security/__init__.py +0 -15
  165. prefect/_vendor/fastapi/security/api_key.py +0 -98
  166. prefect/_vendor/fastapi/security/base.py +0 -6
  167. prefect/_vendor/fastapi/security/http.py +0 -172
  168. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  169. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  170. prefect/_vendor/fastapi/security/utils.py +0 -10
  171. prefect/_vendor/fastapi/staticfiles.py +0 -1
  172. prefect/_vendor/fastapi/templating.py +0 -3
  173. prefect/_vendor/fastapi/testclient.py +0 -1
  174. prefect/_vendor/fastapi/types.py +0 -3
  175. prefect/_vendor/fastapi/utils.py +0 -235
  176. prefect/_vendor/fastapi/websockets.py +0 -7
  177. prefect/_vendor/starlette/__init__.py +0 -1
  178. prefect/_vendor/starlette/_compat.py +0 -28
  179. prefect/_vendor/starlette/_exception_handler.py +0 -80
  180. prefect/_vendor/starlette/_utils.py +0 -88
  181. prefect/_vendor/starlette/applications.py +0 -261
  182. prefect/_vendor/starlette/authentication.py +0 -159
  183. prefect/_vendor/starlette/background.py +0 -43
  184. prefect/_vendor/starlette/concurrency.py +0 -59
  185. prefect/_vendor/starlette/config.py +0 -151
  186. prefect/_vendor/starlette/convertors.py +0 -87
  187. prefect/_vendor/starlette/datastructures.py +0 -707
  188. prefect/_vendor/starlette/endpoints.py +0 -130
  189. prefect/_vendor/starlette/exceptions.py +0 -60
  190. prefect/_vendor/starlette/formparsers.py +0 -276
  191. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  192. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  193. prefect/_vendor/starlette/middleware/base.py +0 -220
  194. prefect/_vendor/starlette/middleware/cors.py +0 -176
  195. prefect/_vendor/starlette/middleware/errors.py +0 -265
  196. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  197. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  198. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  199. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  200. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  201. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  202. prefect/_vendor/starlette/requests.py +0 -328
  203. prefect/_vendor/starlette/responses.py +0 -347
  204. prefect/_vendor/starlette/routing.py +0 -933
  205. prefect/_vendor/starlette/schemas.py +0 -154
  206. prefect/_vendor/starlette/staticfiles.py +0 -248
  207. prefect/_vendor/starlette/status.py +0 -199
  208. prefect/_vendor/starlette/templating.py +0 -231
  209. prefect/_vendor/starlette/testclient.py +0 -804
  210. prefect/_vendor/starlette/types.py +0 -30
  211. prefect/_vendor/starlette/websockets.py +0 -193
  212. prefect/agent.py +0 -698
  213. prefect/deployments/deployments.py +0 -1042
  214. prefect/deprecated/__init__.py +0 -0
  215. prefect/deprecated/data_documents.py +0 -350
  216. prefect/deprecated/packaging/__init__.py +0 -12
  217. prefect/deprecated/packaging/base.py +0 -96
  218. prefect/deprecated/packaging/docker.py +0 -146
  219. prefect/deprecated/packaging/file.py +0 -92
  220. prefect/deprecated/packaging/orion.py +0 -80
  221. prefect/deprecated/packaging/serializers.py +0 -171
  222. prefect/events/instrument.py +0 -135
  223. prefect/infrastructure/base.py +0 -323
  224. prefect/infrastructure/container.py +0 -818
  225. prefect/infrastructure/kubernetes.py +0 -920
  226. prefect/infrastructure/process.py +0 -289
  227. prefect/new_task_engine.py +0 -423
  228. prefect/pydantic/__init__.py +0 -76
  229. prefect/pydantic/main.py +0 -39
  230. prefect/software/__init__.py +0 -2
  231. prefect/software/base.py +0 -50
  232. prefect/software/conda.py +0 -199
  233. prefect/software/pip.py +0 -122
  234. prefect/software/python.py +0 -52
  235. prefect/workers/block.py +0 -218
  236. prefect_client-2.19.3.dist-info/RECORD +0 -292
  237. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
  238. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
  239. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
@@ -4,6 +4,7 @@ Utilities for extensions of and operations on Python collections.
4
4
 
5
5
  import io
6
6
  import itertools
7
+ import warnings
7
8
  from collections import OrderedDict, defaultdict
8
9
  from collections.abc import Iterator as IteratorABC
9
10
  from collections.abc import Sequence
@@ -28,12 +29,7 @@ from typing import (
28
29
  )
29
30
  from unittest.mock import Mock
30
31
 
31
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
32
-
33
- if HAS_PYDANTIC_V2:
34
- import pydantic.v1 as pydantic
35
- else:
36
- import pydantic
32
+ import pydantic
37
33
 
38
34
  # Quote moved to `prefect.utilities.annotations` but preserved here for compatibility
39
35
  from prefect.utilities.annotations import BaseAnnotation, Quote, quote # noqa
@@ -225,7 +221,7 @@ class StopVisiting(BaseException):
225
221
 
226
222
  def visit_collection(
227
223
  expr,
228
- visit_fn: Callable[[Any], Any],
224
+ visit_fn: Union[Callable[[Any, dict], Any], Callable[[Any], Any]],
229
225
  return_data: bool = False,
230
226
  max_depth: int = -1,
231
227
  context: Optional[dict] = None,
@@ -252,8 +248,10 @@ def visit_collection(
252
248
 
253
249
  Args:
254
250
  expr (Any): a Python object or expression
255
- visit_fn (Callable[[Any], Awaitable[Any]]): an async function that
256
- will be applied to every non-collection element of expr.
251
+ visit_fn (Callable[[Any, Optional[dict]], Awaitable[Any]]): a function that
252
+ will be applied to every non-collection element of expr. The function can
253
+ accept one or two arguments. If two arguments are accepted, the second
254
+ argument will be the context dictionary.
257
255
  return_data (bool): if `True`, a copy of `expr` containing data modified
258
256
  by `visit_fn` will be returned. This is slower than `return_data=False`
259
257
  (the default).
@@ -343,39 +341,26 @@ def visit_collection(
343
341
  result = typ(**items) if return_data else None
344
342
 
345
343
  elif isinstance(expr, pydantic.BaseModel):
346
- # NOTE: This implementation *does not* traverse private attributes
347
- # Pydantic does not expose extras in `__fields__` so we use `__fields_set__`
348
- # as well to get all of the relevant attributes
349
- # Check for presence of attrs even if they're in the field set due to pydantic#4916
350
- model_fields = {
351
- f for f in expr.__fields_set__.union(expr.__fields__) if hasattr(expr, f)
352
- }
353
- items = [visit_nested(getattr(expr, key)) for key in model_fields]
344
+ typ = cast(Type[pydantic.BaseModel], typ)
354
345
 
355
- if return_data:
356
- # Collect fields with aliases so reconstruction can use the correct field name
357
- aliases = {
358
- key: value.alias
359
- for key, value in expr.__fields__.items()
360
- if value.has_alias
361
- }
346
+ # when extra=allow, fields not in model_fields may be in model_fields_set
347
+ model_fields = expr.model_fields_set.union(expr.model_fields.keys())
362
348
 
363
- model_instance = typ(
364
- **{
365
- aliases.get(key) or key: value
366
- for key, value in zip(model_fields, items)
367
- }
368
- )
349
+ # We may encounter a deprecated field here, but this isn't the caller's fault
350
+ with warnings.catch_warnings():
351
+ warnings.simplefilter("ignore", category=DeprecationWarning)
369
352
 
370
- # Private attributes are not included in `__fields_set__` but we do not want
371
- # to drop them from the model so we restore them after constructing a new
372
- # model
373
- for attr in expr.__private_attributes__:
374
- # Use `object.__setattr__` to avoid errors on immutable models
375
- object.__setattr__(model_instance, attr, getattr(expr, attr))
353
+ updated_data = {
354
+ field: visit_nested(getattr(expr, field)) for field in model_fields
355
+ }
376
356
 
377
- # Preserve data about which fields were explicitly set on the original model
378
- object.__setattr__(model_instance, "__fields_set__", expr.__fields_set__)
357
+ if return_data:
358
+ # Use construct to avoid validation and handle immutability
359
+ model_instance = typ.model_construct(
360
+ _fields_set=expr.model_fields_set, **updated_data
361
+ )
362
+ for private_attr in expr.__private_attributes__:
363
+ setattr(model_instance, private_attr, getattr(expr, private_attr))
379
364
  result = model_instance
380
365
  else:
381
366
  result = None
@@ -90,8 +90,10 @@ def get_dispatch_key(
90
90
 
91
91
  @classmethod
92
92
  def _register_subclass_of_base_type(cls, **kwargs):
93
- if cls.__init_subclass_original__:
93
+ if hasattr(cls, "__init_subclass_original__"):
94
94
  cls.__init_subclass_original__(**kwargs)
95
+ elif hasattr(cls, "__pydantic_init_subclass_original__"):
96
+ cls.__pydantic_init_subclass_original__(**kwargs)
95
97
 
96
98
  # Do not register abstract base classes
97
99
  if abc.ABC in getattr(cls, "__bases__", []):
@@ -114,8 +116,14 @@ def register_base_type(cls: T) -> T:
114
116
  registry[base_key] = cls
115
117
 
116
118
  # Add automatic subtype registration
117
- cls.__init_subclass_original__ = getattr(cls, "__init_subclass__")
118
- cls.__init_subclass__ = _register_subclass_of_base_type
119
+ if hasattr(cls, "__pydantic_init_subclass__"):
120
+ cls.__pydantic_init_subclass_original__ = getattr(
121
+ cls, "__pydantic_init_subclass__"
122
+ )
123
+ cls.__pydantic_init_subclass__ = _register_subclass_of_base_type
124
+ else:
125
+ cls.__init_subclass_original__ = getattr(cls, "__init_subclass__")
126
+ cls.__init_subclass__ = _register_subclass_of_base_type
119
127
 
120
128
  return cls
121
129
 
@@ -27,6 +27,10 @@ import prefect
27
27
  from prefect.utilities.importtools import lazy_import
28
28
  from prefect.utilities.slugify import slugify
29
29
 
30
+ CONTAINER_LABELS = {
31
+ "io.prefect.version": prefect.__version__,
32
+ }
33
+
30
34
 
31
35
  def python_version_minor() -> str:
32
36
  return f"{sys.version_info.major}.{sys.version_info.minor}"
@@ -6,6 +6,7 @@ import signal
6
6
  import time
7
7
  from functools import partial
8
8
  from typing import (
9
+ TYPE_CHECKING,
9
10
  Any,
10
11
  Callable,
11
12
  Dict,
@@ -24,7 +25,6 @@ import prefect
24
25
  import prefect.context
25
26
  import prefect.plugins
26
27
  from prefect._internal.concurrency.cancellation import get_deadline
27
- from prefect.client.orchestration import PrefectClient, SyncPrefectClient
28
28
  from prefect.client.schemas import OrchestrationResult, TaskRun
29
29
  from prefect.client.schemas.objects import (
30
30
  StateType,
@@ -44,6 +44,7 @@ from prefect.exceptions import (
44
44
  )
45
45
  from prefect.flows import Flow
46
46
  from prefect.futures import PrefectFuture
47
+ from prefect.futures import PrefectFuture as NewPrefectFuture
47
48
  from prefect.logging.loggers import (
48
49
  get_logger,
49
50
  task_run_logger,
@@ -55,17 +56,19 @@ from prefect.settings import (
55
56
  from prefect.states import (
56
57
  State,
57
58
  get_state_exception,
58
- is_state,
59
59
  )
60
60
  from prefect.tasks import Task
61
61
  from prefect.utilities.annotations import allow_failure, quote
62
62
  from prefect.utilities.asyncutils import (
63
63
  gather,
64
- run_sync,
64
+ run_coro_as_sync,
65
65
  )
66
66
  from prefect.utilities.collections import StopVisiting, visit_collection
67
67
  from prefect.utilities.text import truncated_to
68
68
 
69
+ if TYPE_CHECKING:
70
+ from prefect.client.orchestration import PrefectClient, SyncPrefectClient
71
+
69
72
  API_HEALTHCHECKS = {}
70
73
  UNTRACKABLE_TYPES = {bool, type(None), type(...), type(NotImplemented)}
71
74
  engine_logger = get_logger("engine")
@@ -93,7 +96,7 @@ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> Set[TaskRun
93
96
  # We need to wait for futures to be submitted before we can get the task
94
97
  # run id but we want to do so asynchronously
95
98
  futures.add(obj)
96
- elif is_state(obj):
99
+ elif isinstance(obj, State):
97
100
  if obj.state_details.task_run_id:
98
101
  inputs.add(TaskRunResult(id=obj.state_details.task_run_id))
99
102
  # Expressions inside quotes should not be traversed
@@ -118,8 +121,49 @@ async def collect_task_run_inputs(expr: Any, max_depth: int = -1) -> Set[TaskRun
118
121
  return inputs
119
122
 
120
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
+
121
165
  async def wait_for_task_runs_and_report_crashes(
122
- task_run_futures: Iterable[PrefectFuture], client: PrefectClient
166
+ task_run_futures: Iterable[PrefectFuture], client: "PrefectClient"
123
167
  ) -> Literal[True]:
124
168
  crash_exceptions = []
125
169
 
@@ -225,7 +269,7 @@ async def resolve_inputs(
225
269
 
226
270
  if isinstance(expr, PrefectFuture):
227
271
  futures.add(expr)
228
- if is_state(expr):
272
+ if isinstance(expr, State):
229
273
  states.add(expr)
230
274
 
231
275
  return expr
@@ -264,7 +308,7 @@ async def resolve_inputs(
264
308
 
265
309
  if isinstance(expr, PrefectFuture):
266
310
  state = expr._final_state
267
- elif is_state(expr):
311
+ elif isinstance(expr, State):
268
312
  state = expr
269
313
  else:
270
314
  return expr
@@ -311,7 +355,7 @@ async def resolve_inputs(
311
355
 
312
356
 
313
357
  async def propose_state(
314
- client: PrefectClient,
358
+ client: "PrefectClient",
315
359
  state: State[object],
316
360
  force: bool = False,
317
361
  task_run_id: Optional[UUID] = None,
@@ -412,7 +456,7 @@ async def propose_state(
412
456
 
413
457
 
414
458
  def propose_state_sync(
415
- client: SyncPrefectClient,
459
+ client: "SyncPrefectClient",
416
460
  state: State[object],
417
461
  force: bool = False,
418
462
  task_run_id: Optional[UUID] = None,
@@ -459,7 +503,7 @@ def propose_state_sync(
459
503
  # the purpose of disabling `cache_result_in_memory`
460
504
  result = state.result(raise_on_failure=False, fetch=True)
461
505
  if inspect.isawaitable(result):
462
- result = run_sync(result)
506
+ result = run_coro_as_sync(result)
463
507
  else:
464
508
  result = state.data
465
509
 
@@ -515,7 +559,9 @@ def propose_state_sync(
515
559
 
516
560
 
517
561
  def _dynamic_key_for_task_run(context: FlowRunContext, task: Task) -> int:
518
- if context.flow_run is None: # this is an autonomous task run
562
+ if context.detached: # this task is running on remote infrastructure
563
+ return str(uuid4())
564
+ elif context.flow_run is None: # this is an autonomous task run
519
565
  context.task_run_dynamic_keys[task.task_key] = getattr(
520
566
  task, "dynamic_key", str(uuid4())
521
567
  )
@@ -528,14 +574,6 @@ def _dynamic_key_for_task_run(context: FlowRunContext, task: Task) -> int:
528
574
  return context.task_run_dynamic_keys[task.task_key]
529
575
 
530
576
 
531
- def _observed_flow_pauses(context: FlowRunContext) -> int:
532
- if "counter" not in context.observed_flow_pauses:
533
- context.observed_flow_pauses["counter"] = 1
534
- else:
535
- context.observed_flow_pauses["counter"] += 1
536
- return context.observed_flow_pauses["counter"]
537
-
538
-
539
577
  def get_state_for_result(obj: Any) -> Optional[State]:
540
578
  """
541
579
  Get the state related to a result object.
@@ -664,7 +702,7 @@ def _get_hook_name(hook: Callable) -> str:
664
702
  )
665
703
 
666
704
 
667
- async def check_api_reachable(client: PrefectClient, fail_message: str):
705
+ async def check_api_reachable(client: "PrefectClient", fail_message: str):
668
706
  # Do not perform a healthcheck if it exists and is not expired
669
707
  api_url = str(client.api_url)
670
708
  if api_url in API_HEALTHCHECKS:
@@ -734,3 +772,85 @@ def emit_task_run_state_change_event(
734
772
  },
735
773
  follows=follows,
736
774
  )
775
+
776
+
777
+ def resolve_to_final_result(expr, context):
778
+ """
779
+ Resolve any `PrefectFuture`, or `State` types nested in parameters into
780
+ data. Designed to be use with `visit_collection`.
781
+ """
782
+ state = None
783
+
784
+ # Expressions inside quotes should not be modified
785
+ if isinstance(context.get("annotation"), quote):
786
+ raise StopVisiting()
787
+
788
+ if isinstance(expr, NewPrefectFuture):
789
+ expr.wait()
790
+ state = expr.state
791
+ elif isinstance(expr, State):
792
+ state = expr
793
+ else:
794
+ return expr
795
+
796
+ assert state
797
+
798
+ # Do not allow uncompleted upstreams except failures when `allow_failure` has
799
+ # been used
800
+ if not state.is_completed() and not (
801
+ # TODO: Note that the contextual annotation here is only at the current level
802
+ # if `allow_failure` is used then another annotation is used, this will
803
+ # incorrectly evaluate to false — to resolve this, we must track all
804
+ # annotations wrapping the current expression but this is not yet
805
+ # implemented.
806
+ isinstance(context.get("annotation"), allow_failure) and state.is_failed()
807
+ ):
808
+ raise UpstreamTaskError(
809
+ f"Upstream task run '{state.state_details.task_run_id}' did not reach a"
810
+ " 'COMPLETED' state."
811
+ )
812
+
813
+ _result = state.result(raise_on_failure=False, fetch=True)
814
+ if inspect.isawaitable(_result):
815
+ _result = run_coro_as_sync(_result)
816
+ return _result
817
+
818
+
819
+ def resolve_inputs_sync(
820
+ parameters: Dict[str, Any], return_data: bool = True, max_depth: int = -1
821
+ ) -> Dict[str, Any]:
822
+ """
823
+ Resolve any `Quote`, `PrefectFuture`, or `State` types nested in parameters into
824
+ data.
825
+
826
+ Returns:
827
+ A copy of the parameters with resolved data
828
+
829
+ Raises:
830
+ UpstreamTaskError: If any of the upstream states are not `COMPLETED`
831
+ """
832
+
833
+ if not parameters:
834
+ return {}
835
+
836
+ resolved_parameters = {}
837
+ for parameter, value in parameters.items():
838
+ try:
839
+ resolved_parameters[parameter] = visit_collection(
840
+ value,
841
+ visit_fn=resolve_to_final_result,
842
+ return_data=return_data,
843
+ max_depth=max_depth,
844
+ remove_annotations=True,
845
+ context={},
846
+ )
847
+ except UpstreamTaskError:
848
+ raise
849
+ except Exception as exc:
850
+ raise PrefectException(
851
+ f"Failed to resolve inputs in parameter {parameter!r}. If your"
852
+ " parameter type is not supported, consider using the `quote`"
853
+ " annotation to skip resolution of inputs."
854
+ ) from exc
855
+
856
+ return resolved_parameters
@@ -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