prefect-client 2.19.2__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.
- prefect/__init__.py +8 -56
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/concurrency/api.py +0 -34
- prefect/_internal/concurrency/calls.py +0 -6
- prefect/_internal/concurrency/cancellation.py +0 -3
- prefect/_internal/concurrency/event_loop.py +0 -20
- prefect/_internal/concurrency/inspection.py +3 -3
- prefect/_internal/concurrency/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/pydantic/__init__.py +0 -45
- prefect/_internal/pydantic/v1_schema.py +21 -22
- prefect/_internal/pydantic/v2_schema.py +0 -2
- prefect/_internal/pydantic/v2_validated_func.py +18 -23
- prefect/_internal/schemas/bases.py +44 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +60 -158
- prefect/artifacts.py +161 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +268 -148
- prefect/blocks/fields.py +2 -57
- prefect/blocks/kubernetes.py +8 -12
- prefect/blocks/notifications.py +40 -20
- prefect/blocks/system.py +22 -11
- prefect/blocks/webhook.py +2 -9
- prefect/client/base.py +4 -4
- prefect/client/cloud.py +8 -13
- prefect/client/orchestration.py +347 -341
- prefect/client/schemas/actions.py +92 -86
- prefect/client/schemas/filters.py +20 -40
- prefect/client/schemas/objects.py +151 -145
- prefect/client/schemas/responses.py +16 -24
- prefect/client/schemas/schedules.py +47 -35
- prefect/client/subscriptions.py +2 -2
- prefect/client/utilities.py +5 -2
- prefect/concurrency/asyncio.py +3 -1
- prefect/concurrency/events.py +1 -1
- prefect/concurrency/services.py +6 -3
- prefect/context.py +195 -27
- prefect/deployments/__init__.py +5 -6
- prefect/deployments/base.py +7 -5
- prefect/deployments/flow_runs.py +185 -0
- prefect/deployments/runner.py +50 -45
- prefect/deployments/schedules.py +28 -23
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +1 -0
- prefect/deployments/steps/pull.py +7 -21
- prefect/engine.py +12 -2422
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +19 -6
- prefect/events/clients.py +14 -37
- prefect/events/filters.py +14 -18
- prefect/events/related.py +2 -2
- prefect/events/schemas/__init__.py +0 -5
- prefect/events/schemas/automations.py +55 -46
- prefect/events/schemas/deployment_triggers.py +7 -197
- prefect/events/schemas/events.py +34 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +2 -3
- prefect/events/worker.py +2 -3
- prefect/filesystems.py +6 -517
- prefect/{new_flow_engine.py → flow_engine.py} +313 -72
- prefect/flow_runs.py +377 -5
- prefect/flows.py +307 -166
- prefect/futures.py +186 -345
- prefect/infrastructure/__init__.py +0 -27
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +11 -6
- prefect/infrastructure/provisioners/container_instance.py +11 -7
- prefect/infrastructure/provisioners/ecs.py +6 -4
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +5 -7
- prefect/logging/formatters.py +0 -2
- prefect/logging/handlers.py +3 -11
- prefect/logging/loggers.py +2 -2
- prefect/manifests.py +2 -1
- prefect/records/__init__.py +1 -0
- prefect/records/result_store.py +42 -0
- prefect/records/store.py +9 -0
- prefect/results.py +43 -39
- prefect/runner/runner.py +19 -15
- prefect/runner/server.py +6 -10
- prefect/runner/storage.py +3 -8
- prefect/runner/submit.py +2 -2
- prefect/runner/utils.py +2 -2
- prefect/serializers.py +24 -35
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +70 -133
- prefect/states.py +17 -47
- prefect/task_engine.py +697 -58
- prefect/task_runners.py +269 -301
- prefect/task_server.py +53 -34
- prefect/tasks.py +327 -337
- prefect/transactions.py +220 -0
- prefect/types/__init__.py +61 -82
- prefect/utilities/asyncutils.py +195 -136
- prefect/utilities/callables.py +311 -43
- prefect/utilities/collections.py +23 -38
- prefect/utilities/dispatch.py +11 -3
- prefect/utilities/dockerutils.py +4 -0
- prefect/utilities/engine.py +140 -20
- prefect/utilities/importtools.py +97 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +5 -1
- prefect/utilities/templating.py +12 -2
- prefect/variables.py +78 -61
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +15 -17
- prefect/workers/process.py +3 -8
- prefect/workers/server.py +2 -2
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
- prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
- prefect/_internal/pydantic/_base_model.py +0 -51
- prefect/_internal/pydantic/_compat.py +0 -82
- prefect/_internal/pydantic/_flags.py +0 -20
- prefect/_internal/pydantic/_types.py +0 -8
- prefect/_internal/pydantic/utilities/__init__.py +0 -0
- prefect/_internal/pydantic/utilities/config_dict.py +0 -72
- prefect/_internal/pydantic/utilities/field_validator.py +0 -150
- prefect/_internal/pydantic/utilities/model_construct.py +0 -56
- prefect/_internal/pydantic/utilities/model_copy.py +0 -55
- prefect/_internal/pydantic/utilities/model_dump.py +0 -136
- prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
- prefect/_internal/pydantic/utilities/model_fields.py +0 -50
- prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
- prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
- prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
- prefect/_internal/pydantic/utilities/model_validate.py +0 -75
- prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
- prefect/_internal/pydantic/utilities/model_validator.py +0 -87
- prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
- prefect/_vendor/__init__.py +0 -0
- prefect/_vendor/fastapi/__init__.py +0 -25
- prefect/_vendor/fastapi/applications.py +0 -946
- prefect/_vendor/fastapi/background.py +0 -3
- prefect/_vendor/fastapi/concurrency.py +0 -44
- prefect/_vendor/fastapi/datastructures.py +0 -58
- prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
- prefect/_vendor/fastapi/dependencies/models.py +0 -64
- prefect/_vendor/fastapi/dependencies/utils.py +0 -877
- prefect/_vendor/fastapi/encoders.py +0 -177
- prefect/_vendor/fastapi/exception_handlers.py +0 -40
- prefect/_vendor/fastapi/exceptions.py +0 -46
- prefect/_vendor/fastapi/logger.py +0 -3
- prefect/_vendor/fastapi/middleware/__init__.py +0 -1
- prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
- prefect/_vendor/fastapi/middleware/cors.py +0 -3
- prefect/_vendor/fastapi/middleware/gzip.py +0 -3
- prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
- prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
- prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
- prefect/_vendor/fastapi/openapi/__init__.py +0 -0
- prefect/_vendor/fastapi/openapi/constants.py +0 -2
- prefect/_vendor/fastapi/openapi/docs.py +0 -203
- prefect/_vendor/fastapi/openapi/models.py +0 -480
- prefect/_vendor/fastapi/openapi/utils.py +0 -485
- prefect/_vendor/fastapi/param_functions.py +0 -340
- prefect/_vendor/fastapi/params.py +0 -453
- prefect/_vendor/fastapi/requests.py +0 -4
- prefect/_vendor/fastapi/responses.py +0 -40
- prefect/_vendor/fastapi/routing.py +0 -1331
- prefect/_vendor/fastapi/security/__init__.py +0 -15
- prefect/_vendor/fastapi/security/api_key.py +0 -98
- prefect/_vendor/fastapi/security/base.py +0 -6
- prefect/_vendor/fastapi/security/http.py +0 -172
- prefect/_vendor/fastapi/security/oauth2.py +0 -227
- prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
- prefect/_vendor/fastapi/security/utils.py +0 -10
- prefect/_vendor/fastapi/staticfiles.py +0 -1
- prefect/_vendor/fastapi/templating.py +0 -3
- prefect/_vendor/fastapi/testclient.py +0 -1
- prefect/_vendor/fastapi/types.py +0 -3
- prefect/_vendor/fastapi/utils.py +0 -235
- prefect/_vendor/fastapi/websockets.py +0 -7
- prefect/_vendor/starlette/__init__.py +0 -1
- prefect/_vendor/starlette/_compat.py +0 -28
- prefect/_vendor/starlette/_exception_handler.py +0 -80
- prefect/_vendor/starlette/_utils.py +0 -88
- prefect/_vendor/starlette/applications.py +0 -261
- prefect/_vendor/starlette/authentication.py +0 -159
- prefect/_vendor/starlette/background.py +0 -43
- prefect/_vendor/starlette/concurrency.py +0 -59
- prefect/_vendor/starlette/config.py +0 -151
- prefect/_vendor/starlette/convertors.py +0 -87
- prefect/_vendor/starlette/datastructures.py +0 -707
- prefect/_vendor/starlette/endpoints.py +0 -130
- prefect/_vendor/starlette/exceptions.py +0 -60
- prefect/_vendor/starlette/formparsers.py +0 -276
- prefect/_vendor/starlette/middleware/__init__.py +0 -17
- prefect/_vendor/starlette/middleware/authentication.py +0 -52
- prefect/_vendor/starlette/middleware/base.py +0 -220
- prefect/_vendor/starlette/middleware/cors.py +0 -176
- prefect/_vendor/starlette/middleware/errors.py +0 -265
- prefect/_vendor/starlette/middleware/exceptions.py +0 -74
- prefect/_vendor/starlette/middleware/gzip.py +0 -113
- prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
- prefect/_vendor/starlette/middleware/sessions.py +0 -82
- prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
- prefect/_vendor/starlette/middleware/wsgi.py +0 -147
- prefect/_vendor/starlette/requests.py +0 -328
- prefect/_vendor/starlette/responses.py +0 -347
- prefect/_vendor/starlette/routing.py +0 -933
- prefect/_vendor/starlette/schemas.py +0 -154
- prefect/_vendor/starlette/staticfiles.py +0 -248
- prefect/_vendor/starlette/status.py +0 -199
- prefect/_vendor/starlette/templating.py +0 -231
- prefect/_vendor/starlette/testclient.py +0 -804
- prefect/_vendor/starlette/types.py +0 -30
- prefect/_vendor/starlette/websockets.py +0 -193
- prefect/agent.py +0 -698
- prefect/deployments/deployments.py +0 -1042
- prefect/deprecated/__init__.py +0 -0
- prefect/deprecated/data_documents.py +0 -350
- prefect/deprecated/packaging/__init__.py +0 -12
- prefect/deprecated/packaging/base.py +0 -96
- prefect/deprecated/packaging/docker.py +0 -146
- prefect/deprecated/packaging/file.py +0 -92
- prefect/deprecated/packaging/orion.py +0 -80
- prefect/deprecated/packaging/serializers.py +0 -171
- prefect/events/instrument.py +0 -135
- prefect/infrastructure/base.py +0 -323
- prefect/infrastructure/container.py +0 -818
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- prefect/new_task_engine.py +0 -423
- prefect/pydantic/__init__.py +0 -76
- prefect/pydantic/main.py +0 -39
- prefect/software/__init__.py +0 -2
- prefect/software/base.py +0 -50
- prefect/software/conda.py +0 -199
- prefect/software/pip.py +0 -122
- prefect/software/python.py +0 -52
- prefect/workers/block.py +0 -218
- prefect_client-2.19.2.dist-info/RECORD +0 -292
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
- {prefect_client-2.19.2.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
prefect/utilities/engine.py
CHANGED
@@ -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
|
-
|
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
|
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
|
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
|
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 =
|
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.
|
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
|
prefect/utilities/importtools.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
+
import ast
|
1
2
|
import importlib
|
2
3
|
import importlib.util
|
3
|
-
import inspect
|
4
4
|
import os
|
5
5
|
import runpy
|
6
6
|
import sys
|
7
|
+
import warnings
|
7
8
|
from importlib.abc import Loader, MetaPathFinder
|
8
9
|
from importlib.machinery import ModuleSpec
|
9
10
|
from pathlib import Path
|
@@ -14,8 +15,11 @@ from typing import Any, Callable, Dict, Iterable, NamedTuple, Optional, Union
|
|
14
15
|
import fsspec
|
15
16
|
|
16
17
|
from prefect.exceptions import ScriptError
|
18
|
+
from prefect.logging.loggers import get_logger
|
17
19
|
from prefect.utilities.filesystem import filename, is_local_path, tmpchdir
|
18
20
|
|
21
|
+
logger = get_logger(__name__)
|
22
|
+
|
19
23
|
|
20
24
|
def to_qualified_name(obj: Any) -> str:
|
21
25
|
"""
|
@@ -224,24 +228,18 @@ class DelayedImportErrorModule(ModuleType):
|
|
224
228
|
[1]: https://github.com/scientific-python/lazy_loader
|
225
229
|
"""
|
226
230
|
|
227
|
-
def __init__(self,
|
228
|
-
self.
|
231
|
+
def __init__(self, error_message, help_message, *args, **kwargs):
|
232
|
+
self.__error_message = error_message
|
229
233
|
self.__help_message = (
|
230
234
|
help_message or "Import errors for this module are only reported when used."
|
231
235
|
)
|
232
236
|
super().__init__(*args, **kwargs)
|
233
237
|
|
234
238
|
def __getattr__(self, attr):
|
235
|
-
if attr in ("__class__", "__file__", "
|
239
|
+
if attr in ("__class__", "__file__", "__help_message"):
|
236
240
|
super().__getattr__(attr)
|
237
241
|
else:
|
238
|
-
|
239
|
-
raise ModuleNotFoundError(
|
240
|
-
f"No module named '{fd['spec']}'\n\nThis module was originally imported"
|
241
|
-
f" at:\n File \"{fd['filename']}\", line {fd['lineno']}, in"
|
242
|
-
f" {fd['function']}\n\n {''.join(fd['code_context']).strip()}\n"
|
243
|
-
+ self.__help_message
|
244
|
-
)
|
242
|
+
raise ModuleNotFoundError(self.__error_message)
|
245
243
|
|
246
244
|
|
247
245
|
def lazy_import(
|
@@ -252,6 +250,13 @@ def lazy_import(
|
|
252
250
|
Use this to retain module-level imports for libraries that we don't want to
|
253
251
|
actually import until they are needed.
|
254
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
|
+
|
255
260
|
Adapted from the [Python documentation][1] and [lazy_loader][2]
|
256
261
|
|
257
262
|
[1]: https://docs.python.org/3/library/importlib.html#implementing-lazy-imports
|
@@ -263,25 +268,23 @@ def lazy_import(
|
|
263
268
|
except KeyError:
|
264
269
|
pass
|
265
270
|
|
271
|
+
if "." in name:
|
272
|
+
warnings.warn(
|
273
|
+
"Lazy importing subpackages can lead to unexpected behavior.",
|
274
|
+
RuntimeWarning,
|
275
|
+
)
|
276
|
+
|
266
277
|
spec = importlib.util.find_spec(name)
|
278
|
+
|
267
279
|
if spec is None:
|
280
|
+
import_error_message = f"No module named '{name}'.\n{help_message}"
|
281
|
+
|
268
282
|
if error_on_import:
|
269
|
-
raise ModuleNotFoundError(
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
"spec": name,
|
275
|
-
"filename": parent.filename,
|
276
|
-
"lineno": parent.lineno,
|
277
|
-
"function": parent.function,
|
278
|
-
"code_context": parent.code_context,
|
279
|
-
}
|
280
|
-
return DelayedImportErrorModule(
|
281
|
-
frame_data, help_message, "DelayedImportErrorModule"
|
282
|
-
)
|
283
|
-
finally:
|
284
|
-
del parent
|
283
|
+
raise ModuleNotFoundError(import_error_message)
|
284
|
+
|
285
|
+
return DelayedImportErrorModule(
|
286
|
+
import_error_message, help_message, "DelayedImportErrorModule"
|
287
|
+
)
|
285
288
|
|
286
289
|
module = importlib.util.module_from_spec(spec)
|
287
290
|
sys.modules[name] = module
|
@@ -356,3 +359,70 @@ class AliasedModuleLoader(Loader):
|
|
356
359
|
if self.callback is not None:
|
357
360
|
self.callback(self.alias)
|
358
361
|
sys.modules[self.alias] = root_module
|
362
|
+
|
363
|
+
|
364
|
+
def safe_load_namespace(source_code: str):
|
365
|
+
"""
|
366
|
+
Safely load a namespace from source code.
|
367
|
+
|
368
|
+
This function will attempt to import all modules and classes defined in the source
|
369
|
+
code. If an import fails, the error is caught and the import is skipped. This function
|
370
|
+
will also attempt to compile and evaluate class and function definitions locally.
|
371
|
+
|
372
|
+
Args:
|
373
|
+
source_code: The source code to load
|
374
|
+
|
375
|
+
Returns:
|
376
|
+
The namespace loaded from the source code. Can be used when evaluating source
|
377
|
+
code.
|
378
|
+
"""
|
379
|
+
parsed_code = ast.parse(source_code)
|
380
|
+
|
381
|
+
namespace = {}
|
382
|
+
|
383
|
+
# Walk through the AST and find all import statements
|
384
|
+
for node in ast.walk(parsed_code):
|
385
|
+
if isinstance(node, ast.Import):
|
386
|
+
for alias in node.names:
|
387
|
+
module_name = alias.name
|
388
|
+
as_name = alias.asname if alias.asname else module_name
|
389
|
+
try:
|
390
|
+
# Attempt to import the module
|
391
|
+
namespace[as_name] = importlib.import_module(module_name)
|
392
|
+
logger.debug("Successfully imported %s", module_name)
|
393
|
+
except ImportError as e:
|
394
|
+
logger.debug(f"Failed to import {module_name}: {e}")
|
395
|
+
elif isinstance(node, ast.ImportFrom):
|
396
|
+
module_name = node.module
|
397
|
+
if module_name is None:
|
398
|
+
continue
|
399
|
+
try:
|
400
|
+
module = importlib.import_module(module_name)
|
401
|
+
for alias in node.names:
|
402
|
+
name = alias.name
|
403
|
+
asname = alias.asname if alias.asname else name
|
404
|
+
try:
|
405
|
+
# Get the specific attribute from the module
|
406
|
+
attribute = getattr(module, name)
|
407
|
+
namespace[asname] = attribute
|
408
|
+
except AttributeError as e:
|
409
|
+
logger.debug(
|
410
|
+
"Failed to retrieve %s from %s: %s", name, module_name, e
|
411
|
+
)
|
412
|
+
except ImportError as e:
|
413
|
+
logger.debug("Failed to import from %s: %s", node.module, e)
|
414
|
+
|
415
|
+
# Handle local class definitions
|
416
|
+
for node in ast.walk(parsed_code):
|
417
|
+
if isinstance(node, (ast.ClassDef, ast.FunctionDef)):
|
418
|
+
try:
|
419
|
+
# Compile and execute each class and function definition locally
|
420
|
+
code = compile(
|
421
|
+
ast.Module(body=[node], type_ignores=[]),
|
422
|
+
filename="<ast>",
|
423
|
+
mode="exec",
|
424
|
+
)
|
425
|
+
exec(code, namespace)
|
426
|
+
except Exception as e:
|
427
|
+
logger.debug("Failed to compile class definition: %s", e)
|
428
|
+
return namespace
|