prefect-client 2.19.3__py3-none-any.whl → 3.0.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. prefect/__init__.py +8 -56
  2. prefect/_internal/compatibility/deprecated.py +6 -115
  3. prefect/_internal/compatibility/experimental.py +4 -79
  4. prefect/_internal/concurrency/api.py +0 -34
  5. prefect/_internal/concurrency/calls.py +0 -6
  6. prefect/_internal/concurrency/cancellation.py +0 -3
  7. prefect/_internal/concurrency/event_loop.py +0 -20
  8. prefect/_internal/concurrency/inspection.py +3 -3
  9. prefect/_internal/concurrency/threads.py +35 -0
  10. prefect/_internal/concurrency/waiters.py +0 -28
  11. prefect/_internal/pydantic/__init__.py +0 -45
  12. prefect/_internal/pydantic/v1_schema.py +21 -22
  13. prefect/_internal/pydantic/v2_schema.py +0 -2
  14. prefect/_internal/pydantic/v2_validated_func.py +18 -23
  15. prefect/_internal/schemas/bases.py +44 -177
  16. prefect/_internal/schemas/fields.py +1 -43
  17. prefect/_internal/schemas/validators.py +60 -158
  18. prefect/artifacts.py +161 -14
  19. prefect/automations.py +39 -4
  20. prefect/blocks/abstract.py +1 -1
  21. prefect/blocks/core.py +268 -148
  22. prefect/blocks/fields.py +2 -57
  23. prefect/blocks/kubernetes.py +8 -12
  24. prefect/blocks/notifications.py +40 -20
  25. prefect/blocks/system.py +22 -11
  26. prefect/blocks/webhook.py +2 -9
  27. prefect/client/base.py +4 -4
  28. prefect/client/cloud.py +8 -13
  29. prefect/client/orchestration.py +347 -341
  30. prefect/client/schemas/actions.py +92 -86
  31. prefect/client/schemas/filters.py +20 -40
  32. prefect/client/schemas/objects.py +147 -145
  33. prefect/client/schemas/responses.py +16 -24
  34. prefect/client/schemas/schedules.py +47 -35
  35. prefect/client/subscriptions.py +2 -2
  36. prefect/client/utilities.py +5 -2
  37. prefect/concurrency/asyncio.py +3 -1
  38. prefect/concurrency/events.py +1 -1
  39. prefect/concurrency/services.py +6 -3
  40. prefect/context.py +195 -27
  41. prefect/deployments/__init__.py +5 -6
  42. prefect/deployments/base.py +7 -5
  43. prefect/deployments/flow_runs.py +185 -0
  44. prefect/deployments/runner.py +50 -45
  45. prefect/deployments/schedules.py +28 -23
  46. prefect/deployments/steps/__init__.py +0 -1
  47. prefect/deployments/steps/core.py +1 -0
  48. prefect/deployments/steps/pull.py +7 -21
  49. prefect/engine.py +12 -2422
  50. prefect/events/actions.py +17 -23
  51. prefect/events/cli/automations.py +19 -6
  52. prefect/events/clients.py +14 -37
  53. prefect/events/filters.py +14 -18
  54. prefect/events/related.py +2 -2
  55. prefect/events/schemas/__init__.py +0 -5
  56. prefect/events/schemas/automations.py +55 -46
  57. prefect/events/schemas/deployment_triggers.py +7 -197
  58. prefect/events/schemas/events.py +34 -65
  59. prefect/events/schemas/labelling.py +10 -14
  60. prefect/events/utilities.py +2 -3
  61. prefect/events/worker.py +2 -3
  62. prefect/filesystems.py +6 -517
  63. prefect/{new_flow_engine.py → flow_engine.py} +313 -72
  64. prefect/flow_runs.py +377 -5
  65. prefect/flows.py +248 -165
  66. prefect/futures.py +186 -345
  67. prefect/infrastructure/__init__.py +0 -27
  68. prefect/infrastructure/provisioners/__init__.py +5 -3
  69. prefect/infrastructure/provisioners/cloud_run.py +11 -6
  70. prefect/infrastructure/provisioners/container_instance.py +11 -7
  71. prefect/infrastructure/provisioners/ecs.py +6 -4
  72. prefect/infrastructure/provisioners/modal.py +8 -5
  73. prefect/input/actions.py +2 -4
  74. prefect/input/run_input.py +5 -7
  75. prefect/logging/formatters.py +0 -2
  76. prefect/logging/handlers.py +3 -11
  77. prefect/logging/loggers.py +2 -2
  78. prefect/manifests.py +2 -1
  79. prefect/records/__init__.py +1 -0
  80. prefect/records/result_store.py +42 -0
  81. prefect/records/store.py +9 -0
  82. prefect/results.py +43 -39
  83. prefect/runner/runner.py +9 -9
  84. prefect/runner/server.py +6 -10
  85. prefect/runner/storage.py +3 -8
  86. prefect/runner/submit.py +2 -2
  87. prefect/runner/utils.py +2 -2
  88. prefect/serializers.py +24 -35
  89. prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
  90. prefect/settings.py +70 -133
  91. prefect/states.py +17 -47
  92. prefect/task_engine.py +697 -58
  93. prefect/task_runners.py +269 -301
  94. prefect/task_server.py +53 -34
  95. prefect/tasks.py +327 -337
  96. prefect/transactions.py +220 -0
  97. prefect/types/__init__.py +61 -82
  98. prefect/utilities/asyncutils.py +195 -136
  99. prefect/utilities/callables.py +121 -41
  100. prefect/utilities/collections.py +23 -38
  101. prefect/utilities/dispatch.py +11 -3
  102. prefect/utilities/dockerutils.py +4 -0
  103. prefect/utilities/engine.py +140 -20
  104. prefect/utilities/importtools.py +26 -27
  105. prefect/utilities/pydantic.py +128 -38
  106. prefect/utilities/schema_tools/hydration.py +5 -1
  107. prefect/utilities/templating.py +12 -2
  108. prefect/variables.py +78 -61
  109. prefect/workers/__init__.py +0 -1
  110. prefect/workers/base.py +15 -17
  111. prefect/workers/process.py +3 -8
  112. prefect/workers/server.py +2 -2
  113. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
  114. prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
  115. prefect/_internal/pydantic/_base_model.py +0 -51
  116. prefect/_internal/pydantic/_compat.py +0 -82
  117. prefect/_internal/pydantic/_flags.py +0 -20
  118. prefect/_internal/pydantic/_types.py +0 -8
  119. prefect/_internal/pydantic/utilities/__init__.py +0 -0
  120. prefect/_internal/pydantic/utilities/config_dict.py +0 -72
  121. prefect/_internal/pydantic/utilities/field_validator.py +0 -150
  122. prefect/_internal/pydantic/utilities/model_construct.py +0 -56
  123. prefect/_internal/pydantic/utilities/model_copy.py +0 -55
  124. prefect/_internal/pydantic/utilities/model_dump.py +0 -136
  125. prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
  126. prefect/_internal/pydantic/utilities/model_fields.py +0 -50
  127. prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
  128. prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
  129. prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
  130. prefect/_internal/pydantic/utilities/model_validate.py +0 -75
  131. prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
  132. prefect/_internal/pydantic/utilities/model_validator.py +0 -87
  133. prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
  134. prefect/_vendor/__init__.py +0 -0
  135. prefect/_vendor/fastapi/__init__.py +0 -25
  136. prefect/_vendor/fastapi/applications.py +0 -946
  137. prefect/_vendor/fastapi/background.py +0 -3
  138. prefect/_vendor/fastapi/concurrency.py +0 -44
  139. prefect/_vendor/fastapi/datastructures.py +0 -58
  140. prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
  141. prefect/_vendor/fastapi/dependencies/models.py +0 -64
  142. prefect/_vendor/fastapi/dependencies/utils.py +0 -877
  143. prefect/_vendor/fastapi/encoders.py +0 -177
  144. prefect/_vendor/fastapi/exception_handlers.py +0 -40
  145. prefect/_vendor/fastapi/exceptions.py +0 -46
  146. prefect/_vendor/fastapi/logger.py +0 -3
  147. prefect/_vendor/fastapi/middleware/__init__.py +0 -1
  148. prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
  149. prefect/_vendor/fastapi/middleware/cors.py +0 -3
  150. prefect/_vendor/fastapi/middleware/gzip.py +0 -3
  151. prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
  152. prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
  153. prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
  154. prefect/_vendor/fastapi/openapi/__init__.py +0 -0
  155. prefect/_vendor/fastapi/openapi/constants.py +0 -2
  156. prefect/_vendor/fastapi/openapi/docs.py +0 -203
  157. prefect/_vendor/fastapi/openapi/models.py +0 -480
  158. prefect/_vendor/fastapi/openapi/utils.py +0 -485
  159. prefect/_vendor/fastapi/param_functions.py +0 -340
  160. prefect/_vendor/fastapi/params.py +0 -453
  161. prefect/_vendor/fastapi/requests.py +0 -4
  162. prefect/_vendor/fastapi/responses.py +0 -40
  163. prefect/_vendor/fastapi/routing.py +0 -1331
  164. prefect/_vendor/fastapi/security/__init__.py +0 -15
  165. prefect/_vendor/fastapi/security/api_key.py +0 -98
  166. prefect/_vendor/fastapi/security/base.py +0 -6
  167. prefect/_vendor/fastapi/security/http.py +0 -172
  168. prefect/_vendor/fastapi/security/oauth2.py +0 -227
  169. prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
  170. prefect/_vendor/fastapi/security/utils.py +0 -10
  171. prefect/_vendor/fastapi/staticfiles.py +0 -1
  172. prefect/_vendor/fastapi/templating.py +0 -3
  173. prefect/_vendor/fastapi/testclient.py +0 -1
  174. prefect/_vendor/fastapi/types.py +0 -3
  175. prefect/_vendor/fastapi/utils.py +0 -235
  176. prefect/_vendor/fastapi/websockets.py +0 -7
  177. prefect/_vendor/starlette/__init__.py +0 -1
  178. prefect/_vendor/starlette/_compat.py +0 -28
  179. prefect/_vendor/starlette/_exception_handler.py +0 -80
  180. prefect/_vendor/starlette/_utils.py +0 -88
  181. prefect/_vendor/starlette/applications.py +0 -261
  182. prefect/_vendor/starlette/authentication.py +0 -159
  183. prefect/_vendor/starlette/background.py +0 -43
  184. prefect/_vendor/starlette/concurrency.py +0 -59
  185. prefect/_vendor/starlette/config.py +0 -151
  186. prefect/_vendor/starlette/convertors.py +0 -87
  187. prefect/_vendor/starlette/datastructures.py +0 -707
  188. prefect/_vendor/starlette/endpoints.py +0 -130
  189. prefect/_vendor/starlette/exceptions.py +0 -60
  190. prefect/_vendor/starlette/formparsers.py +0 -276
  191. prefect/_vendor/starlette/middleware/__init__.py +0 -17
  192. prefect/_vendor/starlette/middleware/authentication.py +0 -52
  193. prefect/_vendor/starlette/middleware/base.py +0 -220
  194. prefect/_vendor/starlette/middleware/cors.py +0 -176
  195. prefect/_vendor/starlette/middleware/errors.py +0 -265
  196. prefect/_vendor/starlette/middleware/exceptions.py +0 -74
  197. prefect/_vendor/starlette/middleware/gzip.py +0 -113
  198. prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
  199. prefect/_vendor/starlette/middleware/sessions.py +0 -82
  200. prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
  201. prefect/_vendor/starlette/middleware/wsgi.py +0 -147
  202. prefect/_vendor/starlette/requests.py +0 -328
  203. prefect/_vendor/starlette/responses.py +0 -347
  204. prefect/_vendor/starlette/routing.py +0 -933
  205. prefect/_vendor/starlette/schemas.py +0 -154
  206. prefect/_vendor/starlette/staticfiles.py +0 -248
  207. prefect/_vendor/starlette/status.py +0 -199
  208. prefect/_vendor/starlette/templating.py +0 -231
  209. prefect/_vendor/starlette/testclient.py +0 -804
  210. prefect/_vendor/starlette/types.py +0 -30
  211. prefect/_vendor/starlette/websockets.py +0 -193
  212. prefect/agent.py +0 -698
  213. prefect/deployments/deployments.py +0 -1042
  214. prefect/deprecated/__init__.py +0 -0
  215. prefect/deprecated/data_documents.py +0 -350
  216. prefect/deprecated/packaging/__init__.py +0 -12
  217. prefect/deprecated/packaging/base.py +0 -96
  218. prefect/deprecated/packaging/docker.py +0 -146
  219. prefect/deprecated/packaging/file.py +0 -92
  220. prefect/deprecated/packaging/orion.py +0 -80
  221. prefect/deprecated/packaging/serializers.py +0 -171
  222. prefect/events/instrument.py +0 -135
  223. prefect/infrastructure/base.py +0 -323
  224. prefect/infrastructure/container.py +0 -818
  225. prefect/infrastructure/kubernetes.py +0 -920
  226. prefect/infrastructure/process.py +0 -289
  227. prefect/new_task_engine.py +0 -423
  228. prefect/pydantic/__init__.py +0 -76
  229. prefect/pydantic/main.py +0 -39
  230. prefect/software/__init__.py +0 -2
  231. prefect/software/base.py +0 -50
  232. prefect/software/conda.py +0 -199
  233. prefect/software/pip.py +0 -122
  234. prefect/software/python.py +0 -52
  235. prefect/workers/block.py +0 -218
  236. prefect_client-2.19.3.dist-info/RECORD +0 -292
  237. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
  238. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
  239. {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/top_level.txt +0 -0
prefect/events/actions.py CHANGED
@@ -2,14 +2,8 @@ import abc
2
2
  from typing import Any, Dict, Optional, Union
3
3
  from uuid import UUID
4
4
 
5
- from typing_extensions import Literal, TypeAlias
6
-
7
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
8
-
9
- if HAS_PYDANTIC_V2:
10
- from pydantic.v1 import Field, root_validator
11
- else:
12
- from pydantic import Field, root_validator # type: ignore
5
+ from pydantic import Field, model_validator
6
+ from typing_extensions import Literal, Self, TypeAlias
13
7
 
14
8
  from prefect._internal.schemas.bases import PrefectBaseModel
15
9
  from prefect.client.schemas.objects import StateType
@@ -49,16 +43,16 @@ class DeploymentAction(Action):
49
43
  None, description="The identifier of the deployment"
50
44
  )
51
45
 
52
- @root_validator
53
- def selected_deployment_requires_id(cls, values):
54
- wants_selected_deployment = values.get("source") == "selected"
55
- has_deployment_id = bool(values.get("deployment_id"))
46
+ @model_validator(mode="after")
47
+ def selected_deployment_requires_id(self):
48
+ wants_selected_deployment = self.source == "selected"
49
+ has_deployment_id = bool(self.deployment_id)
56
50
  if wants_selected_deployment != has_deployment_id:
57
51
  raise ValueError(
58
52
  "deployment_id is "
59
53
  + ("not allowed" if has_deployment_id else "required")
60
54
  )
61
- return values
55
+ return self
62
56
 
63
57
 
64
58
  class RunDeployment(DeploymentAction):
@@ -199,16 +193,16 @@ class WorkQueueAction(Action):
199
193
  None, description="The identifier of the work queue to pause"
200
194
  )
201
195
 
202
- @root_validator
203
- def selected_work_queue_requires_id(cls, values):
204
- wants_selected_work_queue = values.get("source") == "selected"
205
- has_work_queue_id = bool(values.get("work_queue_id"))
196
+ @model_validator(mode="after")
197
+ def selected_work_queue_requires_id(self) -> Self:
198
+ wants_selected_work_queue = self.source == "selected"
199
+ has_work_queue_id = bool(self.work_queue_id)
206
200
  if wants_selected_work_queue != has_work_queue_id:
207
201
  raise ValueError(
208
202
  "work_queue_id is "
209
203
  + ("not allowed" if has_work_queue_id else "required")
210
204
  )
211
- return values
205
+ return self
212
206
 
213
207
 
214
208
  class PauseWorkQueue(WorkQueueAction):
@@ -241,16 +235,16 @@ class AutomationAction(Action):
241
235
  None, description="The identifier of the automation to act on"
242
236
  )
243
237
 
244
- @root_validator
245
- def selected_automation_requires_id(cls, values):
246
- wants_selected_automation = values.get("source") == "selected"
247
- has_automation_id = bool(values.get("automation_id"))
238
+ @model_validator(mode="after")
239
+ def selected_automation_requires_id(self) -> Self:
240
+ wants_selected_automation = self.source == "selected"
241
+ has_automation_id = bool(self.automation_id)
248
242
  if wants_selected_automation != has_automation_id:
249
243
  raise ValueError(
250
244
  "automation_id is "
251
245
  + ("not allowed" if has_automation_id else "required")
252
246
  )
253
- return values
247
+ return self
254
248
 
255
249
 
256
250
  class PauseAutomation(AutomationAction):
@@ -3,19 +3,20 @@ Command line interface for working with automations.
3
3
  """
4
4
 
5
5
  import functools
6
- from typing import Optional
6
+ from typing import Optional, Type
7
7
  from uuid import UUID
8
8
 
9
9
  import orjson
10
10
  import typer
11
11
  import yaml as pyyaml
12
+ from pydantic import BaseModel
12
13
  from rich.pretty import Pretty
13
14
  from rich.table import Table
14
15
  from rich.text import Text
15
16
 
16
17
  from prefect.cli._types import PrefectTyper
17
18
  from prefect.cli._utilities import exit_with_error, exit_with_success
18
- from prefect.cli.root import app
19
+ from prefect.cli.root import app, is_interactive
19
20
  from prefect.client.orchestration import get_client
20
21
  from prefect.events.schemas.automations import Automation
21
22
  from prefect.exceptions import PrefectHTTPStatusError
@@ -148,10 +149,22 @@ async def inspect(
148
149
  exit_with_error(f"Automation with id {id!r} not found.")
149
150
 
150
151
  if yaml or json:
152
+
153
+ def no_really_json(obj: Type[BaseModel]):
154
+ # Working around a weird bug where pydantic isn't rendering enums as strings
155
+ #
156
+ # automation.trigger.model_dump(mode="json")
157
+ # {..., 'posture': 'Reactive', ...}
158
+ #
159
+ # automation.model_dump(mode="json")
160
+ # {..., 'posture': Posture.Reactive, ...}
161
+ return orjson.loads(obj.model_dump_json())
162
+
151
163
  if isinstance(automation, list):
152
- automation = [a.dict(json_compatible=True) for a in automation]
164
+ automation = [no_really_json(a) for a in automation]
153
165
  elif isinstance(automation, Automation):
154
- automation = automation.dict(json_compatible=True)
166
+ automation = no_really_json(automation)
167
+
155
168
  if yaml:
156
169
  app.console.print(pyyaml.dump(automation, sort_keys=False))
157
170
  elif json:
@@ -297,7 +310,7 @@ async def delete(
297
310
  automation = await client.read_automation(id)
298
311
  if not automation:
299
312
  exit_with_error(f"Automation with id {id!r} not found.")
300
- if not typer.confirm(
313
+ if is_interactive() and not typer.confirm(
301
314
  (f"Are you sure you want to delete automation with id {id!r}?"),
302
315
  default=False,
303
316
  ):
@@ -315,7 +328,7 @@ async def delete(
315
328
  exit_with_error(
316
329
  f"Multiple automations found with name {name!r}. Please specify an id with the `--id` flag instead."
317
330
  )
318
- if not typer.confirm(
331
+ if is_interactive() and not typer.confirm(
319
332
  (f"Are you sure you want to delete automation with name {name!r}?"),
320
333
  default=False,
321
334
  ):
prefect/events/clients.py CHANGED
@@ -31,12 +31,7 @@ from websockets.exceptions import (
31
31
  from prefect.client.base import PrefectHttpxAsyncClient
32
32
  from prefect.events import Event
33
33
  from prefect.logging import get_logger
34
- from prefect.settings import (
35
- PREFECT_API_KEY,
36
- PREFECT_API_URL,
37
- PREFECT_CLOUD_API_URL,
38
- PREFECT_EXPERIMENTAL_EVENTS,
39
- )
34
+ from prefect.settings import PREFECT_API_KEY, PREFECT_API_URL, PREFECT_CLOUD_API_URL
40
35
 
41
36
  if TYPE_CHECKING:
42
37
  from prefect.events.filters import EventFilter
@@ -54,20 +49,13 @@ def get_events_client(
54
49
  reconnection_attempts=reconnection_attempts,
55
50
  checkpoint_every=checkpoint_every,
56
51
  )
57
- elif PREFECT_EXPERIMENTAL_EVENTS:
58
- if PREFECT_API_URL:
59
- return PrefectEventsClient(
60
- reconnection_attempts=reconnection_attempts,
61
- checkpoint_every=checkpoint_every,
62
- )
63
- else:
64
- return PrefectEphemeralEventsClient()
65
-
66
- raise RuntimeError(
67
- "The current server and client configuration does not support "
68
- "events. Enable experimental events support with the "
69
- "PREFECT_EXPERIMENTAL_EVENTS setting."
70
- )
52
+ elif PREFECT_API_URL:
53
+ return PrefectEventsClient(
54
+ reconnection_attempts=reconnection_attempts,
55
+ checkpoint_every=checkpoint_every,
56
+ )
57
+ else:
58
+ return PrefectEphemeralEventsClient()
71
59
 
72
60
 
73
61
  def get_events_subscriber(
@@ -79,17 +67,11 @@ def get_events_subscriber(
79
67
  return PrefectCloudEventSubscriber(
80
68
  filter=filter, reconnection_attempts=reconnection_attempts
81
69
  )
82
- elif PREFECT_EXPERIMENTAL_EVENTS:
70
+ else:
83
71
  return PrefectEventSubscriber(
84
72
  filter=filter, reconnection_attempts=reconnection_attempts
85
73
  )
86
74
 
87
- raise RuntimeError(
88
- "The current server and client configuration does not support "
89
- "events. Enable experimental events support with the "
90
- "PREFECT_EXPERIMENTAL_EVENTS setting."
91
- )
92
-
93
75
 
94
76
  class EventsClient(abc.ABC):
95
77
  """The abstract interface for all Prefect Events clients"""
@@ -132,7 +114,7 @@ class AssertingEventsClient(EventsClient):
132
114
  """A Prefect Events client that records all events sent to it for inspection during
133
115
  tests."""
134
116
 
135
- last: ClassVar["AssertingEventsClient | None"] = None
117
+ last: ClassVar["Optional[AssertingEventsClient]"] = None
136
118
  all: ClassVar[List["AssertingEventsClient"]] = []
137
119
 
138
120
  args: Tuple
@@ -179,11 +161,6 @@ class PrefectEphemeralEventsClient(EventsClient):
179
161
  """A Prefect Events client that sends events to an ephemeral Prefect server"""
180
162
 
181
163
  def __init__(self):
182
- if not PREFECT_EXPERIMENTAL_EVENTS:
183
- raise ValueError(
184
- "PrefectEphemeralEventsClient can only be used when "
185
- "PREFECT_EXPERIMENTAL_EVENTS is set to True"
186
- )
187
164
  if PREFECT_API_KEY.value():
188
165
  raise ValueError(
189
166
  "PrefectEphemeralEventsClient cannot be used when PREFECT_API_KEY is set."
@@ -217,7 +194,7 @@ class PrefectEphemeralEventsClient(EventsClient):
217
194
  async def _emit(self, event: Event) -> None:
218
195
  await self._http_client.post(
219
196
  "/events",
220
- json=[event.dict(json_compatible=True)],
197
+ json=[event.model_dump(mode="json")],
221
198
  )
222
199
 
223
200
 
@@ -324,7 +301,7 @@ class PrefectEventsClient(EventsClient):
324
301
  await self._reconnect()
325
302
  assert self._websocket
326
303
 
327
- await self._websocket.send(event.json())
304
+ await self._websocket.send(event.model_dump_json())
328
305
  await self._checkpoint(event)
329
306
 
330
307
  return
@@ -489,7 +466,7 @@ class PrefectEventSubscriber:
489
466
  logger.debug(" filtering events since %s...", self._filter.occurred.since)
490
467
  filter_message = {
491
468
  "type": "filter",
492
- "filter": self._filter.dict(json_compatible=True),
469
+ "filter": self._filter.model_dump(mode="json"),
493
470
  }
494
471
  await self._websocket.send(orjson.dumps(filter_message).decode())
495
472
 
@@ -520,7 +497,7 @@ class PrefectEventSubscriber:
520
497
 
521
498
  while True:
522
499
  message = orjson.loads(await self._websocket.recv())
523
- event: Event = Event.parse_obj(message["event"])
500
+ event: Event = Event.model_validate(message["event"])
524
501
 
525
502
  if event.id in self._seen_events:
526
503
  continue
prefect/events/filters.py CHANGED
@@ -2,24 +2,19 @@ from typing import List, Optional, Tuple, cast
2
2
  from uuid import UUID
3
3
 
4
4
  import pendulum
5
+ from pydantic import Field, PrivateAttr
6
+ from pydantic_extra_types.pendulum_dt import DateTime
5
7
 
6
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
7
8
  from prefect._internal.schemas.bases import PrefectBaseModel
8
- from prefect._internal.schemas.fields import DateTimeTZ
9
9
  from prefect.utilities.collections import AutoEnum
10
10
 
11
11
  from .schemas.events import Event, Resource, ResourceSpecification
12
12
 
13
- if HAS_PYDANTIC_V2:
14
- from pydantic.v1 import Field, PrivateAttr
15
- else:
16
- from pydantic import Field, PrivateAttr # type: ignore
17
-
18
13
 
19
14
  class AutomationFilterCreated(PrefectBaseModel):
20
15
  """Filter by `Automation.created`."""
21
16
 
22
- before_: Optional[DateTimeTZ] = Field(
17
+ before_: Optional[DateTime] = Field(
23
18
  default=None,
24
19
  description="Only include automations created before this datetime",
25
20
  )
@@ -46,18 +41,19 @@ class AutomationFilter(PrefectBaseModel):
46
41
  class EventDataFilter(PrefectBaseModel, extra="forbid"): # type: ignore[call-arg]
47
42
  """A base class for filtering event data."""
48
43
 
49
- _top_level_filter: "EventFilter | None" = PrivateAttr(None)
44
+ _top_level_filter: Optional["EventFilter"] = PrivateAttr(None)
50
45
 
51
46
  def get_filters(self) -> List["EventDataFilter"]:
52
- return [
47
+ filters: List["EventDataFilter"] = [
53
48
  filter
54
49
  for filter in [
55
- getattr(self, name)
56
- for name, field in self.__fields__.items()
57
- if issubclass(field.type_, EventDataFilter)
50
+ getattr(self, name) for name, field in self.model_fields.items()
58
51
  ]
59
- if filter
52
+ if isinstance(filter, EventDataFilter)
60
53
  ]
54
+ for filter in filters:
55
+ filter._top_level_filter = self._top_level_filter
56
+ return filters
61
57
 
62
58
  def includes(self, event: Event) -> bool:
63
59
  """Does the given event match the criteria of this filter?"""
@@ -69,15 +65,15 @@ class EventDataFilter(PrefectBaseModel, extra="forbid"): # type: ignore[call-ar
69
65
 
70
66
 
71
67
  class EventOccurredFilter(EventDataFilter):
72
- since: DateTimeTZ = Field(
68
+ since: DateTime = Field(
73
69
  default_factory=lambda: cast(
74
- DateTimeTZ,
70
+ DateTime,
75
71
  pendulum.now("UTC").start_of("day").subtract(days=180),
76
72
  ),
77
73
  description="Only include events after this time (inclusive)",
78
74
  )
79
- until: DateTimeTZ = Field(
80
- default_factory=lambda: cast(DateTimeTZ, pendulum.now("UTC")),
75
+ until: DateTime = Field(
76
+ default_factory=lambda: cast(DateTime, pendulum.now("UTC")),
81
77
  description="Only include events prior to this time (inclusive)",
82
78
  )
83
79
 
prefect/events/related.py CHANGED
@@ -31,7 +31,7 @@ RESOURCE_CACHE: RelatedResourceCache = {}
31
31
 
32
32
  def tags_as_related_resources(tags: Iterable[str]) -> List[RelatedResource]:
33
33
  return [
34
- RelatedResource.parse_obj(
34
+ RelatedResource.model_validate(
35
35
  {
36
36
  "prefect.resource.id": f"prefect.tag.{tag}",
37
37
  "prefect.resource.role": "tag",
@@ -44,7 +44,7 @@ def tags_as_related_resources(tags: Iterable[str]) -> List[RelatedResource]:
44
44
  def object_as_related_resource(kind: str, role: str, object: Any) -> RelatedResource:
45
45
  resource_id = f"prefect.{kind}.{object.id}"
46
46
 
47
- return RelatedResource.parse_obj(
47
+ return RelatedResource.model_validate(
48
48
  {
49
49
  "prefect.resource.id": resource_id,
50
50
  "prefect.resource.role": role,
@@ -1,5 +0,0 @@
1
- # TODO: these are just for backward compatibility, can can be removed in the future
2
-
3
- from .deployment_triggers import DeploymentTrigger
4
-
5
- __all__ = ["DeploymentTrigger"]
@@ -14,19 +14,15 @@ from typing import (
14
14
  )
15
15
  from uuid import UUID
16
16
 
17
- from typing_extensions import TypeAlias
18
-
19
- from prefect._internal.pydantic import HAS_PYDANTIC_V2
20
-
21
- if HAS_PYDANTIC_V2:
22
- from pydantic.v1 import Field, PrivateAttr, root_validator, validator
23
- from pydantic.v1.fields import ModelField
24
- else:
25
- from pydantic import Field, PrivateAttr, root_validator, validator # type: ignore
26
- from pydantic.fields import ModelField # type: ignore
17
+ from pydantic import (
18
+ Field,
19
+ PrivateAttr,
20
+ field_validator,
21
+ model_validator,
22
+ )
23
+ from typing_extensions import Self, TypeAlias
27
24
 
28
25
  from prefect._internal.schemas.bases import PrefectBaseModel
29
- from prefect._internal.schemas.validators import validate_trigger_within
30
26
  from prefect.events.actions import ActionTypes, RunDeployment
31
27
  from prefect.utilities.collections import AutoEnum
32
28
 
@@ -81,7 +77,7 @@ class Trigger(PrefectBaseModel, abc.ABC, extra="ignore"): # type: ignore[call-a
81
77
  # This is one of the Deployment*Trigger classes, so translate it over to a
82
78
  # plain Trigger
83
79
  if hasattr(self, "trigger_type"):
84
- trigger = self.trigger_type(**self.dict())
80
+ trigger = self.trigger_type(**self.model_dump())
85
81
 
86
82
  return AutomationCore(
87
83
  name=(
@@ -104,11 +100,11 @@ class ResourceTrigger(Trigger, abc.ABC):
104
100
  type: str
105
101
 
106
102
  match: ResourceSpecification = Field(
107
- default_factory=lambda: ResourceSpecification.parse_obj({}),
103
+ default_factory=lambda: ResourceSpecification.model_validate({}),
108
104
  description="Labels for resources which this trigger will match.",
109
105
  )
110
106
  match_related: ResourceSpecification = Field(
111
- default_factory=lambda: ResourceSpecification.parse_obj({}),
107
+ default_factory=lambda: ResourceSpecification.model_validate({}),
112
108
  description="Labels for related resources which this trigger will match.",
113
109
  )
114
110
 
@@ -167,9 +163,8 @@ class EventTrigger(ResourceTrigger):
167
163
  ),
168
164
  )
169
165
  within: timedelta = Field(
170
- timedelta(0),
171
- minimum=0.0,
172
- exclusiveMinimum=False,
166
+ timedelta(seconds=0),
167
+ ge=timedelta(seconds=0),
173
168
  description=(
174
169
  "The time period over which the events must occur. For Reactive triggers, "
175
170
  "this may be as low as 0 seconds, but must be at least 10 seconds for "
@@ -177,26 +172,33 @@ class EventTrigger(ResourceTrigger):
177
172
  ),
178
173
  )
179
174
 
180
- @validator("within")
181
- def enforce_minimum_within(
182
- cls, value: timedelta, values, config, field: ModelField
183
- ):
184
- return validate_trigger_within(value, field)
175
+ @model_validator(mode="before")
176
+ @classmethod
177
+ def enforce_minimum_within_for_proactive_triggers(
178
+ cls, data: Dict[str, Any]
179
+ ) -> Dict[str, Any]:
180
+ if not isinstance(data, dict):
181
+ return data
182
+
183
+ if "within" in data and data["within"] is None:
184
+ raise ValueError("`within` should be a valid timedelta")
185
185
 
186
- @root_validator(skip_on_failure=True)
187
- def enforce_minimum_within_for_proactive_triggers(cls, values: Dict[str, Any]):
188
- posture: Optional[Posture] = values.get("posture")
189
- within: Optional[timedelta] = values.get("within")
186
+ posture: Optional[Posture] = data.get("posture")
187
+ within: Optional[timedelta] = data.get("within")
188
+
189
+ if isinstance(within, (int, float)):
190
+ data["within"] = within = timedelta(seconds=within)
190
191
 
191
192
  if posture == Posture.Proactive:
192
193
  if not within or within == timedelta(0):
193
- values["within"] = timedelta(seconds=10.0)
194
+ data["within"] = timedelta(seconds=10.0)
194
195
  elif within < timedelta(seconds=10.0):
195
196
  raise ValueError(
196
- "The minimum within for Proactive triggers is 10 seconds"
197
+ "`within` for Proactive triggers must be greater than or equal to "
198
+ "10 seconds"
197
199
  )
198
200
 
199
- return values
201
+ return data
200
202
 
201
203
  def describe_for_cli(self, indent: int = 0) -> str:
202
204
  """Return a human-readable description of this trigger for the CLI"""
@@ -258,8 +260,6 @@ class MetricTriggerQuery(PrefectBaseModel):
258
260
  )
259
261
  range: timedelta = Field(
260
262
  timedelta(seconds=300), # defaults to 5 minutes
261
- minimum=300.0,
262
- exclusiveMinimum=False,
263
263
  description=(
264
264
  "The lookback duration (seconds) for a metric query. This duration is "
265
265
  "used to determine the time range over which the query will be executed. "
@@ -268,8 +268,6 @@ class MetricTriggerQuery(PrefectBaseModel):
268
268
  )
269
269
  firing_for: timedelta = Field(
270
270
  timedelta(seconds=300), # defaults to 5 minutes
271
- minimum=300.0,
272
- exclusiveMinimum=False,
273
271
  description=(
274
272
  "The duration (seconds) for which the metric query must breach "
275
273
  "or resolve continuously before the state is updated and the "
@@ -278,6 +276,12 @@ class MetricTriggerQuery(PrefectBaseModel):
278
276
  ),
279
277
  )
280
278
 
279
+ @field_validator("range", "firing_for")
280
+ def enforce_minimum_range(cls, value: timedelta):
281
+ if value < timedelta(seconds=300):
282
+ raise ValueError("The minimum range is 300 seconds (5 minutes)")
283
+ return value
284
+
281
285
 
282
286
  class MetricTrigger(ResourceTrigger):
283
287
  """
@@ -316,7 +320,14 @@ class CompositeTrigger(Trigger, abc.ABC):
316
320
 
317
321
  type: Literal["compound", "sequence"]
318
322
  triggers: List["TriggerTypes"]
319
- within: Optional[timedelta]
323
+ within: Optional[timedelta] = Field(
324
+ None,
325
+ description=(
326
+ "The time period over which the events must occur. For Reactive triggers, "
327
+ "this may be as low as 0 seconds, but must be at least 10 seconds for "
328
+ "Proactive triggers"
329
+ ),
330
+ )
320
331
 
321
332
 
322
333
  class CompoundTrigger(CompositeTrigger):
@@ -326,19 +337,17 @@ class CompoundTrigger(CompositeTrigger):
326
337
  type: Literal["compound"] = "compound"
327
338
  require: Union[int, Literal["any", "all"]]
328
339
 
329
- @root_validator
330
- def validate_require(cls, values: Dict[str, Any]) -> Dict[str, Any]:
331
- require = values.get("require")
332
-
333
- if isinstance(require, int):
334
- if require < 1:
335
- raise ValueError("required must be at least 1")
336
- if require > len(values["triggers"]):
340
+ @model_validator(mode="after")
341
+ def validate_require(self) -> Self:
342
+ if isinstance(self.require, int):
343
+ if self.require < 1:
344
+ raise ValueError("require must be at least 1")
345
+ if self.require > len(self.triggers):
337
346
  raise ValueError(
338
- "required must be less than or equal to the number of triggers"
347
+ "require must be less than or equal to the number of triggers"
339
348
  )
340
349
 
341
- return values
350
+ return self
342
351
 
343
352
  def describe_for_cli(self, indent: int = 0) -> str:
344
353
  """Return a human-readable description of this trigger for the CLI"""
@@ -387,8 +396,8 @@ TriggerTypes: TypeAlias = Union[
387
396
  ]
388
397
  """The union of all concrete trigger types that a user may actually create"""
389
398
 
390
- CompoundTrigger.update_forward_refs()
391
- SequenceTrigger.update_forward_refs()
399
+ CompoundTrigger.model_rebuild()
400
+ SequenceTrigger.model_rebuild()
392
401
 
393
402
 
394
403
  class AutomationCore(PrefectBaseModel, extra="ignore"): # type: ignore[call-arg]