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.
- prefect/__init__.py +8 -56
- prefect/_internal/compatibility/deprecated.py +6 -115
- prefect/_internal/compatibility/experimental.py +4 -79
- prefect/_internal/concurrency/api.py +0 -34
- prefect/_internal/concurrency/calls.py +0 -6
- prefect/_internal/concurrency/cancellation.py +0 -3
- prefect/_internal/concurrency/event_loop.py +0 -20
- prefect/_internal/concurrency/inspection.py +3 -3
- prefect/_internal/concurrency/threads.py +35 -0
- prefect/_internal/concurrency/waiters.py +0 -28
- prefect/_internal/pydantic/__init__.py +0 -45
- prefect/_internal/pydantic/v1_schema.py +21 -22
- prefect/_internal/pydantic/v2_schema.py +0 -2
- prefect/_internal/pydantic/v2_validated_func.py +18 -23
- prefect/_internal/schemas/bases.py +44 -177
- prefect/_internal/schemas/fields.py +1 -43
- prefect/_internal/schemas/validators.py +60 -158
- prefect/artifacts.py +161 -14
- prefect/automations.py +39 -4
- prefect/blocks/abstract.py +1 -1
- prefect/blocks/core.py +268 -148
- prefect/blocks/fields.py +2 -57
- prefect/blocks/kubernetes.py +8 -12
- prefect/blocks/notifications.py +40 -20
- prefect/blocks/system.py +22 -11
- prefect/blocks/webhook.py +2 -9
- prefect/client/base.py +4 -4
- prefect/client/cloud.py +8 -13
- prefect/client/orchestration.py +347 -341
- prefect/client/schemas/actions.py +92 -86
- prefect/client/schemas/filters.py +20 -40
- prefect/client/schemas/objects.py +147 -145
- prefect/client/schemas/responses.py +16 -24
- prefect/client/schemas/schedules.py +47 -35
- prefect/client/subscriptions.py +2 -2
- prefect/client/utilities.py +5 -2
- prefect/concurrency/asyncio.py +3 -1
- prefect/concurrency/events.py +1 -1
- prefect/concurrency/services.py +6 -3
- prefect/context.py +195 -27
- prefect/deployments/__init__.py +5 -6
- prefect/deployments/base.py +7 -5
- prefect/deployments/flow_runs.py +185 -0
- prefect/deployments/runner.py +50 -45
- prefect/deployments/schedules.py +28 -23
- prefect/deployments/steps/__init__.py +0 -1
- prefect/deployments/steps/core.py +1 -0
- prefect/deployments/steps/pull.py +7 -21
- prefect/engine.py +12 -2422
- prefect/events/actions.py +17 -23
- prefect/events/cli/automations.py +19 -6
- prefect/events/clients.py +14 -37
- prefect/events/filters.py +14 -18
- prefect/events/related.py +2 -2
- prefect/events/schemas/__init__.py +0 -5
- prefect/events/schemas/automations.py +55 -46
- prefect/events/schemas/deployment_triggers.py +7 -197
- prefect/events/schemas/events.py +34 -65
- prefect/events/schemas/labelling.py +10 -14
- prefect/events/utilities.py +2 -3
- prefect/events/worker.py +2 -3
- prefect/filesystems.py +6 -517
- prefect/{new_flow_engine.py → flow_engine.py} +313 -72
- prefect/flow_runs.py +377 -5
- prefect/flows.py +248 -165
- prefect/futures.py +186 -345
- prefect/infrastructure/__init__.py +0 -27
- prefect/infrastructure/provisioners/__init__.py +5 -3
- prefect/infrastructure/provisioners/cloud_run.py +11 -6
- prefect/infrastructure/provisioners/container_instance.py +11 -7
- prefect/infrastructure/provisioners/ecs.py +6 -4
- prefect/infrastructure/provisioners/modal.py +8 -5
- prefect/input/actions.py +2 -4
- prefect/input/run_input.py +5 -7
- prefect/logging/formatters.py +0 -2
- prefect/logging/handlers.py +3 -11
- prefect/logging/loggers.py +2 -2
- prefect/manifests.py +2 -1
- prefect/records/__init__.py +1 -0
- prefect/records/result_store.py +42 -0
- prefect/records/store.py +9 -0
- prefect/results.py +43 -39
- prefect/runner/runner.py +9 -9
- prefect/runner/server.py +6 -10
- prefect/runner/storage.py +3 -8
- prefect/runner/submit.py +2 -2
- prefect/runner/utils.py +2 -2
- prefect/serializers.py +24 -35
- prefect/server/api/collections_data/views/aggregate-worker-metadata.json +5 -14
- prefect/settings.py +70 -133
- prefect/states.py +17 -47
- prefect/task_engine.py +697 -58
- prefect/task_runners.py +269 -301
- prefect/task_server.py +53 -34
- prefect/tasks.py +327 -337
- prefect/transactions.py +220 -0
- prefect/types/__init__.py +61 -82
- prefect/utilities/asyncutils.py +195 -136
- prefect/utilities/callables.py +121 -41
- prefect/utilities/collections.py +23 -38
- prefect/utilities/dispatch.py +11 -3
- prefect/utilities/dockerutils.py +4 -0
- prefect/utilities/engine.py +140 -20
- prefect/utilities/importtools.py +26 -27
- prefect/utilities/pydantic.py +128 -38
- prefect/utilities/schema_tools/hydration.py +5 -1
- prefect/utilities/templating.py +12 -2
- prefect/variables.py +78 -61
- prefect/workers/__init__.py +0 -1
- prefect/workers/base.py +15 -17
- prefect/workers/process.py +3 -8
- prefect/workers/server.py +2 -2
- {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/METADATA +22 -21
- prefect_client-3.0.0rc1.dist-info/RECORD +176 -0
- prefect/_internal/pydantic/_base_model.py +0 -51
- prefect/_internal/pydantic/_compat.py +0 -82
- prefect/_internal/pydantic/_flags.py +0 -20
- prefect/_internal/pydantic/_types.py +0 -8
- prefect/_internal/pydantic/utilities/__init__.py +0 -0
- prefect/_internal/pydantic/utilities/config_dict.py +0 -72
- prefect/_internal/pydantic/utilities/field_validator.py +0 -150
- prefect/_internal/pydantic/utilities/model_construct.py +0 -56
- prefect/_internal/pydantic/utilities/model_copy.py +0 -55
- prefect/_internal/pydantic/utilities/model_dump.py +0 -136
- prefect/_internal/pydantic/utilities/model_dump_json.py +0 -112
- prefect/_internal/pydantic/utilities/model_fields.py +0 -50
- prefect/_internal/pydantic/utilities/model_fields_set.py +0 -29
- prefect/_internal/pydantic/utilities/model_json_schema.py +0 -82
- prefect/_internal/pydantic/utilities/model_rebuild.py +0 -80
- prefect/_internal/pydantic/utilities/model_validate.py +0 -75
- prefect/_internal/pydantic/utilities/model_validate_json.py +0 -68
- prefect/_internal/pydantic/utilities/model_validator.py +0 -87
- prefect/_internal/pydantic/utilities/type_adapter.py +0 -71
- prefect/_vendor/__init__.py +0 -0
- prefect/_vendor/fastapi/__init__.py +0 -25
- prefect/_vendor/fastapi/applications.py +0 -946
- prefect/_vendor/fastapi/background.py +0 -3
- prefect/_vendor/fastapi/concurrency.py +0 -44
- prefect/_vendor/fastapi/datastructures.py +0 -58
- prefect/_vendor/fastapi/dependencies/__init__.py +0 -0
- prefect/_vendor/fastapi/dependencies/models.py +0 -64
- prefect/_vendor/fastapi/dependencies/utils.py +0 -877
- prefect/_vendor/fastapi/encoders.py +0 -177
- prefect/_vendor/fastapi/exception_handlers.py +0 -40
- prefect/_vendor/fastapi/exceptions.py +0 -46
- prefect/_vendor/fastapi/logger.py +0 -3
- prefect/_vendor/fastapi/middleware/__init__.py +0 -1
- prefect/_vendor/fastapi/middleware/asyncexitstack.py +0 -25
- prefect/_vendor/fastapi/middleware/cors.py +0 -3
- prefect/_vendor/fastapi/middleware/gzip.py +0 -3
- prefect/_vendor/fastapi/middleware/httpsredirect.py +0 -3
- prefect/_vendor/fastapi/middleware/trustedhost.py +0 -3
- prefect/_vendor/fastapi/middleware/wsgi.py +0 -3
- prefect/_vendor/fastapi/openapi/__init__.py +0 -0
- prefect/_vendor/fastapi/openapi/constants.py +0 -2
- prefect/_vendor/fastapi/openapi/docs.py +0 -203
- prefect/_vendor/fastapi/openapi/models.py +0 -480
- prefect/_vendor/fastapi/openapi/utils.py +0 -485
- prefect/_vendor/fastapi/param_functions.py +0 -340
- prefect/_vendor/fastapi/params.py +0 -453
- prefect/_vendor/fastapi/requests.py +0 -4
- prefect/_vendor/fastapi/responses.py +0 -40
- prefect/_vendor/fastapi/routing.py +0 -1331
- prefect/_vendor/fastapi/security/__init__.py +0 -15
- prefect/_vendor/fastapi/security/api_key.py +0 -98
- prefect/_vendor/fastapi/security/base.py +0 -6
- prefect/_vendor/fastapi/security/http.py +0 -172
- prefect/_vendor/fastapi/security/oauth2.py +0 -227
- prefect/_vendor/fastapi/security/open_id_connect_url.py +0 -34
- prefect/_vendor/fastapi/security/utils.py +0 -10
- prefect/_vendor/fastapi/staticfiles.py +0 -1
- prefect/_vendor/fastapi/templating.py +0 -3
- prefect/_vendor/fastapi/testclient.py +0 -1
- prefect/_vendor/fastapi/types.py +0 -3
- prefect/_vendor/fastapi/utils.py +0 -235
- prefect/_vendor/fastapi/websockets.py +0 -7
- prefect/_vendor/starlette/__init__.py +0 -1
- prefect/_vendor/starlette/_compat.py +0 -28
- prefect/_vendor/starlette/_exception_handler.py +0 -80
- prefect/_vendor/starlette/_utils.py +0 -88
- prefect/_vendor/starlette/applications.py +0 -261
- prefect/_vendor/starlette/authentication.py +0 -159
- prefect/_vendor/starlette/background.py +0 -43
- prefect/_vendor/starlette/concurrency.py +0 -59
- prefect/_vendor/starlette/config.py +0 -151
- prefect/_vendor/starlette/convertors.py +0 -87
- prefect/_vendor/starlette/datastructures.py +0 -707
- prefect/_vendor/starlette/endpoints.py +0 -130
- prefect/_vendor/starlette/exceptions.py +0 -60
- prefect/_vendor/starlette/formparsers.py +0 -276
- prefect/_vendor/starlette/middleware/__init__.py +0 -17
- prefect/_vendor/starlette/middleware/authentication.py +0 -52
- prefect/_vendor/starlette/middleware/base.py +0 -220
- prefect/_vendor/starlette/middleware/cors.py +0 -176
- prefect/_vendor/starlette/middleware/errors.py +0 -265
- prefect/_vendor/starlette/middleware/exceptions.py +0 -74
- prefect/_vendor/starlette/middleware/gzip.py +0 -113
- prefect/_vendor/starlette/middleware/httpsredirect.py +0 -19
- prefect/_vendor/starlette/middleware/sessions.py +0 -82
- prefect/_vendor/starlette/middleware/trustedhost.py +0 -64
- prefect/_vendor/starlette/middleware/wsgi.py +0 -147
- prefect/_vendor/starlette/requests.py +0 -328
- prefect/_vendor/starlette/responses.py +0 -347
- prefect/_vendor/starlette/routing.py +0 -933
- prefect/_vendor/starlette/schemas.py +0 -154
- prefect/_vendor/starlette/staticfiles.py +0 -248
- prefect/_vendor/starlette/status.py +0 -199
- prefect/_vendor/starlette/templating.py +0 -231
- prefect/_vendor/starlette/testclient.py +0 -804
- prefect/_vendor/starlette/types.py +0 -30
- prefect/_vendor/starlette/websockets.py +0 -193
- prefect/agent.py +0 -698
- prefect/deployments/deployments.py +0 -1042
- prefect/deprecated/__init__.py +0 -0
- prefect/deprecated/data_documents.py +0 -350
- prefect/deprecated/packaging/__init__.py +0 -12
- prefect/deprecated/packaging/base.py +0 -96
- prefect/deprecated/packaging/docker.py +0 -146
- prefect/deprecated/packaging/file.py +0 -92
- prefect/deprecated/packaging/orion.py +0 -80
- prefect/deprecated/packaging/serializers.py +0 -171
- prefect/events/instrument.py +0 -135
- prefect/infrastructure/base.py +0 -323
- prefect/infrastructure/container.py +0 -818
- prefect/infrastructure/kubernetes.py +0 -920
- prefect/infrastructure/process.py +0 -289
- prefect/new_task_engine.py +0 -423
- prefect/pydantic/__init__.py +0 -76
- prefect/pydantic/main.py +0 -39
- prefect/software/__init__.py +0 -2
- prefect/software/base.py +0 -50
- prefect/software/conda.py +0 -199
- prefect/software/pip.py +0 -122
- prefect/software/python.py +0 -52
- prefect/workers/block.py +0 -218
- prefect_client-2.19.3.dist-info/RECORD +0 -292
- {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/LICENSE +0 -0
- {prefect_client-2.19.3.dist-info → prefect_client-3.0.0rc1.dist-info}/WHEEL +0 -0
- {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
|
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
|
-
@
|
53
|
-
def selected_deployment_requires_id(
|
54
|
-
wants_selected_deployment =
|
55
|
-
has_deployment_id = bool(
|
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
|
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
|
-
@
|
203
|
-
def selected_work_queue_requires_id(
|
204
|
-
wants_selected_work_queue =
|
205
|
-
has_work_queue_id = bool(
|
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
|
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
|
-
@
|
245
|
-
def selected_automation_requires_id(
|
246
|
-
wants_selected_automation =
|
247
|
-
has_automation_id = bool(
|
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
|
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
|
164
|
+
automation = [no_really_json(a) for a in automation]
|
153
165
|
elif isinstance(automation, Automation):
|
154
|
-
automation = automation
|
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
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
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
|
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.
|
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.
|
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.
|
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.
|
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[
|
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
|
44
|
+
_top_level_filter: Optional["EventFilter"] = PrivateAttr(None)
|
50
45
|
|
51
46
|
def get_filters(self) -> List["EventDataFilter"]:
|
52
|
-
|
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:
|
68
|
+
since: DateTime = Field(
|
73
69
|
default_factory=lambda: cast(
|
74
|
-
|
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:
|
80
|
-
default_factory=lambda: cast(
|
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.
|
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.
|
47
|
+
return RelatedResource.model_validate(
|
48
48
|
{
|
49
49
|
"prefect.resource.id": resource_id,
|
50
50
|
"prefect.resource.role": role,
|
@@ -14,19 +14,15 @@ from typing import (
|
|
14
14
|
)
|
15
15
|
from uuid import UUID
|
16
16
|
|
17
|
-
from
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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.
|
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.
|
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.
|
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
|
-
|
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
|
-
@
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
189
|
-
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
|
-
|
194
|
+
data["within"] = timedelta(seconds=10.0)
|
194
195
|
elif within < timedelta(seconds=10.0):
|
195
196
|
raise ValueError(
|
196
|
-
"
|
197
|
+
"`within` for Proactive triggers must be greater than or equal to "
|
198
|
+
"10 seconds"
|
197
199
|
)
|
198
200
|
|
199
|
-
return
|
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
|
-
@
|
330
|
-
def validate_require(
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
if require
|
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
|
-
"
|
347
|
+
"require must be less than or equal to the number of triggers"
|
339
348
|
)
|
340
349
|
|
341
|
-
return
|
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.
|
391
|
-
SequenceTrigger.
|
399
|
+
CompoundTrigger.model_rebuild()
|
400
|
+
SequenceTrigger.model_rebuild()
|
392
401
|
|
393
402
|
|
394
403
|
class AutomationCore(PrefectBaseModel, extra="ignore"): # type: ignore[call-arg]
|