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.
- prefect/__init__.py +74 -110
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/compatibility/migration.py +166 -0
- prefect/_internal/concurrency/__init__.py +2 -2
- prefect/_internal/concurrency/api.py +1 -35
- 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/primitives.py +1 -0
- prefect/_internal/concurrency/services.py +23 -0
- prefect/_internal/concurrency/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/integrations.py +7 -0
- prefect/_internal/pydantic/__init__.py +0 -45
- prefect/_internal/pydantic/annotations/pendulum.py +2 -2
- 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/pytz.py +1 -1
- prefect/_internal/retries.py +61 -0
- prefect/_internal/schemas/bases.py +45 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +47 -233
- prefect/agent.py +3 -695
- prefect/artifacts.py +173 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +405 -153
- prefect/blocks/fields.py +2 -57
- prefect/blocks/notifications.py +43 -28
- prefect/blocks/redis.py +168 -0
- prefect/blocks/system.py +67 -20
- prefect/blocks/webhook.py +2 -9
- prefect/cache_policies.py +239 -0
- prefect/client/__init__.py +4 -0
- prefect/client/base.py +33 -27
- prefect/client/cloud.py +65 -20
- prefect/client/collections.py +1 -1
- prefect/client/orchestration.py +650 -442
- prefect/client/schemas/actions.py +115 -100
- prefect/client/schemas/filters.py +46 -52
- prefect/client/schemas/objects.py +228 -178
- prefect/client/schemas/responses.py +18 -36
- prefect/client/schemas/schedules.py +55 -36
- prefect/client/schemas/sorting.py +2 -0
- prefect/client/subscriptions.py +8 -7
- prefect/client/types/flexible_schedule_list.py +11 -0
- prefect/client/utilities.py +9 -6
- prefect/concurrency/asyncio.py +60 -11
- prefect/concurrency/context.py +24 -0
- prefect/concurrency/events.py +2 -2
- prefect/concurrency/services.py +46 -16
- prefect/concurrency/sync.py +51 -7
- prefect/concurrency/v1/asyncio.py +143 -0
- prefect/concurrency/v1/context.py +27 -0
- prefect/concurrency/v1/events.py +61 -0
- prefect/concurrency/v1/services.py +116 -0
- prefect/concurrency/v1/sync.py +92 -0
- prefect/context.py +246 -149
- prefect/deployments/__init__.py +33 -18
- prefect/deployments/base.py +10 -15
- prefect/deployments/deployments.py +2 -1048
- prefect/deployments/flow_runs.py +178 -0
- prefect/deployments/runner.py +72 -173
- prefect/deployments/schedules.py +31 -25
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +7 -0
- prefect/deployments/steps/pull.py +15 -21
- prefect/deployments/steps/utility.py +2 -1
- prefect/docker/__init__.py +20 -0
- prefect/docker/docker_image.py +82 -0
- prefect/engine.py +15 -2475
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +20 -7
- prefect/events/clients.py +142 -80
- prefect/events/filters.py +14 -18
- prefect/events/related.py +74 -75
- 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 +46 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +4 -5
- prefect/events/worker.py +23 -8
- prefect/exceptions.py +15 -0
- prefect/filesystems.py +30 -529
- prefect/flow_engine.py +827 -0
- prefect/flow_runs.py +379 -7
- prefect/flows.py +470 -360
- prefect/futures.py +382 -331
- prefect/infrastructure/__init__.py +5 -26
- prefect/infrastructure/base.py +3 -320
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +13 -8
- prefect/infrastructure/provisioners/container_instance.py +14 -9
- prefect/infrastructure/provisioners/ecs.py +10 -8
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/__init__.py +4 -0
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +9 -9
- prefect/logging/formatters.py +2 -4
- prefect/logging/handlers.py +9 -14
- prefect/logging/loggers.py +5 -5
- prefect/main.py +72 -0
- prefect/plugins.py +2 -64
- prefect/profiles.toml +16 -2
- prefect/records/__init__.py +1 -0
- prefect/records/base.py +223 -0
- prefect/records/filesystem.py +207 -0
- prefect/records/memory.py +178 -0
- prefect/records/result_store.py +64 -0
- prefect/results.py +577 -504
- prefect/runner/runner.py +117 -47
- prefect/runner/server.py +32 -34
- prefect/runner/storage.py +3 -12
- prefect/runner/submit.py +2 -10
- prefect/runner/utils.py +2 -2
- prefect/runtime/__init__.py +1 -0
- prefect/runtime/deployment.py +1 -0
- prefect/runtime/flow_run.py +40 -5
- prefect/runtime/task_run.py +1 -0
- prefect/serializers.py +28 -39
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +209 -332
- prefect/states.py +160 -63
- prefect/task_engine.py +1478 -57
- prefect/task_runners.py +383 -287
- prefect/task_runs.py +240 -0
- prefect/task_worker.py +463 -0
- prefect/tasks.py +684 -374
- prefect/transactions.py +410 -0
- prefect/types/__init__.py +72 -86
- prefect/types/entrypoint.py +13 -0
- prefect/utilities/annotations.py +4 -3
- prefect/utilities/asyncutils.py +227 -148
- prefect/utilities/callables.py +137 -45
- prefect/utilities/collections.py +134 -86
- prefect/utilities/dispatch.py +27 -14
- prefect/utilities/dockerutils.py +11 -4
- prefect/utilities/engine.py +186 -32
- prefect/utilities/filesystem.py +4 -5
- prefect/utilities/importtools.py +26 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +18 -1
- prefect/utilities/schema_tools/validation.py +30 -0
- prefect/utilities/services.py +35 -9
- prefect/utilities/templating.py +12 -2
- prefect/utilities/timeout.py +20 -5
- prefect/utilities/urls.py +195 -0
- prefect/utilities/visualization.py +1 -0
- prefect/variables.py +78 -59
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +237 -244
- prefect/workers/block.py +5 -226
- prefect/workers/cloud.py +6 -0
- prefect/workers/process.py +265 -12
- prefect/workers/server.py +29 -11
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
- prefect_client-3.0.0.dist-info/RECORD +201 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
- 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/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/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/py.typed +0 -0
- 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/py.typed +0 -0
- 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/blocks/kubernetes.py +0 -119
- 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/container.py +0 -824
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- prefect/manifests.py +0 -20
- prefect/new_flow_engine.py +0 -449
- 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/task_server.py +0 -322
- prefect_client-2.20.4.dist-info/RECORD +0 -294
- /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
- /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
- {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
prefect/transactions.py
ADDED
@@ -0,0 +1,410 @@
|
|
1
|
+
import copy
|
2
|
+
import logging
|
3
|
+
from contextlib import contextmanager
|
4
|
+
from contextvars import ContextVar, Token
|
5
|
+
from functools import partial
|
6
|
+
from typing import (
|
7
|
+
Any,
|
8
|
+
Callable,
|
9
|
+
Dict,
|
10
|
+
Generator,
|
11
|
+
List,
|
12
|
+
Optional,
|
13
|
+
Type,
|
14
|
+
Union,
|
15
|
+
)
|
16
|
+
|
17
|
+
from pydantic import Field, PrivateAttr
|
18
|
+
from typing_extensions import Self
|
19
|
+
|
20
|
+
from prefect.context import ContextModel, FlowRunContext, TaskRunContext
|
21
|
+
from prefect.exceptions import MissingContextError, SerializationError
|
22
|
+
from prefect.logging.loggers import get_logger, get_run_logger
|
23
|
+
from prefect.records import RecordStore
|
24
|
+
from prefect.results import (
|
25
|
+
BaseResult,
|
26
|
+
ResultStore,
|
27
|
+
get_default_result_storage,
|
28
|
+
)
|
29
|
+
from prefect.utilities.annotations import NotSet
|
30
|
+
from prefect.utilities.collections import AutoEnum
|
31
|
+
from prefect.utilities.engine import _get_hook_name
|
32
|
+
|
33
|
+
|
34
|
+
class IsolationLevel(AutoEnum):
|
35
|
+
READ_COMMITTED = AutoEnum.auto()
|
36
|
+
SERIALIZABLE = AutoEnum.auto()
|
37
|
+
|
38
|
+
|
39
|
+
class CommitMode(AutoEnum):
|
40
|
+
EAGER = AutoEnum.auto()
|
41
|
+
LAZY = AutoEnum.auto()
|
42
|
+
OFF = AutoEnum.auto()
|
43
|
+
|
44
|
+
|
45
|
+
class TransactionState(AutoEnum):
|
46
|
+
PENDING = AutoEnum.auto()
|
47
|
+
ACTIVE = AutoEnum.auto()
|
48
|
+
STAGED = AutoEnum.auto()
|
49
|
+
COMMITTED = AutoEnum.auto()
|
50
|
+
ROLLED_BACK = AutoEnum.auto()
|
51
|
+
|
52
|
+
|
53
|
+
class Transaction(ContextModel):
|
54
|
+
"""
|
55
|
+
A base model for transaction state.
|
56
|
+
"""
|
57
|
+
|
58
|
+
store: Optional[RecordStore] = None
|
59
|
+
key: Optional[str] = None
|
60
|
+
children: List["Transaction"] = Field(default_factory=list)
|
61
|
+
commit_mode: Optional[CommitMode] = None
|
62
|
+
isolation_level: Optional[IsolationLevel] = IsolationLevel.READ_COMMITTED
|
63
|
+
state: TransactionState = TransactionState.PENDING
|
64
|
+
on_commit_hooks: List[Callable[["Transaction"], None]] = Field(default_factory=list)
|
65
|
+
on_rollback_hooks: List[Callable[["Transaction"], None]] = Field(
|
66
|
+
default_factory=list
|
67
|
+
)
|
68
|
+
overwrite: bool = False
|
69
|
+
logger: Union[logging.Logger, logging.LoggerAdapter] = Field(
|
70
|
+
default_factory=partial(get_logger, "transactions")
|
71
|
+
)
|
72
|
+
_stored_values: Dict[str, Any] = PrivateAttr(default_factory=dict)
|
73
|
+
_staged_value: Any = None
|
74
|
+
__var__: ContextVar = ContextVar("transaction")
|
75
|
+
|
76
|
+
def set(self, name: str, value: Any) -> None:
|
77
|
+
self._stored_values[name] = value
|
78
|
+
|
79
|
+
def get(self, name: str, default: Any = NotSet) -> Any:
|
80
|
+
if name not in self._stored_values:
|
81
|
+
if default is not NotSet:
|
82
|
+
return default
|
83
|
+
raise ValueError(f"Could not retrieve value for unknown key: {name}")
|
84
|
+
return self._stored_values.get(name)
|
85
|
+
|
86
|
+
def is_committed(self) -> bool:
|
87
|
+
return self.state == TransactionState.COMMITTED
|
88
|
+
|
89
|
+
def is_rolled_back(self) -> bool:
|
90
|
+
return self.state == TransactionState.ROLLED_BACK
|
91
|
+
|
92
|
+
def is_staged(self) -> bool:
|
93
|
+
return self.state == TransactionState.STAGED
|
94
|
+
|
95
|
+
def is_pending(self) -> bool:
|
96
|
+
return self.state == TransactionState.PENDING
|
97
|
+
|
98
|
+
def is_active(self) -> bool:
|
99
|
+
return self.state == TransactionState.ACTIVE
|
100
|
+
|
101
|
+
def __enter__(self):
|
102
|
+
if self._token is not None:
|
103
|
+
raise RuntimeError(
|
104
|
+
"Context already entered. Context enter calls cannot be nested."
|
105
|
+
)
|
106
|
+
parent = get_transaction()
|
107
|
+
if parent:
|
108
|
+
self._stored_values = copy.deepcopy(parent._stored_values)
|
109
|
+
# set default commit behavior; either inherit from parent or set a default of eager
|
110
|
+
if self.commit_mode is None:
|
111
|
+
self.commit_mode = parent.commit_mode if parent else CommitMode.LAZY
|
112
|
+
# set default isolation level; either inherit from parent or set a default of read committed
|
113
|
+
if self.isolation_level is None:
|
114
|
+
self.isolation_level = (
|
115
|
+
parent.isolation_level if parent else IsolationLevel.READ_COMMITTED
|
116
|
+
)
|
117
|
+
|
118
|
+
assert self.isolation_level is not None, "Isolation level was not set correctly"
|
119
|
+
if (
|
120
|
+
self.store
|
121
|
+
and self.key
|
122
|
+
and not self.store.supports_isolation_level(self.isolation_level)
|
123
|
+
):
|
124
|
+
raise ValueError(
|
125
|
+
f"Isolation level {self.isolation_level.name} is not supported by record store type {self.store.__class__.__name__}"
|
126
|
+
)
|
127
|
+
|
128
|
+
# this needs to go before begin, which could set the state to committed
|
129
|
+
self.state = TransactionState.ACTIVE
|
130
|
+
self.begin()
|
131
|
+
self._token = self.__var__.set(self)
|
132
|
+
return self
|
133
|
+
|
134
|
+
def __exit__(self, *exc_info):
|
135
|
+
exc_type, exc_val, _ = exc_info
|
136
|
+
if not self._token:
|
137
|
+
raise RuntimeError(
|
138
|
+
"Asymmetric use of context. Context exit called without an enter."
|
139
|
+
)
|
140
|
+
if exc_type:
|
141
|
+
self.rollback()
|
142
|
+
self.reset()
|
143
|
+
raise exc_val
|
144
|
+
|
145
|
+
if self.commit_mode == CommitMode.EAGER:
|
146
|
+
self.commit()
|
147
|
+
|
148
|
+
# if parent, let them take responsibility
|
149
|
+
if self.get_parent():
|
150
|
+
self.reset()
|
151
|
+
return
|
152
|
+
|
153
|
+
if self.commit_mode == CommitMode.OFF:
|
154
|
+
# if no one took responsibility to commit, rolling back
|
155
|
+
# note that rollback returns if already committed
|
156
|
+
self.rollback()
|
157
|
+
elif self.commit_mode == CommitMode.LAZY:
|
158
|
+
# no one left to take responsibility for committing
|
159
|
+
self.commit()
|
160
|
+
|
161
|
+
self.reset()
|
162
|
+
|
163
|
+
def begin(self):
|
164
|
+
if (
|
165
|
+
self.store
|
166
|
+
and self.key
|
167
|
+
and self.isolation_level == IsolationLevel.SERIALIZABLE
|
168
|
+
):
|
169
|
+
self.logger.debug(f"Acquiring lock for transaction {self.key!r}")
|
170
|
+
self.store.acquire_lock(self.key)
|
171
|
+
if (
|
172
|
+
not self.overwrite
|
173
|
+
and self.store
|
174
|
+
and self.key
|
175
|
+
and self.store.exists(key=self.key)
|
176
|
+
):
|
177
|
+
self.state = TransactionState.COMMITTED
|
178
|
+
|
179
|
+
def read(self) -> Optional[BaseResult]:
|
180
|
+
if self.store and self.key:
|
181
|
+
record = self.store.read(key=self.key)
|
182
|
+
if record is not None:
|
183
|
+
return record.result
|
184
|
+
return None
|
185
|
+
|
186
|
+
def reset(self) -> None:
|
187
|
+
parent = self.get_parent()
|
188
|
+
|
189
|
+
if parent:
|
190
|
+
# parent takes responsibility
|
191
|
+
parent.add_child(self)
|
192
|
+
|
193
|
+
if self._token:
|
194
|
+
self.__var__.reset(self._token)
|
195
|
+
self._token = None
|
196
|
+
|
197
|
+
# do this below reset so that get_transaction() returns the relevant txn
|
198
|
+
if parent and self.state == TransactionState.ROLLED_BACK:
|
199
|
+
parent.rollback()
|
200
|
+
|
201
|
+
def add_child(self, transaction: "Transaction") -> None:
|
202
|
+
self.children.append(transaction)
|
203
|
+
|
204
|
+
def get_parent(self) -> Optional["Transaction"]:
|
205
|
+
prev_var = getattr(self._token, "old_value")
|
206
|
+
if prev_var != Token.MISSING:
|
207
|
+
parent = prev_var
|
208
|
+
else:
|
209
|
+
parent = None
|
210
|
+
return parent
|
211
|
+
|
212
|
+
def commit(self) -> bool:
|
213
|
+
if self.state in [TransactionState.ROLLED_BACK, TransactionState.COMMITTED]:
|
214
|
+
if (
|
215
|
+
self.store
|
216
|
+
and self.key
|
217
|
+
and self.isolation_level == IsolationLevel.SERIALIZABLE
|
218
|
+
):
|
219
|
+
self.logger.debug(f"Releasing lock for transaction {self.key!r}")
|
220
|
+
self.store.release_lock(self.key)
|
221
|
+
|
222
|
+
return False
|
223
|
+
|
224
|
+
try:
|
225
|
+
for child in self.children:
|
226
|
+
child.commit()
|
227
|
+
|
228
|
+
for hook in self.on_commit_hooks:
|
229
|
+
self.run_hook(hook, "commit")
|
230
|
+
|
231
|
+
if self.store and self.key:
|
232
|
+
self.store.write(key=self.key, result=self._staged_value)
|
233
|
+
self.state = TransactionState.COMMITTED
|
234
|
+
if (
|
235
|
+
self.store
|
236
|
+
and self.key
|
237
|
+
and self.isolation_level == IsolationLevel.SERIALIZABLE
|
238
|
+
):
|
239
|
+
self.logger.debug(f"Releasing lock for transaction {self.key!r}")
|
240
|
+
self.store.release_lock(self.key)
|
241
|
+
return True
|
242
|
+
except SerializationError as exc:
|
243
|
+
if self.logger:
|
244
|
+
self.logger.warning(
|
245
|
+
f"Encountered an error while serializing result for transaction {self.key!r}: {exc}"
|
246
|
+
" Code execution will continue, but the transaction will not be committed.",
|
247
|
+
)
|
248
|
+
self.rollback()
|
249
|
+
return False
|
250
|
+
except Exception:
|
251
|
+
if self.logger:
|
252
|
+
self.logger.exception(
|
253
|
+
f"An error was encountered while committing transaction {self.key!r}",
|
254
|
+
exc_info=True,
|
255
|
+
)
|
256
|
+
self.rollback()
|
257
|
+
return False
|
258
|
+
|
259
|
+
def run_hook(self, hook, hook_type: str) -> None:
|
260
|
+
hook_name = _get_hook_name(hook)
|
261
|
+
# Undocumented way to disable logging for a hook. Subject to change.
|
262
|
+
should_log = getattr(hook, "log_on_run", True)
|
263
|
+
|
264
|
+
if should_log:
|
265
|
+
self.logger.info(f"Running {hook_type} hook {hook_name!r}")
|
266
|
+
|
267
|
+
try:
|
268
|
+
hook(self)
|
269
|
+
except Exception as exc:
|
270
|
+
if should_log:
|
271
|
+
self.logger.error(
|
272
|
+
f"An error was encountered while running {hook_type} hook {hook_name!r}",
|
273
|
+
)
|
274
|
+
raise exc
|
275
|
+
else:
|
276
|
+
if should_log:
|
277
|
+
self.logger.info(
|
278
|
+
f"{hook_type.capitalize()} hook {hook_name!r} finished running successfully"
|
279
|
+
)
|
280
|
+
|
281
|
+
def stage(
|
282
|
+
self,
|
283
|
+
value: BaseResult,
|
284
|
+
on_rollback_hooks: Optional[List] = None,
|
285
|
+
on_commit_hooks: Optional[List] = None,
|
286
|
+
) -> None:
|
287
|
+
"""
|
288
|
+
Stage a value to be committed later.
|
289
|
+
"""
|
290
|
+
on_commit_hooks = on_commit_hooks or []
|
291
|
+
on_rollback_hooks = on_rollback_hooks or []
|
292
|
+
|
293
|
+
if self.state != TransactionState.COMMITTED:
|
294
|
+
self._staged_value = value
|
295
|
+
self.on_rollback_hooks += on_rollback_hooks
|
296
|
+
self.on_commit_hooks += on_commit_hooks
|
297
|
+
self.state = TransactionState.STAGED
|
298
|
+
|
299
|
+
def rollback(self) -> bool:
|
300
|
+
if self.state in [TransactionState.ROLLED_BACK, TransactionState.COMMITTED]:
|
301
|
+
return False
|
302
|
+
|
303
|
+
try:
|
304
|
+
for hook in reversed(self.on_rollback_hooks):
|
305
|
+
self.run_hook(hook, "rollback")
|
306
|
+
|
307
|
+
self.state = TransactionState.ROLLED_BACK
|
308
|
+
|
309
|
+
for child in reversed(self.children):
|
310
|
+
child.rollback()
|
311
|
+
|
312
|
+
return True
|
313
|
+
except Exception:
|
314
|
+
if self.logger:
|
315
|
+
self.logger.exception(
|
316
|
+
f"An error was encountered while rolling back transaction {self.key!r}",
|
317
|
+
exc_info=True,
|
318
|
+
)
|
319
|
+
return False
|
320
|
+
finally:
|
321
|
+
if (
|
322
|
+
self.store
|
323
|
+
and self.key
|
324
|
+
and self.isolation_level == IsolationLevel.SERIALIZABLE
|
325
|
+
):
|
326
|
+
self.logger.debug(f"Releasing lock for transaction {self.key!r}")
|
327
|
+
self.store.release_lock(self.key)
|
328
|
+
|
329
|
+
@classmethod
|
330
|
+
def get_active(cls: Type[Self]) -> Optional[Self]:
|
331
|
+
return cls.__var__.get(None)
|
332
|
+
|
333
|
+
|
334
|
+
def get_transaction() -> Optional[Transaction]:
|
335
|
+
return Transaction.get_active()
|
336
|
+
|
337
|
+
|
338
|
+
@contextmanager
|
339
|
+
def transaction(
|
340
|
+
key: Optional[str] = None,
|
341
|
+
store: Optional[RecordStore] = None,
|
342
|
+
commit_mode: Optional[CommitMode] = None,
|
343
|
+
isolation_level: Optional[IsolationLevel] = None,
|
344
|
+
overwrite: bool = False,
|
345
|
+
logger: Union[logging.Logger, logging.LoggerAdapter, None] = None,
|
346
|
+
) -> Generator[Transaction, None, None]:
|
347
|
+
"""
|
348
|
+
A context manager for opening and managing a transaction.
|
349
|
+
|
350
|
+
Args:
|
351
|
+
- key: An identifier to use for the transaction
|
352
|
+
- store: The store to use for persisting the transaction result. If not provided,
|
353
|
+
a default store will be used based on the current run context.
|
354
|
+
- commit_mode: The commit mode controlling when the transaction and
|
355
|
+
child transactions are committed
|
356
|
+
- overwrite: Whether to overwrite an existing transaction record in the store
|
357
|
+
|
358
|
+
Yields:
|
359
|
+
- Transaction: An object representing the transaction state
|
360
|
+
"""
|
361
|
+
# if there is no key, we won't persist a record
|
362
|
+
if key and not store:
|
363
|
+
flow_run_context = FlowRunContext.get()
|
364
|
+
task_run_context = TaskRunContext.get()
|
365
|
+
existing_store = getattr(task_run_context, "result_store", None) or getattr(
|
366
|
+
flow_run_context, "result_store", None
|
367
|
+
)
|
368
|
+
|
369
|
+
new_store: ResultStore
|
370
|
+
if existing_store and existing_store.result_storage_block_id:
|
371
|
+
new_store = existing_store.model_copy(
|
372
|
+
update={
|
373
|
+
"persist_result": True,
|
374
|
+
}
|
375
|
+
)
|
376
|
+
else:
|
377
|
+
default_storage = get_default_result_storage(_sync=True)
|
378
|
+
if existing_store:
|
379
|
+
new_store = existing_store.model_copy(
|
380
|
+
update={
|
381
|
+
"persist_result": True,
|
382
|
+
"storage_block": default_storage,
|
383
|
+
"storage_block_id": default_storage._block_document_id,
|
384
|
+
}
|
385
|
+
)
|
386
|
+
else:
|
387
|
+
new_store = ResultStore(
|
388
|
+
persist_result=True,
|
389
|
+
result_storage=default_storage,
|
390
|
+
)
|
391
|
+
from prefect.records.result_store import ResultRecordStore
|
392
|
+
|
393
|
+
store = ResultRecordStore(
|
394
|
+
result_store=new_store,
|
395
|
+
)
|
396
|
+
|
397
|
+
try:
|
398
|
+
logger = logger or get_run_logger()
|
399
|
+
except MissingContextError:
|
400
|
+
logger = get_logger("transactions")
|
401
|
+
|
402
|
+
with Transaction(
|
403
|
+
key=key,
|
404
|
+
store=store,
|
405
|
+
commit_mode=commit_mode,
|
406
|
+
isolation_level=isolation_level,
|
407
|
+
overwrite=overwrite,
|
408
|
+
logger=logger,
|
409
|
+
) as txn:
|
410
|
+
yield txn
|
prefect/types/__init__.py
CHANGED
@@ -1,112 +1,98 @@
|
|
1
|
-
from typing import Any,
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
from
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
schema: ClassVar[CoreSchema] = core_schema.int_schema(gt=0)
|
32
|
-
|
33
|
-
@classmethod
|
34
|
-
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
|
35
|
-
yield cls.validate
|
1
|
+
from typing import Annotated, Any, Dict, List, Union
|
2
|
+
import orjson
|
3
|
+
import pydantic
|
4
|
+
|
5
|
+
from pydantic import (
|
6
|
+
BeforeValidator,
|
7
|
+
Field,
|
8
|
+
StrictBool,
|
9
|
+
StrictFloat,
|
10
|
+
StrictInt,
|
11
|
+
StrictStr,
|
12
|
+
)
|
13
|
+
from zoneinfo import available_timezones
|
14
|
+
|
15
|
+
MAX_VARIABLE_NAME_LENGTH = 255
|
16
|
+
MAX_VARIABLE_VALUE_LENGTH = 5000
|
17
|
+
|
18
|
+
NonNegativeInteger = Annotated[int, Field(ge=0)]
|
19
|
+
PositiveInteger = Annotated[int, Field(gt=0)]
|
20
|
+
NonNegativeFloat = Annotated[float, Field(ge=0.0)]
|
21
|
+
|
22
|
+
TimeZone = Annotated[
|
23
|
+
str,
|
24
|
+
Field(
|
25
|
+
default="UTC",
|
26
|
+
pattern="|".join(
|
27
|
+
[z for z in sorted(available_timezones()) if "localtime" not in z]
|
28
|
+
),
|
29
|
+
),
|
30
|
+
]
|
36
31
|
|
37
|
-
@classmethod
|
38
|
-
def __get_pydantic_core_schema__(
|
39
|
-
cls, source_type: Any, handler: Callable[..., Any]
|
40
|
-
) -> CoreSchema:
|
41
|
-
return cls.schema
|
42
32
|
|
43
|
-
|
44
|
-
def validate(cls, v: Any) -> Self:
|
45
|
-
return SchemaValidator(schema=cls.schema).validate_python(v)
|
33
|
+
BANNED_CHARACTERS = ["/", "%", "&", ">", "<"]
|
46
34
|
|
35
|
+
WITHOUT_BANNED_CHARACTERS = r"^[^" + "".join(BANNED_CHARACTERS) + "]+$"
|
36
|
+
Name = Annotated[str, Field(pattern=WITHOUT_BANNED_CHARACTERS)]
|
47
37
|
|
48
|
-
|
49
|
-
|
38
|
+
WITHOUT_BANNED_CHARACTERS_EMPTY_OK = r"^[^" + "".join(BANNED_CHARACTERS) + "]*$"
|
39
|
+
NameOrEmpty = Annotated[str, Field(pattern=WITHOUT_BANNED_CHARACTERS_EMPTY_OK)]
|
50
40
|
|
51
|
-
@classmethod
|
52
|
-
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
|
53
|
-
yield cls.validate
|
54
41
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
) -> CoreSchema:
|
59
|
-
return cls.schema
|
42
|
+
def non_emptyish(value: str) -> str:
|
43
|
+
if not value.strip("' \""):
|
44
|
+
raise ValueError("name cannot be an empty string")
|
60
45
|
|
61
|
-
|
62
|
-
def validate(cls, v: Any) -> Self:
|
63
|
-
return SchemaValidator(schema=cls.schema).validate_python(v)
|
46
|
+
return value
|
64
47
|
|
65
48
|
|
66
|
-
|
67
|
-
|
49
|
+
NonEmptyishName = Annotated[
|
50
|
+
str,
|
51
|
+
Field(pattern=WITHOUT_BANNED_CHARACTERS),
|
52
|
+
BeforeValidator(non_emptyish),
|
53
|
+
]
|
68
54
|
|
69
|
-
schema: ClassVar = core_schema.timedelta_schema(ge=timedelta(seconds=0))
|
70
55
|
|
71
|
-
|
72
|
-
|
73
|
-
|
56
|
+
VariableValue = Union[
|
57
|
+
StrictStr,
|
58
|
+
StrictInt,
|
59
|
+
StrictBool,
|
60
|
+
StrictFloat,
|
61
|
+
None,
|
62
|
+
Dict[str, Any],
|
63
|
+
List[Any],
|
64
|
+
]
|
74
65
|
|
75
|
-
@classmethod
|
76
|
-
def __get_pydantic_core_schema__(
|
77
|
-
cls, source_type: Any, handler: Callable[..., Any]
|
78
|
-
) -> CoreSchema:
|
79
|
-
return cls.schema
|
80
66
|
|
81
|
-
|
82
|
-
|
83
|
-
|
67
|
+
def check_variable_value(value: object) -> object:
|
68
|
+
try:
|
69
|
+
json_string = orjson.dumps(value)
|
70
|
+
except orjson.JSONEncodeError:
|
71
|
+
raise ValueError("Variable value must be serializable to JSON")
|
84
72
|
|
73
|
+
if value is not None and len(json_string) > MAX_VARIABLE_VALUE_LENGTH:
|
74
|
+
raise ValueError(
|
75
|
+
f"Variable value must be less than {MAX_VARIABLE_VALUE_LENGTH} characters"
|
76
|
+
)
|
77
|
+
return value
|
85
78
|
|
86
|
-
class PositiveDuration(timedelta):
|
87
|
-
"""A timedelta that must be greater than 0."""
|
88
79
|
|
89
|
-
|
80
|
+
StrictVariableValue = Annotated[VariableValue, BeforeValidator(check_variable_value)]
|
90
81
|
|
91
|
-
|
92
|
-
def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
|
93
|
-
yield cls.validate
|
82
|
+
LaxUrl = Annotated[str, BeforeValidator(lambda x: str(x).strip())]
|
94
83
|
|
95
|
-
@classmethod
|
96
|
-
def __get_pydantic_core_schema__(
|
97
|
-
cls, source_type: Any, handler: Callable[..., Any]
|
98
|
-
) -> CoreSchema:
|
99
|
-
return cls.schema
|
100
84
|
|
101
|
-
|
102
|
-
|
103
|
-
return SchemaValidator(schema=cls.schema).validate_python(v)
|
85
|
+
class SecretDict(pydantic.Secret[Dict[str, Any]]):
|
86
|
+
pass
|
104
87
|
|
105
88
|
|
106
89
|
__all__ = [
|
107
90
|
"NonNegativeInteger",
|
108
91
|
"PositiveInteger",
|
109
92
|
"NonNegativeFloat",
|
110
|
-
"
|
111
|
-
"
|
93
|
+
"Name",
|
94
|
+
"NameOrEmpty",
|
95
|
+
"NonEmptyishName",
|
96
|
+
"SecretDict",
|
97
|
+
"StrictVariableValue",
|
112
98
|
]
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
|
4
|
+
class EntrypointType(Enum):
|
5
|
+
"""
|
6
|
+
Enum representing a entrypoint type.
|
7
|
+
|
8
|
+
File path entrypoints are in the format: `path/to/file.py:function_name`.
|
9
|
+
Module path entrypoints are in the format: `path.to.module.function_name`.
|
10
|
+
"""
|
11
|
+
|
12
|
+
FILE_PATH = "file_path"
|
13
|
+
MODULE_PATH = "module_path"
|
prefect/utilities/annotations.py
CHANGED
@@ -21,8 +21,8 @@ class BaseAnnotation(
|
|
21
21
|
def rewrap(self, value: T) -> "BaseAnnotation[T]":
|
22
22
|
return type(self)(value)
|
23
23
|
|
24
|
-
def __eq__(self, other:
|
25
|
-
if
|
24
|
+
def __eq__(self, other: "BaseAnnotation[T]") -> bool:
|
25
|
+
if type(self) is not type(other):
|
26
26
|
return False
|
27
27
|
return self.unwrap() == other.unwrap()
|
28
28
|
|
@@ -90,10 +90,11 @@ class quote(BaseAnnotation[T]):
|
|
90
90
|
class Quote(quote):
|
91
91
|
def __init__(self, expr):
|
92
92
|
warnings.warn(
|
93
|
-
DeprecationWarning,
|
94
93
|
"Use of `Quote` is deprecated. Use `quote` instead.",
|
94
|
+
DeprecationWarning,
|
95
95
|
stacklevel=2,
|
96
96
|
)
|
97
|
+
super().__init__(expr)
|
97
98
|
|
98
99
|
|
99
100
|
class NotSet:
|