prefect-client 2.20.4__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. prefect/__init__.py +74 -110
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/compatibility/migration.py +166 -0
  5. prefect/_internal/concurrency/__init__.py +2 -2
  6. prefect/_internal/concurrency/api.py +1 -35
  7. prefect/_internal/concurrency/calls.py +0 -6
  8. prefect/_internal/concurrency/cancellation.py +0 -3
  9. prefect/_internal/concurrency/event_loop.py +0 -20
  10. prefect/_internal/concurrency/inspection.py +3 -3
  11. prefect/_internal/concurrency/primitives.py +1 -0
  12. prefect/_internal/concurrency/services.py +23 -0
  13. prefect/_internal/concurrency/threads.py +35 -0
  14. prefect/_internal/concurrency/waiters.py +0 -28
  15. prefect/_internal/integrations.py +7 -0
  16. prefect/_internal/pydantic/__init__.py +0 -45
  17. prefect/_internal/pydantic/annotations/pendulum.py +2 -2
  18. prefect/_internal/pydantic/v1_schema.py +21 -22
  19. prefect/_internal/pydantic/v2_schema.py +0 -2
  20. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  21. prefect/_internal/pytz.py +1 -1
  22. prefect/_internal/retries.py +61 -0
  23. prefect/_internal/schemas/bases.py +45 -177
  24. prefect/_internal/schemas/fields.py +1 -43
  25. prefect/_internal/schemas/validators.py +47 -233
  26. prefect/agent.py +3 -695
  27. prefect/artifacts.py +173 -14
  28. prefect/automations.py +39 -4
  29. prefect/blocks/abstract.py +1 -1
  30. prefect/blocks/core.py +405 -153
  31. prefect/blocks/fields.py +2 -57
  32. prefect/blocks/notifications.py +43 -28
  33. prefect/blocks/redis.py +168 -0
  34. prefect/blocks/system.py +67 -20
  35. prefect/blocks/webhook.py +2 -9
  36. prefect/cache_policies.py +239 -0
  37. prefect/client/__init__.py +4 -0
  38. prefect/client/base.py +33 -27
  39. prefect/client/cloud.py +65 -20
  40. prefect/client/collections.py +1 -1
  41. prefect/client/orchestration.py +650 -442
  42. prefect/client/schemas/actions.py +115 -100
  43. prefect/client/schemas/filters.py +46 -52
  44. prefect/client/schemas/objects.py +228 -178
  45. prefect/client/schemas/responses.py +18 -36
  46. prefect/client/schemas/schedules.py +55 -36
  47. prefect/client/schemas/sorting.py +2 -0
  48. prefect/client/subscriptions.py +8 -7
  49. prefect/client/types/flexible_schedule_list.py +11 -0
  50. prefect/client/utilities.py +9 -6
  51. prefect/concurrency/asyncio.py +60 -11
  52. prefect/concurrency/context.py +24 -0
  53. prefect/concurrency/events.py +2 -2
  54. prefect/concurrency/services.py +46 -16
  55. prefect/concurrency/sync.py +51 -7
  56. prefect/concurrency/v1/asyncio.py +143 -0
  57. prefect/concurrency/v1/context.py +27 -0
  58. prefect/concurrency/v1/events.py +61 -0
  59. prefect/concurrency/v1/services.py +116 -0
  60. prefect/concurrency/v1/sync.py +92 -0
  61. prefect/context.py +246 -149
  62. prefect/deployments/__init__.py +33 -18
  63. prefect/deployments/base.py +10 -15
  64. prefect/deployments/deployments.py +2 -1048
  65. prefect/deployments/flow_runs.py +178 -0
  66. prefect/deployments/runner.py +72 -173
  67. prefect/deployments/schedules.py +31 -25
  68. prefect/deployments/steps/__init__.py +0 -1
  69. prefect/deployments/steps/core.py +7 -0
  70. prefect/deployments/steps/pull.py +15 -21
  71. prefect/deployments/steps/utility.py +2 -1
  72. prefect/docker/__init__.py +20 -0
  73. prefect/docker/docker_image.py +82 -0
  74. prefect/engine.py +15 -2475
  75. prefect/events/actions.py +17 -23
  76. prefect/events/cli/automations.py +20 -7
  77. prefect/events/clients.py +142 -80
  78. prefect/events/filters.py +14 -18
  79. prefect/events/related.py +74 -75
  80. prefect/events/schemas/__init__.py +0 -5
  81. prefect/events/schemas/automations.py +55 -46
  82. prefect/events/schemas/deployment_triggers.py +7 -197
  83. prefect/events/schemas/events.py +46 -65
  84. prefect/events/schemas/labelling.py +10 -14
  85. prefect/events/utilities.py +4 -5
  86. prefect/events/worker.py +23 -8
  87. prefect/exceptions.py +15 -0
  88. prefect/filesystems.py +30 -529
  89. prefect/flow_engine.py +827 -0
  90. prefect/flow_runs.py +379 -7
  91. prefect/flows.py +470 -360
  92. prefect/futures.py +382 -331
  93. prefect/infrastructure/__init__.py +5 -26
  94. prefect/infrastructure/base.py +3 -320
  95. prefect/infrastructure/provisioners/__init__.py +5 -3
  96. prefect/infrastructure/provisioners/cloud_run.py +13 -8
  97. prefect/infrastructure/provisioners/container_instance.py +14 -9
  98. prefect/infrastructure/provisioners/ecs.py +10 -8
  99. prefect/infrastructure/provisioners/modal.py +8 -5
  100. prefect/input/__init__.py +4 -0
  101. prefect/input/actions.py +2 -4
  102. prefect/input/run_input.py +9 -9
  103. prefect/logging/formatters.py +2 -4
  104. prefect/logging/handlers.py +9 -14
  105. prefect/logging/loggers.py +5 -5
  106. prefect/main.py +72 -0
  107. prefect/plugins.py +2 -64
  108. prefect/profiles.toml +16 -2
  109. prefect/records/__init__.py +1 -0
  110. prefect/records/base.py +223 -0
  111. prefect/records/filesystem.py +207 -0
  112. prefect/records/memory.py +178 -0
  113. prefect/records/result_store.py +64 -0
  114. prefect/results.py +577 -504
  115. prefect/runner/runner.py +117 -47
  116. prefect/runner/server.py +32 -34
  117. prefect/runner/storage.py +3 -12
  118. prefect/runner/submit.py +2 -10
  119. prefect/runner/utils.py +2 -2
  120. prefect/runtime/__init__.py +1 -0
  121. prefect/runtime/deployment.py +1 -0
  122. prefect/runtime/flow_run.py +40 -5
  123. prefect/runtime/task_run.py +1 -0
  124. prefect/serializers.py +28 -39
  125. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  126. prefect/settings.py +209 -332
  127. prefect/states.py +160 -63
  128. prefect/task_engine.py +1478 -57
  129. prefect/task_runners.py +383 -287
  130. prefect/task_runs.py +240 -0
  131. prefect/task_worker.py +463 -0
  132. prefect/tasks.py +684 -374
  133. prefect/transactions.py +410 -0
  134. prefect/types/__init__.py +72 -86
  135. prefect/types/entrypoint.py +13 -0
  136. prefect/utilities/annotations.py +4 -3
  137. prefect/utilities/asyncutils.py +227 -148
  138. prefect/utilities/callables.py +137 -45
  139. prefect/utilities/collections.py +134 -86
  140. prefect/utilities/dispatch.py +27 -14
  141. prefect/utilities/dockerutils.py +11 -4
  142. prefect/utilities/engine.py +186 -32
  143. prefect/utilities/filesystem.py +4 -5
  144. prefect/utilities/importtools.py +26 -27
  145. prefect/utilities/pydantic.py +128 -38
  146. prefect/utilities/schema_tools/hydration.py +18 -1
  147. prefect/utilities/schema_tools/validation.py +30 -0
  148. prefect/utilities/services.py +35 -9
  149. prefect/utilities/templating.py +12 -2
  150. prefect/utilities/timeout.py +20 -5
  151. prefect/utilities/urls.py +195 -0
  152. prefect/utilities/visualization.py +1 -0
  153. prefect/variables.py +78 -59
  154. prefect/workers/__init__.py +0 -1
  155. prefect/workers/base.py +237 -244
  156. prefect/workers/block.py +5 -226
  157. prefect/workers/cloud.py +6 -0
  158. prefect/workers/process.py +265 -12
  159. prefect/workers/server.py +29 -11
  160. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/METADATA +28 -24
  161. prefect_client-3.0.0.dist-info/RECORD +201 -0
  162. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/WHEEL +1 -1
  163. prefect/_internal/pydantic/_base_model.py +0 -51
  164. prefect/_internal/pydantic/_compat.py +0 -82
  165. prefect/_internal/pydantic/_flags.py +0 -20
  166. prefect/_internal/pydantic/_types.py +0 -8
  167. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  168. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  169. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  170. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  171. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  172. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  173. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  174. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  175. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  176. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  177. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  178. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  179. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  180. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  181. prefect/_vendor/fastapi/__init__.py +0 -25
  182. prefect/_vendor/fastapi/applications.py +0 -946
  183. prefect/_vendor/fastapi/background.py +0 -3
  184. prefect/_vendor/fastapi/concurrency.py +0 -44
  185. prefect/_vendor/fastapi/datastructures.py +0 -58
  186. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  187. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  188. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  189. prefect/_vendor/fastapi/encoders.py +0 -177
  190. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  191. prefect/_vendor/fastapi/exceptions.py +0 -46
  192. prefect/_vendor/fastapi/logger.py +0 -3
  193. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  194. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  195. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  196. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  197. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  198. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  199. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  200. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  201. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  202. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  203. prefect/_vendor/fastapi/openapi/models.py +0 -480
  204. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  205. prefect/_vendor/fastapi/param_functions.py +0 -340
  206. prefect/_vendor/fastapi/params.py +0 -453
  207. prefect/_vendor/fastapi/py.typed +0 -0
  208. prefect/_vendor/fastapi/requests.py +0 -4
  209. prefect/_vendor/fastapi/responses.py +0 -40
  210. prefect/_vendor/fastapi/routing.py +0 -1331
  211. prefect/_vendor/fastapi/security/__init__.py +0 -15
  212. prefect/_vendor/fastapi/security/api_key.py +0 -98
  213. prefect/_vendor/fastapi/security/base.py +0 -6
  214. prefect/_vendor/fastapi/security/http.py +0 -172
  215. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  216. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  217. prefect/_vendor/fastapi/security/utils.py +0 -10
  218. prefect/_vendor/fastapi/staticfiles.py +0 -1
  219. prefect/_vendor/fastapi/templating.py +0 -3
  220. prefect/_vendor/fastapi/testclient.py +0 -1
  221. prefect/_vendor/fastapi/types.py +0 -3
  222. prefect/_vendor/fastapi/utils.py +0 -235
  223. prefect/_vendor/fastapi/websockets.py +0 -7
  224. prefect/_vendor/starlette/__init__.py +0 -1
  225. prefect/_vendor/starlette/_compat.py +0 -28
  226. prefect/_vendor/starlette/_exception_handler.py +0 -80
  227. prefect/_vendor/starlette/_utils.py +0 -88
  228. prefect/_vendor/starlette/applications.py +0 -261
  229. prefect/_vendor/starlette/authentication.py +0 -159
  230. prefect/_vendor/starlette/background.py +0 -43
  231. prefect/_vendor/starlette/concurrency.py +0 -59
  232. prefect/_vendor/starlette/config.py +0 -151
  233. prefect/_vendor/starlette/convertors.py +0 -87
  234. prefect/_vendor/starlette/datastructures.py +0 -707
  235. prefect/_vendor/starlette/endpoints.py +0 -130
  236. prefect/_vendor/starlette/exceptions.py +0 -60
  237. prefect/_vendor/starlette/formparsers.py +0 -276
  238. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  239. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  240. prefect/_vendor/starlette/middleware/base.py +0 -220
  241. prefect/_vendor/starlette/middleware/cors.py +0 -176
  242. prefect/_vendor/starlette/middleware/errors.py +0 -265
  243. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  244. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  245. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  246. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  247. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  248. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  249. prefect/_vendor/starlette/py.typed +0 -0
  250. prefect/_vendor/starlette/requests.py +0 -328
  251. prefect/_vendor/starlette/responses.py +0 -347
  252. prefect/_vendor/starlette/routing.py +0 -933
  253. prefect/_vendor/starlette/schemas.py +0 -154
  254. prefect/_vendor/starlette/staticfiles.py +0 -248
  255. prefect/_vendor/starlette/status.py +0 -199
  256. prefect/_vendor/starlette/templating.py +0 -231
  257. prefect/_vendor/starlette/testclient.py +0 -804
  258. prefect/_vendor/starlette/types.py +0 -30
  259. prefect/_vendor/starlette/websockets.py +0 -193
  260. prefect/blocks/kubernetes.py +0 -119
  261. prefect/deprecated/__init__.py +0 -0
  262. prefect/deprecated/data_documents.py +0 -350
  263. prefect/deprecated/packaging/__init__.py +0 -12
  264. prefect/deprecated/packaging/base.py +0 -96
  265. prefect/deprecated/packaging/docker.py +0 -146
  266. prefect/deprecated/packaging/file.py +0 -92
  267. prefect/deprecated/packaging/orion.py +0 -80
  268. prefect/deprecated/packaging/serializers.py +0 -171
  269. prefect/events/instrument.py +0 -135
  270. prefect/infrastructure/container.py +0 -824
  271. prefect/infrastructure/kubernetes.py +0 -920
  272. prefect/infrastructure/process.py +0 -289
  273. prefect/manifests.py +0 -20
  274. prefect/new_flow_engine.py +0 -449
  275. prefect/new_task_engine.py +0 -423
  276. prefect/pydantic/__init__.py +0 -76
  277. prefect/pydantic/main.py +0 -39
  278. prefect/software/__init__.py +0 -2
  279. prefect/software/base.py +0 -50
  280. prefect/software/conda.py +0 -199
  281. prefect/software/pip.py +0 -122
  282. prefect/software/python.py +0 -52
  283. prefect/task_server.py +0 -322
  284. prefect_client-2.20.4.dist-info/RECORD +0 -294
  285. /prefect/{_internal/pydantic/utilities → client/types}/__init__.py +0 -0
  286. /prefect/{_vendor → concurrency/v1}/__init__.py +0 -0
  287. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/LICENSE +0 -0
  288. {prefect_client-2.20.4.dist-info → prefect_client-3.0.0.dist-info}/top_level.txt +0 -0
@@ -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, Callable, ClassVar, Generator
2
-
3
- from pydantic_core import core_schema, CoreSchema, SchemaValidator
4
- from typing_extensions import Self
5
- from datetime import timedelta
6
-
7
-
8
- class NonNegativeInteger(int):
9
- """An integer that must be greater than or equal to 0."""
10
-
11
- schema: ClassVar[CoreSchema] = core_schema.int_schema(ge=0)
12
-
13
- @classmethod
14
- def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
15
- yield cls.validate
16
-
17
- @classmethod
18
- def __get_pydantic_core_schema__(
19
- cls, source_type: Any, handler: Callable[..., Any]
20
- ) -> CoreSchema:
21
- return cls.schema
22
-
23
- @classmethod
24
- def validate(cls, v: Any) -> Self:
25
- return SchemaValidator(schema=cls.schema).validate_python(v)
26
-
27
-
28
- class PositiveInteger(int):
29
- """An integer that must be greater than 0."""
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
- @classmethod
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
- class NonNegativeFloat(float):
49
- schema: ClassVar[CoreSchema] = core_schema.float_schema(ge=0)
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
- @classmethod
56
- def __get_pydantic_core_schema__(
57
- cls, source_type: Any, handler: Callable[..., Any]
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
- @classmethod
62
- def validate(cls, v: Any) -> Self:
63
- return SchemaValidator(schema=cls.schema).validate_python(v)
46
+ return value
64
47
 
65
48
 
66
- class NonNegativeDuration(timedelta):
67
- """A timedelta that must be greater than or equal to 0."""
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
- @classmethod
72
- def __get_validators__(cls) -> Generator[Callable[..., Any], None, None]:
73
- yield cls.validate
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
- @classmethod
82
- def validate(cls, v: Any) -> Self:
83
- return SchemaValidator(schema=cls.schema).validate_python(v)
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
- schema: ClassVar = core_schema.timedelta_schema(gt=timedelta(seconds=0))
80
+ StrictVariableValue = Annotated[VariableValue, BeforeValidator(check_variable_value)]
90
81
 
91
- @classmethod
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
- @classmethod
102
- def validate(cls, v: Any) -> Self:
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
- "NonNegativeDuration",
111
- "PositiveDuration",
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"
@@ -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: object) -> bool:
25
- if not type(self) == type(other):
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: