prefect-client 3.1.12__py3-none-any.whl → 3.1.14__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/_experimental/lineage.py +63 -0
- prefect/_experimental/sla/client.py +53 -27
- prefect/_experimental/sla/objects.py +10 -2
- prefect/_internal/concurrency/services.py +2 -2
- prefect/_internal/concurrency/threads.py +6 -0
- prefect/_internal/retries.py +6 -3
- prefect/_internal/schemas/validators.py +6 -4
- prefect/_version.py +3 -3
- prefect/artifacts.py +4 -1
- prefect/automations.py +1 -1
- prefect/blocks/abstract.py +5 -2
- prefect/blocks/notifications.py +1 -0
- prefect/cache_policies.py +70 -22
- prefect/client/orchestration/_automations/client.py +4 -0
- prefect/client/orchestration/_deployments/client.py +3 -3
- prefect/client/utilities.py +3 -3
- prefect/context.py +16 -6
- prefect/deployments/base.py +7 -4
- prefect/deployments/flow_runs.py +5 -1
- prefect/deployments/runner.py +6 -11
- prefect/deployments/steps/core.py +1 -1
- prefect/deployments/steps/pull.py +8 -3
- prefect/deployments/steps/utility.py +2 -2
- prefect/docker/docker_image.py +13 -9
- prefect/engine.py +19 -10
- prefect/events/cli/automations.py +4 -4
- prefect/events/clients.py +17 -14
- prefect/events/filters.py +34 -34
- prefect/events/schemas/automations.py +12 -8
- prefect/events/schemas/events.py +5 -1
- prefect/events/worker.py +1 -1
- prefect/filesystems.py +1 -1
- prefect/flow_engine.py +172 -123
- prefect/flows.py +119 -74
- prefect/futures.py +14 -7
- prefect/infrastructure/provisioners/__init__.py +2 -0
- prefect/infrastructure/provisioners/cloud_run.py +4 -4
- prefect/infrastructure/provisioners/coiled.py +249 -0
- prefect/infrastructure/provisioners/container_instance.py +4 -3
- prefect/infrastructure/provisioners/ecs.py +55 -43
- prefect/infrastructure/provisioners/modal.py +5 -4
- prefect/input/actions.py +5 -1
- prefect/input/run_input.py +157 -43
- prefect/logging/configuration.py +5 -8
- prefect/logging/filters.py +2 -2
- prefect/logging/formatters.py +15 -11
- prefect/logging/handlers.py +24 -14
- prefect/logging/highlighters.py +5 -5
- prefect/logging/loggers.py +29 -20
- prefect/main.py +3 -1
- prefect/results.py +166 -86
- prefect/runner/runner.py +112 -84
- prefect/runner/server.py +3 -1
- prefect/runner/storage.py +18 -18
- prefect/runner/submit.py +19 -12
- prefect/runtime/deployment.py +15 -8
- prefect/runtime/flow_run.py +19 -6
- prefect/runtime/task_run.py +7 -3
- prefect/settings/base.py +17 -7
- prefect/settings/legacy.py +4 -4
- prefect/settings/models/api.py +4 -3
- prefect/settings/models/cli.py +4 -3
- prefect/settings/models/client.py +7 -4
- prefect/settings/models/cloud.py +4 -3
- prefect/settings/models/deployments.py +4 -3
- prefect/settings/models/experiments.py +4 -3
- prefect/settings/models/flows.py +4 -3
- prefect/settings/models/internal.py +4 -3
- prefect/settings/models/logging.py +8 -6
- prefect/settings/models/results.py +4 -3
- prefect/settings/models/root.py +11 -16
- prefect/settings/models/runner.py +8 -5
- prefect/settings/models/server/api.py +6 -3
- prefect/settings/models/server/database.py +120 -25
- prefect/settings/models/server/deployments.py +4 -3
- prefect/settings/models/server/ephemeral.py +7 -4
- prefect/settings/models/server/events.py +6 -3
- prefect/settings/models/server/flow_run_graph.py +4 -3
- prefect/settings/models/server/root.py +4 -3
- prefect/settings/models/server/services.py +15 -12
- prefect/settings/models/server/tasks.py +7 -4
- prefect/settings/models/server/ui.py +4 -3
- prefect/settings/models/tasks.py +10 -5
- prefect/settings/models/testing.py +4 -3
- prefect/settings/models/worker.py +7 -4
- prefect/settings/profiles.py +13 -12
- prefect/settings/sources.py +20 -19
- prefect/states.py +17 -13
- prefect/task_engine.py +43 -33
- prefect/task_runners.py +35 -23
- prefect/task_runs.py +20 -11
- prefect/task_worker.py +12 -7
- prefect/tasks.py +67 -25
- prefect/telemetry/bootstrap.py +4 -1
- prefect/telemetry/run_telemetry.py +15 -13
- prefect/transactions.py +3 -3
- prefect/types/__init__.py +9 -6
- prefect/types/_datetime.py +19 -0
- prefect/utilities/_deprecated.py +38 -0
- prefect/utilities/engine.py +11 -4
- prefect/utilities/filesystem.py +2 -2
- prefect/utilities/generics.py +1 -1
- prefect/utilities/pydantic.py +21 -36
- prefect/workers/base.py +52 -30
- prefect/workers/process.py +20 -15
- prefect/workers/server.py +4 -5
- {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/METADATA +2 -2
- {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/RECORD +111 -108
- {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/LICENSE +0 -0
- {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/WHEEL +0 -0
- {prefect_client-3.1.12.dist-info → prefect_client-3.1.14.dist-info}/top_level.txt +0 -0
prefect/context.py
CHANGED
@@ -337,10 +337,15 @@ class EngineContext(RunContext):
|
|
337
337
|
flow: The flow instance associated with the run
|
338
338
|
flow_run: The API metadata for the flow run
|
339
339
|
task_runner: The task runner instance being used for the flow run
|
340
|
-
task_run_futures: A list of futures for task runs submitted within this flow run
|
341
|
-
task_run_states: A list of states for task runs created within this flow run
|
342
340
|
task_run_results: A mapping of result ids to task run states for this flow run
|
343
|
-
|
341
|
+
log_prints: Whether to log print statements from the flow run
|
342
|
+
parameters: The parameters passed to the flow run
|
343
|
+
detached: Flag indicating if context has been serialized and sent to remote infrastructure
|
344
|
+
result_store: The result store used to persist results
|
345
|
+
persist_result: Whether to persist the flow run result
|
346
|
+
task_run_dynamic_keys: Counter for task calls allowing unique keys
|
347
|
+
observed_flow_pauses: Counter for flow pauses
|
348
|
+
events: Events worker to emit events
|
344
349
|
"""
|
345
350
|
|
346
351
|
flow: Optional["Flow[Any, Any]"] = None
|
@@ -373,7 +378,7 @@ class EngineContext(RunContext):
|
|
373
378
|
__var__: ClassVar[ContextVar[Self]] = ContextVar("flow_run")
|
374
379
|
|
375
380
|
def serialize(self: Self, include_secrets: bool = True) -> dict[str, Any]:
|
376
|
-
|
381
|
+
serialized = self.model_dump(
|
377
382
|
include={
|
378
383
|
"flow_run",
|
379
384
|
"flow",
|
@@ -381,13 +386,18 @@ class EngineContext(RunContext):
|
|
381
386
|
"log_prints",
|
382
387
|
"start_time",
|
383
388
|
"input_keyset",
|
384
|
-
"result_store",
|
385
389
|
"persist_result",
|
386
390
|
},
|
387
391
|
exclude_unset=True,
|
388
|
-
serialize_as_any=True,
|
389
392
|
context={"include_secrets": include_secrets},
|
390
393
|
)
|
394
|
+
if self.result_store:
|
395
|
+
serialized["result_store"] = self.result_store.model_dump(
|
396
|
+
serialize_as_any=True,
|
397
|
+
exclude_unset=True,
|
398
|
+
context={"include_secrets": include_secrets},
|
399
|
+
)
|
400
|
+
return serialized
|
391
401
|
|
392
402
|
|
393
403
|
FlowRunContext = EngineContext # for backwards compatibility
|
prefect/deployments/base.py
CHANGED
@@ -19,6 +19,7 @@ from prefect.client.schemas.actions import DeploymentScheduleCreate
|
|
19
19
|
from prefect.client.schemas.objects import ConcurrencyLimitStrategy
|
20
20
|
from prefect.client.schemas.schedules import IntervalSchedule
|
21
21
|
from prefect.utilities._git import get_git_branch, get_git_remote_origin_url
|
22
|
+
from prefect.utilities.annotations import NotSet
|
22
23
|
from prefect.utilities.filesystem import create_default_ignore_file
|
23
24
|
from prefect.utilities.templating import apply_values
|
24
25
|
|
@@ -113,7 +114,9 @@ def create_default_prefect_yaml(
|
|
113
114
|
return True
|
114
115
|
|
115
116
|
|
116
|
-
def configure_project_by_recipe(
|
117
|
+
def configure_project_by_recipe(
|
118
|
+
recipe: str, **formatting_kwargs: Any
|
119
|
+
) -> dict[str, Any] | type[NotSet]:
|
117
120
|
"""
|
118
121
|
Given a recipe name, returns a dictionary representing base configuration options.
|
119
122
|
|
@@ -131,13 +134,13 @@ def configure_project_by_recipe(recipe: str, **formatting_kwargs) -> dict:
|
|
131
134
|
raise ValueError(f"Unknown recipe {recipe!r} provided.")
|
132
135
|
|
133
136
|
with recipe_path.open(mode="r") as f:
|
134
|
-
config = yaml.safe_load(f)
|
137
|
+
config: dict[str, Any] = yaml.safe_load(f)
|
135
138
|
|
136
|
-
|
139
|
+
templated_config = apply_values(
|
137
140
|
template=config, values=formatting_kwargs, remove_notset=False
|
138
141
|
)
|
139
142
|
|
140
|
-
return
|
143
|
+
return templated_config
|
141
144
|
|
142
145
|
|
143
146
|
def initialize_project(
|
prefect/deployments/flow_runs.py
CHANGED
prefect/deployments/runner.py
CHANGED
@@ -36,7 +36,6 @@ from pathlib import Path
|
|
36
36
|
from typing import TYPE_CHECKING, Any, ClassVar, Iterable, List, Optional, Union
|
37
37
|
from uuid import UUID
|
38
38
|
|
39
|
-
from exceptiongroup import ExceptionGroup # novermin
|
40
39
|
from pydantic import (
|
41
40
|
BaseModel,
|
42
41
|
ConfigDict,
|
@@ -361,7 +360,11 @@ class RunnerDeployment(BaseModel):
|
|
361
360
|
|
362
361
|
# We plan to support SLA configuration on the Prefect Server in the future.
|
363
362
|
# For now, we only support it on Prefect Cloud.
|
364
|
-
|
363
|
+
|
364
|
+
# If we're provided with an empty list, we will call the apply endpoint
|
365
|
+
# to remove existing SLAs for the deployment. If the argument is not provided,
|
366
|
+
# we will not call the endpoint.
|
367
|
+
if self._sla or self._sla == []:
|
365
368
|
await self._create_slas(deployment_id, client)
|
366
369
|
|
367
370
|
return deployment_id
|
@@ -371,15 +374,7 @@ class RunnerDeployment(BaseModel):
|
|
371
374
|
self._sla = [self._sla]
|
372
375
|
|
373
376
|
if client.server_type == ServerType.CLOUD:
|
374
|
-
|
375
|
-
for sla in self._sla:
|
376
|
-
try:
|
377
|
-
sla.set_deployment_id(deployment_id)
|
378
|
-
await client.create_sla(sla)
|
379
|
-
except Exception as e:
|
380
|
-
exceptions.append(e)
|
381
|
-
if exceptions:
|
382
|
-
raise ExceptionGroup("Failed to create SLAs", exceptions) # novermin
|
377
|
+
await client.apply_slas_for_deployment(deployment_id, self._sla)
|
383
378
|
else:
|
384
379
|
raise ValueError(
|
385
380
|
"SLA configuration is currently only supported on Prefect Cloud."
|
@@ -141,7 +141,7 @@ async def run_steps(
|
|
141
141
|
steps: List[Dict[str, Any]],
|
142
142
|
upstream_outputs: Optional[Dict[str, Any]] = None,
|
143
143
|
print_function: Any = print,
|
144
|
-
):
|
144
|
+
) -> dict[str, Any]:
|
145
145
|
upstream_outputs = deepcopy(upstream_outputs) if upstream_outputs else {}
|
146
146
|
for step in steps:
|
147
147
|
if not step:
|
@@ -12,7 +12,10 @@ from prefect.logging.loggers import get_logger
|
|
12
12
|
from prefect.runner.storage import BlockStorageAdapter, GitRepository, RemoteStorage
|
13
13
|
from prefect.utilities.asyncutils import run_coro_as_sync
|
14
14
|
|
15
|
-
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
import logging
|
17
|
+
|
18
|
+
deployment_logger: "logging.Logger" = get_logger("deployment")
|
16
19
|
|
17
20
|
if TYPE_CHECKING:
|
18
21
|
from prefect.blocks.core import Block
|
@@ -197,7 +200,7 @@ def git_clone(
|
|
197
200
|
return dict(directory=str(storage.destination.relative_to(Path.cwd())))
|
198
201
|
|
199
202
|
|
200
|
-
async def pull_from_remote_storage(url: str, **settings: Any):
|
203
|
+
async def pull_from_remote_storage(url: str, **settings: Any) -> dict[str, Any]:
|
201
204
|
"""
|
202
205
|
Pulls code from a remote storage location into the current working directory.
|
203
206
|
|
@@ -239,7 +242,9 @@ async def pull_from_remote_storage(url: str, **settings: Any):
|
|
239
242
|
return {"directory": directory}
|
240
243
|
|
241
244
|
|
242
|
-
async def pull_with_block(
|
245
|
+
async def pull_with_block(
|
246
|
+
block_document_name: str, block_type_slug: str
|
247
|
+
) -> dict[str, Any]:
|
243
248
|
"""
|
244
249
|
Pulls code using a block.
|
245
250
|
|
@@ -26,7 +26,7 @@ import shlex
|
|
26
26
|
import string
|
27
27
|
import subprocess
|
28
28
|
import sys
|
29
|
-
from typing import Dict, Optional
|
29
|
+
from typing import Any, Dict, Optional
|
30
30
|
|
31
31
|
from anyio import create_task_group
|
32
32
|
from anyio.streams.text import TextReceiveStream
|
@@ -205,7 +205,7 @@ async def pip_install_requirements(
|
|
205
205
|
directory: Optional[str] = None,
|
206
206
|
requirements_file: str = "requirements.txt",
|
207
207
|
stream_output: bool = True,
|
208
|
-
):
|
208
|
+
) -> dict[str, Any]:
|
209
209
|
"""
|
210
210
|
Installs dependencies from a requirements.txt file.
|
211
211
|
|
prefect/docker/docker_image.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from pathlib import Path
|
2
|
-
from typing import Optional
|
2
|
+
from typing import Any, Optional
|
3
3
|
|
4
4
|
from pendulum import now as pendulum_now
|
5
5
|
|
@@ -34,7 +34,11 @@ class DockerImage:
|
|
34
34
|
"""
|
35
35
|
|
36
36
|
def __init__(
|
37
|
-
self,
|
37
|
+
self,
|
38
|
+
name: str,
|
39
|
+
tag: Optional[str] = None,
|
40
|
+
dockerfile: str = "auto",
|
41
|
+
**build_kwargs: Any,
|
38
42
|
):
|
39
43
|
image_name, image_tag = parse_image_tag(name)
|
40
44
|
if tag and image_tag:
|
@@ -49,16 +53,16 @@ class DockerImage:
|
|
49
53
|
namespace = PREFECT_DEFAULT_DOCKER_BUILD_NAMESPACE.value()
|
50
54
|
# join the namespace and repository to create the full image name
|
51
55
|
# ignore namespace if it is None
|
52
|
-
self.name = "/".join(filter(None, [namespace, repository]))
|
53
|
-
self.tag = tag or image_tag or slugify(pendulum_now("utc").isoformat())
|
54
|
-
self.dockerfile = dockerfile
|
55
|
-
self.build_kwargs = build_kwargs
|
56
|
+
self.name: str = "/".join(filter(None, [namespace, repository]))
|
57
|
+
self.tag: str = tag or image_tag or slugify(pendulum_now("utc").isoformat())
|
58
|
+
self.dockerfile: str = dockerfile
|
59
|
+
self.build_kwargs: dict[str, Any] = build_kwargs
|
56
60
|
|
57
61
|
@property
|
58
|
-
def reference(self):
|
62
|
+
def reference(self) -> str:
|
59
63
|
return f"{self.name}:{self.tag}"
|
60
64
|
|
61
|
-
def build(self):
|
65
|
+
def build(self) -> None:
|
62
66
|
full_image_name = self.reference
|
63
67
|
build_kwargs = self.build_kwargs.copy()
|
64
68
|
build_kwargs["context"] = Path.cwd()
|
@@ -72,7 +76,7 @@ class DockerImage:
|
|
72
76
|
build_kwargs["dockerfile"] = self.dockerfile
|
73
77
|
build_image(**build_kwargs)
|
74
78
|
|
75
|
-
def push(self):
|
79
|
+
def push(self) -> None:
|
76
80
|
with docker_client() as client:
|
77
81
|
events = client.api.push(
|
78
82
|
repository=self.name, tag=self.tag, stream=True, decode=True
|
prefect/engine.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import os
|
2
2
|
import sys
|
3
|
-
from typing import Any, Callable
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
4
4
|
from uuid import UUID
|
5
5
|
|
6
6
|
from prefect._internal.compatibility.migration import getattr_migration
|
@@ -15,12 +15,19 @@ from prefect.utilities.asyncutils import (
|
|
15
15
|
run_coro_as_sync,
|
16
16
|
)
|
17
17
|
|
18
|
-
|
18
|
+
if TYPE_CHECKING:
|
19
|
+
import logging
|
20
|
+
|
21
|
+
from prefect.flow_engine import FlowRun
|
22
|
+
from prefect.flows import Flow
|
23
|
+
from prefect.logging.loggers import LoggingAdapter
|
24
|
+
|
25
|
+
engine_logger: "logging.Logger" = get_logger("engine")
|
19
26
|
|
20
27
|
|
21
28
|
if __name__ == "__main__":
|
22
29
|
try:
|
23
|
-
flow_run_id = UUID(
|
30
|
+
flow_run_id: UUID = UUID(
|
24
31
|
sys.argv[1] if len(sys.argv) > 1 else os.environ.get("PREFECT__FLOW_RUN_ID")
|
25
32
|
)
|
26
33
|
except Exception:
|
@@ -37,11 +44,11 @@ if __name__ == "__main__":
|
|
37
44
|
run_flow,
|
38
45
|
)
|
39
46
|
|
40
|
-
flow_run = load_flow_run(flow_run_id=flow_run_id)
|
41
|
-
run_logger = flow_run_logger(flow_run=flow_run)
|
47
|
+
flow_run: "FlowRun" = load_flow_run(flow_run_id=flow_run_id)
|
48
|
+
run_logger: "LoggingAdapter" = flow_run_logger(flow_run=flow_run)
|
42
49
|
|
43
50
|
try:
|
44
|
-
flow = load_flow(flow_run)
|
51
|
+
flow: "Flow[..., Any]" = load_flow(flow_run)
|
45
52
|
except Exception:
|
46
53
|
run_logger.error(
|
47
54
|
"Unexpected exception encountered when trying to load flow",
|
@@ -55,15 +62,17 @@ if __name__ == "__main__":
|
|
55
62
|
else:
|
56
63
|
run_flow(flow, flow_run=flow_run, error_logger=run_logger)
|
57
64
|
|
58
|
-
except Abort as
|
65
|
+
except Abort as abort_signal:
|
66
|
+
abort_signal: Abort
|
59
67
|
engine_logger.info(
|
60
68
|
f"Engine execution of flow run '{flow_run_id}' aborted by orchestrator:"
|
61
|
-
f" {
|
69
|
+
f" {abort_signal}"
|
62
70
|
)
|
63
71
|
exit(0)
|
64
|
-
except Pause as
|
72
|
+
except Pause as pause_signal:
|
73
|
+
pause_signal: Pause
|
65
74
|
engine_logger.info(
|
66
|
-
f"Engine execution of flow run '{flow_run_id}' is paused: {
|
75
|
+
f"Engine execution of flow run '{flow_run_id}' is paused: {pause_signal}"
|
67
76
|
)
|
68
77
|
exit(0)
|
69
78
|
except Exception:
|
@@ -3,7 +3,7 @@ Command line interface for working with automations.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import functools
|
6
|
-
from typing import Optional, Type
|
6
|
+
from typing import Any, Callable, Optional, Type
|
7
7
|
from uuid import UUID
|
8
8
|
|
9
9
|
import orjson
|
@@ -21,16 +21,16 @@ from prefect.client.orchestration import get_client
|
|
21
21
|
from prefect.events.schemas.automations import Automation
|
22
22
|
from prefect.exceptions import PrefectHTTPStatusError
|
23
23
|
|
24
|
-
automations_app = PrefectTyper(
|
24
|
+
automations_app: PrefectTyper = PrefectTyper(
|
25
25
|
name="automation",
|
26
26
|
help="Manage automations.",
|
27
27
|
)
|
28
28
|
app.add_typer(automations_app, aliases=["automations"])
|
29
29
|
|
30
30
|
|
31
|
-
def requires_automations(func):
|
31
|
+
def requires_automations(func: Callable[..., Any]) -> Callable[..., Any]:
|
32
32
|
@functools.wraps(func)
|
33
|
-
async def wrapper(*args, **kwargs):
|
33
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
34
34
|
try:
|
35
35
|
return await func(*args, **kwargs)
|
36
36
|
except RuntimeError as exc:
|
prefect/events/clients.py
CHANGED
@@ -70,18 +70,21 @@ EVENT_WEBSOCKET_CHECKPOINTS = Counter(
|
|
70
70
|
labelnames=["client"],
|
71
71
|
)
|
72
72
|
|
73
|
-
|
73
|
+
if TYPE_CHECKING:
|
74
|
+
import logging
|
75
|
+
|
76
|
+
logger: "logging.Logger" = get_logger(__name__)
|
74
77
|
|
75
78
|
|
76
|
-
def http_to_ws(url: str):
|
79
|
+
def http_to_ws(url: str) -> str:
|
77
80
|
return url.replace("https://", "wss://").replace("http://", "ws://").rstrip("/")
|
78
81
|
|
79
82
|
|
80
|
-
def events_in_socket_from_api_url(url: str):
|
83
|
+
def events_in_socket_from_api_url(url: str) -> str:
|
81
84
|
return http_to_ws(url) + "/events/in"
|
82
85
|
|
83
86
|
|
84
|
-
def events_out_socket_from_api_url(url: str):
|
87
|
+
def events_out_socket_from_api_url(url: str) -> str:
|
85
88
|
return http_to_ws(url) + "/events/out"
|
86
89
|
|
87
90
|
|
@@ -250,11 +253,11 @@ class AssertingEventsClient(EventsClient):
|
|
250
253
|
last: ClassVar["Optional[AssertingEventsClient]"] = None
|
251
254
|
all: ClassVar[List["AssertingEventsClient"]] = []
|
252
255
|
|
253
|
-
args:
|
254
|
-
kwargs:
|
255
|
-
events:
|
256
|
+
args: tuple[Any, ...]
|
257
|
+
kwargs: dict[str, Any]
|
258
|
+
events: list[Event]
|
256
259
|
|
257
|
-
def __init__(self, *args, **kwargs):
|
260
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
258
261
|
AssertingEventsClient.last = self
|
259
262
|
AssertingEventsClient.all.append(self)
|
260
263
|
self.args = args
|
@@ -431,13 +434,13 @@ class AssertingPassthroughEventsClient(PrefectEventsClient):
|
|
431
434
|
during tests AND sends them to a Prefect server."""
|
432
435
|
|
433
436
|
last: ClassVar["Optional[AssertingPassthroughEventsClient]"] = None
|
434
|
-
all: ClassVar[
|
437
|
+
all: ClassVar[list["AssertingPassthroughEventsClient"]] = []
|
435
438
|
|
436
|
-
args:
|
437
|
-
kwargs:
|
438
|
-
events:
|
439
|
+
args: tuple[Any, ...]
|
440
|
+
kwargs: dict[str, Any]
|
441
|
+
events: list[Event]
|
439
442
|
|
440
|
-
def __init__(self, *args, **kwargs):
|
443
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
441
444
|
super().__init__(*args, **kwargs)
|
442
445
|
AssertingPassthroughEventsClient.last = self
|
443
446
|
AssertingPassthroughEventsClient.all.append(self)
|
@@ -449,7 +452,7 @@ class AssertingPassthroughEventsClient(PrefectEventsClient):
|
|
449
452
|
cls.last = None
|
450
453
|
cls.all = []
|
451
454
|
|
452
|
-
def pop_events(self) ->
|
455
|
+
def pop_events(self) -> list[Event]:
|
453
456
|
events = self.events
|
454
457
|
self.events = []
|
455
458
|
return events
|
prefect/events/filters.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Optional
|
2
2
|
from uuid import UUID
|
3
3
|
|
4
|
-
import pendulum
|
5
4
|
from pydantic import Field
|
6
5
|
|
7
6
|
from prefect._internal.schemas.bases import PrefectBaseModel
|
@@ -23,7 +22,7 @@ class AutomationFilterCreated(PrefectBaseModel):
|
|
23
22
|
class AutomationFilterName(PrefectBaseModel):
|
24
23
|
"""Filter by `Automation.created`."""
|
25
24
|
|
26
|
-
any_: Optional[
|
25
|
+
any_: Optional[list[str]] = Field(
|
27
26
|
default=None,
|
28
27
|
description="Only include automations with names that match any of these strings",
|
29
28
|
)
|
@@ -41,8 +40,8 @@ class AutomationFilter(PrefectBaseModel):
|
|
41
40
|
class EventDataFilter(PrefectBaseModel, extra="forbid"): # type: ignore[call-arg]
|
42
41
|
"""A base class for filtering event data."""
|
43
42
|
|
44
|
-
def get_filters(self) ->
|
45
|
-
filters:
|
43
|
+
def get_filters(self) -> list["EventDataFilter"]:
|
44
|
+
filters: list["EventDataFilter"] = [
|
46
45
|
filter
|
47
46
|
for filter in [getattr(self, name) for name in self.model_fields]
|
48
47
|
if isinstance(filter, EventDataFilter)
|
@@ -60,14 +59,11 @@ class EventDataFilter(PrefectBaseModel, extra="forbid"): # type: ignore[call-ar
|
|
60
59
|
|
61
60
|
class EventOccurredFilter(EventDataFilter):
|
62
61
|
since: DateTime = Field(
|
63
|
-
default_factory=lambda:
|
64
|
-
DateTime,
|
65
|
-
pendulum.now("UTC").start_of("day").subtract(days=180),
|
66
|
-
),
|
62
|
+
default_factory=lambda: DateTime.now("UTC").start_of("day").subtract(days=180),
|
67
63
|
description="Only include events after this time (inclusive)",
|
68
64
|
)
|
69
65
|
until: DateTime = Field(
|
70
|
-
default_factory=lambda:
|
66
|
+
default_factory=lambda: DateTime.now("UTC"),
|
71
67
|
description="Only include events prior to this time (inclusive)",
|
72
68
|
)
|
73
69
|
|
@@ -76,18 +72,18 @@ class EventOccurredFilter(EventDataFilter):
|
|
76
72
|
|
77
73
|
|
78
74
|
class EventNameFilter(EventDataFilter):
|
79
|
-
prefix: Optional[
|
75
|
+
prefix: Optional[list[str]] = Field(
|
80
76
|
default=None, description="Only include events matching one of these prefixes"
|
81
77
|
)
|
82
|
-
exclude_prefix: Optional[
|
78
|
+
exclude_prefix: Optional[list[str]] = Field(
|
83
79
|
default=None, description="Exclude events matching one of these prefixes"
|
84
80
|
)
|
85
81
|
|
86
|
-
name: Optional[
|
82
|
+
name: Optional[list[str]] = Field(
|
87
83
|
default=None,
|
88
84
|
description="Only include events matching one of these names exactly",
|
89
85
|
)
|
90
|
-
exclude_name: Optional[
|
86
|
+
exclude_name: Optional[list[str]] = Field(
|
91
87
|
default=None, description="Exclude events matching one of these names exactly"
|
92
88
|
)
|
93
89
|
|
@@ -112,20 +108,20 @@ class EventNameFilter(EventDataFilter):
|
|
112
108
|
|
113
109
|
|
114
110
|
class EventResourceFilter(EventDataFilter):
|
115
|
-
id: Optional[
|
116
|
-
None, description="Only include events for resources with these IDs"
|
111
|
+
id: Optional[list[str]] = Field(
|
112
|
+
default=None, description="Only include events for resources with these IDs"
|
117
113
|
)
|
118
|
-
id_prefix: Optional[
|
119
|
-
None,
|
114
|
+
id_prefix: Optional[list[str]] = Field(
|
115
|
+
default=None,
|
120
116
|
description=(
|
121
117
|
"Only include events for resources with IDs starting with these prefixes."
|
122
118
|
),
|
123
119
|
)
|
124
120
|
labels: Optional[ResourceSpecification] = Field(
|
125
|
-
None, description="Only include events for resources with these labels"
|
121
|
+
default=None, description="Only include events for resources with these labels"
|
126
122
|
)
|
127
123
|
distinct: bool = Field(
|
128
|
-
False,
|
124
|
+
default=False,
|
129
125
|
description="Only include events for distinct resources",
|
130
126
|
)
|
131
127
|
|
@@ -148,35 +144,39 @@ class EventResourceFilter(EventDataFilter):
|
|
148
144
|
|
149
145
|
|
150
146
|
class EventRelatedFilter(EventDataFilter):
|
151
|
-
id: Optional[
|
152
|
-
None,
|
147
|
+
id: Optional[list[str]] = Field(
|
148
|
+
default=None,
|
149
|
+
description="Only include events for related resources with these IDs",
|
153
150
|
)
|
154
|
-
role: Optional[
|
155
|
-
None,
|
151
|
+
role: Optional[list[str]] = Field(
|
152
|
+
default=None,
|
153
|
+
description="Only include events for related resources in these roles",
|
156
154
|
)
|
157
|
-
resources_in_roles: Optional[
|
158
|
-
None,
|
155
|
+
resources_in_roles: Optional[list[tuple[str, str]]] = Field(
|
156
|
+
default=None,
|
159
157
|
description=(
|
160
158
|
"Only include events with specific related resources in specific roles"
|
161
159
|
),
|
162
160
|
)
|
163
161
|
labels: Optional[ResourceSpecification] = Field(
|
164
|
-
None,
|
162
|
+
default=None,
|
163
|
+
description="Only include events for related resources with these labels",
|
165
164
|
)
|
166
165
|
|
167
166
|
|
168
167
|
class EventAnyResourceFilter(EventDataFilter):
|
169
|
-
id: Optional[
|
170
|
-
None, description="Only include events for resources with these IDs"
|
168
|
+
id: Optional[list[str]] = Field(
|
169
|
+
default=None, description="Only include events for resources with these IDs"
|
171
170
|
)
|
172
|
-
id_prefix: Optional[
|
173
|
-
None,
|
171
|
+
id_prefix: Optional[list[str]] = Field(
|
172
|
+
default=None,
|
174
173
|
description=(
|
175
174
|
"Only include events for resources with IDs starting with these prefixes"
|
176
175
|
),
|
177
176
|
)
|
178
177
|
labels: Optional[ResourceSpecification] = Field(
|
179
|
-
None,
|
178
|
+
default=None,
|
179
|
+
description="Only include events for related resources with these labels",
|
180
180
|
)
|
181
181
|
|
182
182
|
def includes(self, event: Event) -> bool:
|
@@ -202,8 +202,8 @@ class EventAnyResourceFilter(EventDataFilter):
|
|
202
202
|
|
203
203
|
|
204
204
|
class EventIDFilter(EventDataFilter):
|
205
|
-
id: Optional[
|
206
|
-
None, description="Only include events with one of these IDs"
|
205
|
+
id: Optional[list[UUID]] = Field(
|
206
|
+
default=None, description="Only include events with one of these IDs"
|
207
207
|
)
|
208
208
|
|
209
209
|
def includes(self, event: Event) -> bool:
|
@@ -52,7 +52,7 @@ class Trigger(PrefectBaseModel, abc.ABC, extra="ignore"): # type: ignore[call-a
|
|
52
52
|
|
53
53
|
_deployment_id: Optional[UUID] = PrivateAttr(default=None)
|
54
54
|
|
55
|
-
def set_deployment_id(self, deployment_id: UUID):
|
55
|
+
def set_deployment_id(self, deployment_id: UUID) -> None:
|
56
56
|
self._deployment_id = deployment_id
|
57
57
|
|
58
58
|
def owner_resource(self) -> Optional[str]:
|
@@ -277,7 +277,7 @@ class MetricTriggerQuery(PrefectBaseModel):
|
|
277
277
|
)
|
278
278
|
|
279
279
|
@field_validator("range", "firing_for")
|
280
|
-
def enforce_minimum_range(cls, value: timedelta):
|
280
|
+
def enforce_minimum_range(cls, value: timedelta) -> timedelta:
|
281
281
|
if value < timedelta(seconds=300):
|
282
282
|
raise ValueError("The minimum range is 300 seconds (5 minutes)")
|
283
283
|
return value
|
@@ -404,13 +404,17 @@ class AutomationCore(PrefectBaseModel, extra="ignore"): # type: ignore[call-arg
|
|
404
404
|
"""Defines an action a user wants to take when a certain number of events
|
405
405
|
do or don't happen to the matching resources"""
|
406
406
|
|
407
|
-
name: str = Field(
|
408
|
-
description: str = Field(
|
407
|
+
name: str = Field(default=..., description="The name of this automation")
|
408
|
+
description: str = Field(
|
409
|
+
default="", description="A longer description of this automation"
|
410
|
+
)
|
409
411
|
|
410
|
-
enabled: bool = Field(
|
412
|
+
enabled: bool = Field(
|
413
|
+
default=True, description="Whether this automation will be evaluated"
|
414
|
+
)
|
411
415
|
|
412
416
|
trigger: TriggerTypes = Field(
|
413
|
-
|
417
|
+
default=...,
|
414
418
|
description=(
|
415
419
|
"The criteria for which events this Automation covers and how it will "
|
416
420
|
"respond to the presence or absence of those events"
|
@@ -418,7 +422,7 @@ class AutomationCore(PrefectBaseModel, extra="ignore"): # type: ignore[call-arg
|
|
418
422
|
)
|
419
423
|
|
420
424
|
actions: List[ActionTypes] = Field(
|
421
|
-
|
425
|
+
default=...,
|
422
426
|
description="The actions to perform when this Automation triggers",
|
423
427
|
)
|
424
428
|
|
@@ -438,4 +442,4 @@ class AutomationCore(PrefectBaseModel, extra="ignore"): # type: ignore[call-arg
|
|
438
442
|
|
439
443
|
|
440
444
|
class Automation(AutomationCore):
|
441
|
-
id: UUID = Field(
|
445
|
+
id: UUID = Field(default=..., description="The ID of this automation")
|
prefect/events/schemas/events.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import copy
|
2
2
|
from collections import defaultdict
|
3
3
|
from typing import (
|
4
|
+
TYPE_CHECKING,
|
4
5
|
Any,
|
5
6
|
ClassVar,
|
6
7
|
Dict,
|
@@ -32,7 +33,10 @@ from prefect.types import DateTime
|
|
32
33
|
|
33
34
|
from .labelling import Labelled
|
34
35
|
|
35
|
-
|
36
|
+
if TYPE_CHECKING:
|
37
|
+
import logging
|
38
|
+
|
39
|
+
logger: "logging.Logger" = get_logger(__name__)
|
36
40
|
|
37
41
|
|
38
42
|
class Resource(Labelled):
|
prefect/events/worker.py
CHANGED
@@ -82,7 +82,7 @@ class EventsWorker(QueueService[Event]):
|
|
82
82
|
|
83
83
|
await self._client.emit(event)
|
84
84
|
|
85
|
-
async def attach_related_resources_from_context(self, event: Event):
|
85
|
+
async def attach_related_resources_from_context(self, event: Event) -> None:
|
86
86
|
if "prefect.resource.lineage-group" in event.resource:
|
87
87
|
# We attach related resources to lineage events in `emit_lineage_event`,
|
88
88
|
# instead of the worker, because not all run-related resources are
|
prefect/filesystems.py
CHANGED
@@ -281,7 +281,7 @@ class RemoteFileSystem(WritableFileSystem, WritableDeploymentStorage):
|
|
281
281
|
_filesystem: fsspec.AbstractFileSystem = None
|
282
282
|
|
283
283
|
@field_validator("basepath")
|
284
|
-
def check_basepath(cls, value):
|
284
|
+
def check_basepath(cls, value: str) -> str:
|
285
285
|
return validate_basepath(value)
|
286
286
|
|
287
287
|
def _resolve_path(self, path: str) -> str:
|