prefect-client 3.0.0rc9__py3-none-any.whl → 3.0.0rc11__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/_internal/compatibility/migration.py +48 -8
- prefect/_internal/concurrency/api.py +1 -1
- prefect/_internal/retries.py +61 -0
- prefect/agent.py +6 -0
- prefect/client/cloud.py +1 -1
- prefect/client/schemas/objects.py +3 -4
- prefect/concurrency/asyncio.py +3 -3
- prefect/concurrency/events.py +1 -1
- prefect/concurrency/services.py +3 -2
- prefect/concurrency/sync.py +19 -5
- prefect/context.py +14 -2
- prefect/deployments/__init__.py +28 -15
- prefect/deployments/schedules.py +5 -2
- prefect/deployments/steps/pull.py +7 -0
- prefect/events/schemas/automations.py +3 -3
- prefect/exceptions.py +4 -1
- prefect/filesystems.py +4 -3
- prefect/flow_engine.py +76 -14
- prefect/flows.py +222 -64
- prefect/futures.py +53 -7
- prefect/infrastructure/__init__.py +6 -0
- prefect/infrastructure/base.py +6 -0
- prefect/logging/loggers.py +1 -1
- prefect/results.py +50 -67
- prefect/runner/runner.py +93 -20
- prefect/runner/server.py +20 -22
- prefect/runner/submit.py +0 -8
- prefect/runtime/flow_run.py +38 -3
- prefect/serializers.py +3 -3
- prefect/settings.py +15 -45
- prefect/task_engine.py +77 -21
- prefect/task_runners.py +28 -16
- prefect/task_worker.py +6 -4
- prefect/tasks.py +30 -5
- prefect/transactions.py +18 -2
- prefect/utilities/asyncutils.py +9 -3
- prefect/utilities/engine.py +34 -1
- prefect/utilities/importtools.py +1 -1
- prefect/utilities/timeout.py +20 -5
- prefect/workers/base.py +98 -208
- prefect/workers/block.py +6 -0
- prefect/workers/cloud.py +6 -0
- prefect/workers/process.py +262 -4
- prefect/workers/server.py +27 -9
- {prefect_client-3.0.0rc9.dist-info → prefect_client-3.0.0rc11.dist-info}/METADATA +4 -4
- {prefect_client-3.0.0rc9.dist-info → prefect_client-3.0.0rc11.dist-info}/RECORD +49 -44
- {prefect_client-3.0.0rc9.dist-info → prefect_client-3.0.0rc11.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.0rc9.dist-info → prefect_client-3.0.0rc11.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.0rc9.dist-info → prefect_client-3.0.0rc11.dist-info}/top_level.txt +0 -0
@@ -5,11 +5,41 @@ The `getattr_migration` function is used to handle imports for moved or removed
|
|
5
5
|
It is used in the `__getattr__` attribute of modules that have moved or removed objects.
|
6
6
|
|
7
7
|
Usage:
|
8
|
-
```python
|
9
|
-
from prefect._internal.compatibility.migration import getattr_migration
|
10
8
|
|
11
|
-
|
12
|
-
|
9
|
+
Moved objects:
|
10
|
+
1. Add the old and new path to the `MOVED_IN_V3` dictionary, e.g. `MOVED_IN_V3 = {"old_path": "new_path"}`
|
11
|
+
2. In the module where the object was moved from, add the following lines:
|
12
|
+
```python
|
13
|
+
# at top
|
14
|
+
from prefect._internal.compatibility.migration import getattr_migration
|
15
|
+
|
16
|
+
# at bottom
|
17
|
+
__getattr__ = getattr_migration(__name__)
|
18
|
+
```
|
19
|
+
|
20
|
+
Example at src/prefect/engine.py
|
21
|
+
|
22
|
+
Removed objects:
|
23
|
+
1. Add the old path and error message to the `REMOVED_IN_V3` dictionary, e.g. `REMOVED_IN_V3 = {"old_path": "error_message"}`
|
24
|
+
2. In the module where the object was removed, add the following lines:
|
25
|
+
```python
|
26
|
+
# at top
|
27
|
+
from prefect._internal.compatibility.migration import getattr_migration
|
28
|
+
|
29
|
+
# at bottom
|
30
|
+
__getattr__ = getattr_migration(__name__)
|
31
|
+
|
32
|
+
```
|
33
|
+
If the entire old module was removed, add a stub for the module with the following lines:
|
34
|
+
```python
|
35
|
+
# at top
|
36
|
+
from prefect._internal.compatibility.migration import getattr_migration
|
37
|
+
|
38
|
+
# at bottom
|
39
|
+
__getattr__ = getattr_migration(__name__)
|
40
|
+
```
|
41
|
+
|
42
|
+
Example at src/prefect/infrastructure/base.py
|
13
43
|
"""
|
14
44
|
|
15
45
|
import sys
|
@@ -30,14 +60,24 @@ MOVED_IN_V3 = {
|
|
30
60
|
"prefect.client:get_client": "prefect.client.orchestration:get_client",
|
31
61
|
}
|
32
62
|
|
63
|
+
upgrade_guide_msg = "Refer to the upgrade guide for more information: https://docs.prefect.io/latest/guides/upgrade-guide-agents-to-workers/."
|
64
|
+
|
33
65
|
REMOVED_IN_V3 = {
|
34
66
|
"prefect.client.schemas.objects:MinimalDeploymentSchedule": "Use `prefect.client.schemas.actions.DeploymentScheduleCreate` instead.",
|
67
|
+
"prefect.context:PrefectObjectRegistry": upgrade_guide_msg,
|
35
68
|
"prefect.deployments.deployments:Deployment": "Use `flow.serve()`, `flow.deploy()`, or `prefect deploy` instead.",
|
36
69
|
"prefect.deployments:Deployment": "Use `flow.serve()`, `flow.deploy()`, or `prefect deploy` instead.",
|
37
|
-
"prefect.filesystems:GCS": "Use `prefect_gcp` instead.",
|
38
|
-
"prefect.filesystems:Azure": "Use `prefect_azure` instead.",
|
39
|
-
"prefect.filesystems:S3": "Use `prefect_aws` instead.",
|
70
|
+
"prefect.filesystems:GCS": "Use `prefect_gcp.GcsBucket` instead.",
|
71
|
+
"prefect.filesystems:Azure": "Use `prefect_azure.AzureBlobStorageContainer` instead.",
|
72
|
+
"prefect.filesystems:S3": "Use `prefect_aws.S3Bucket` instead.",
|
73
|
+
"prefect.filesystems:GitHub": "Use `prefect_github.GitHubRepository` instead.",
|
40
74
|
"prefect.engine:_out_of_process_pause": "Use `prefect.flow_runs.pause_flow_run` instead.",
|
75
|
+
"prefect.agent:PrefectAgent": "Use workers instead. " + upgrade_guide_msg,
|
76
|
+
"prefect.infrastructure:KubernetesJob": "Use workers instead. " + upgrade_guide_msg,
|
77
|
+
"prefect.infrastructure.base:Infrastructure": "Use the `BaseWorker` class to create custom infrastructure integrations instead. "
|
78
|
+
+ upgrade_guide_msg,
|
79
|
+
"prefect.workers.block:BlockWorkerJobConfiguration": upgrade_guide_msg,
|
80
|
+
"prefect.workers.cloud:BlockWorker": upgrade_guide_msg,
|
41
81
|
}
|
42
82
|
|
43
83
|
# IMPORTANT FOR USAGE: When adding new modules to MOVED_IN_V3 or REMOVED_IN_V3, include the following lines at the bottom of that module:
|
@@ -114,7 +154,7 @@ def getattr_migration(module_name: str) -> Callable[[str], Any]:
|
|
114
154
|
if import_path in REMOVED_IN_V3.keys():
|
115
155
|
error_message = REMOVED_IN_V3[import_path]
|
116
156
|
raise PrefectImportError(
|
117
|
-
f"{import_path
|
157
|
+
f"`{import_path}` has been removed. {error_message}"
|
118
158
|
)
|
119
159
|
|
120
160
|
globals: Dict[str, Any] = sys.modules[module_name].__dict__
|
@@ -151,7 +151,7 @@ class from_async(_base):
|
|
151
151
|
__call: Union[Callable[[], T], Call[T]],
|
152
152
|
timeout: Optional[float] = None,
|
153
153
|
done_callbacks: Optional[Iterable[Call]] = None,
|
154
|
-
) ->
|
154
|
+
) -> T:
|
155
155
|
call = _cast_to_call(__call)
|
156
156
|
waiter = AsyncWaiter(call=call)
|
157
157
|
for callback in done_callbacks or []:
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import asyncio
|
2
|
+
from functools import wraps
|
3
|
+
from typing import Any, Callable, Tuple, Type
|
4
|
+
|
5
|
+
from prefect.logging.loggers import get_logger
|
6
|
+
from prefect.utilities.math import clamped_poisson_interval
|
7
|
+
|
8
|
+
logger = get_logger("retries")
|
9
|
+
|
10
|
+
|
11
|
+
def exponential_backoff_with_jitter(
|
12
|
+
attempt: int, base_delay: float, max_delay: float
|
13
|
+
) -> float:
|
14
|
+
average_interval = min(base_delay * (2**attempt), max_delay)
|
15
|
+
return clamped_poisson_interval(average_interval, clamping_factor=0.3)
|
16
|
+
|
17
|
+
|
18
|
+
def retry_async_fn(
|
19
|
+
max_attempts: int = 3,
|
20
|
+
backoff_strategy: Callable[
|
21
|
+
[int, float, float], float
|
22
|
+
] = exponential_backoff_with_jitter,
|
23
|
+
base_delay: float = 1,
|
24
|
+
max_delay: float = 10,
|
25
|
+
retry_on_exceptions: Tuple[Type[Exception], ...] = (Exception,),
|
26
|
+
):
|
27
|
+
"""A decorator for retrying an async function.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
max_attempts: The maximum number of times to retry the function.
|
31
|
+
backoff_strategy: A function that takes in the number of attempts, the base
|
32
|
+
delay, and the maximum delay, and returns the delay to use for the next
|
33
|
+
attempt. Defaults to an exponential backoff with jitter.
|
34
|
+
base_delay: The base delay to use for the first attempt.
|
35
|
+
max_delay: The maximum delay to use for the last attempt.
|
36
|
+
retry_on_exceptions: A tuple of exception types to retry on. Defaults to
|
37
|
+
retrying on all exceptions.
|
38
|
+
"""
|
39
|
+
|
40
|
+
def decorator(func):
|
41
|
+
@wraps(func)
|
42
|
+
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
43
|
+
for attempt in range(max_attempts):
|
44
|
+
try:
|
45
|
+
return await func(*args, **kwargs)
|
46
|
+
except retry_on_exceptions as e:
|
47
|
+
if attempt == max_attempts - 1:
|
48
|
+
logger.exception(
|
49
|
+
f"Function {func.__name__!r} failed after {max_attempts} attempts"
|
50
|
+
)
|
51
|
+
raise
|
52
|
+
delay = backoff_strategy(attempt, base_delay, max_delay)
|
53
|
+
logger.warning(
|
54
|
+
f"Attempt {attempt + 1} of function {func.__name__!r} failed with {type(e).__name__}. "
|
55
|
+
f"Retrying in {delay:.2f} seconds..."
|
56
|
+
)
|
57
|
+
await asyncio.sleep(delay)
|
58
|
+
|
59
|
+
return wrapper
|
60
|
+
|
61
|
+
return decorator
|
prefect/agent.py
ADDED
prefect/client/cloud.py
CHANGED
@@ -9,7 +9,7 @@ from starlette import status
|
|
9
9
|
import prefect.context
|
10
10
|
import prefect.settings
|
11
11
|
from prefect.client.base import PrefectHttpxAsyncClient
|
12
|
-
from prefect.client.schemas import Workspace
|
12
|
+
from prefect.client.schemas.objects import Workspace
|
13
13
|
from prefect.exceptions import ObjectNotFound, PrefectException
|
14
14
|
from prefect.settings import (
|
15
15
|
PREFECT_API_KEY,
|
@@ -8,7 +8,6 @@ from typing import (
|
|
8
8
|
Generic,
|
9
9
|
List,
|
10
10
|
Optional,
|
11
|
-
TypeVar,
|
12
11
|
Union,
|
13
12
|
overload,
|
14
13
|
)
|
@@ -26,7 +25,7 @@ from pydantic import (
|
|
26
25
|
model_validator,
|
27
26
|
)
|
28
27
|
from pydantic_extra_types.pendulum_dt import DateTime
|
29
|
-
from typing_extensions import Literal, Self
|
28
|
+
from typing_extensions import Literal, Self, TypeVar
|
30
29
|
|
31
30
|
from prefect._internal.compatibility.migration import getattr_migration
|
32
31
|
from prefect._internal.schemas.bases import ObjectBaseModel, PrefectBaseModel
|
@@ -61,7 +60,7 @@ if TYPE_CHECKING:
|
|
61
60
|
from prefect.results import BaseResult
|
62
61
|
|
63
62
|
|
64
|
-
R = TypeVar("R")
|
63
|
+
R = TypeVar("R", default=Any)
|
65
64
|
|
66
65
|
|
67
66
|
DEFAULT_BLOCK_SCHEMA_VERSION = "non-versioned"
|
@@ -792,7 +791,7 @@ class TaskRun(ObjectBaseModel):
|
|
792
791
|
|
793
792
|
state: Optional[State] = Field(
|
794
793
|
default=None,
|
795
|
-
description="The state of the
|
794
|
+
description="The state of the task run.",
|
796
795
|
examples=["State(type=StateType.COMPLETED)"],
|
797
796
|
)
|
798
797
|
|
prefect/concurrency/asyncio.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
from contextlib import asynccontextmanager
|
3
|
-
from typing import List, Literal, Optional, Union, cast
|
3
|
+
from typing import AsyncGenerator, List, Literal, Optional, Union, cast
|
4
4
|
|
5
5
|
import httpx
|
6
6
|
import pendulum
|
@@ -34,7 +34,7 @@ async def concurrency(
|
|
34
34
|
names: Union[str, List[str]],
|
35
35
|
occupy: int = 1,
|
36
36
|
timeout_seconds: Optional[float] = None,
|
37
|
-
):
|
37
|
+
) -> AsyncGenerator[None, None]:
|
38
38
|
"""A context manager that acquires and releases concurrency slots from the
|
39
39
|
given concurrency limits.
|
40
40
|
|
@@ -77,7 +77,7 @@ async def concurrency(
|
|
77
77
|
_emit_concurrency_release_events(limits, occupy, emitted_events)
|
78
78
|
|
79
79
|
|
80
|
-
async def rate_limit(names: Union[str, List[str]], occupy: int = 1):
|
80
|
+
async def rate_limit(names: Union[str, List[str]], occupy: int = 1) -> None:
|
81
81
|
"""Block execution until an `occupy` number of slots of the concurrency
|
82
82
|
limits given in `names` are acquired. Requires that all given concurrency
|
83
83
|
limits have a slot decay.
|
prefect/concurrency/events.py
CHANGED
@@ -54,6 +54,6 @@ def _emit_concurrency_release_events(
|
|
54
54
|
limits: List[MinimalConcurrencyLimitResponse],
|
55
55
|
occupy: int,
|
56
56
|
events: Dict[UUID, Optional[Event]],
|
57
|
-
):
|
57
|
+
) -> None:
|
58
58
|
for limit in limits:
|
59
59
|
_emit_concurrency_event("released", limit, limits, occupy, events[limit.id])
|
prefect/concurrency/services.py
CHANGED
@@ -3,6 +3,7 @@ import concurrent.futures
|
|
3
3
|
from contextlib import asynccontextmanager
|
4
4
|
from typing import (
|
5
5
|
TYPE_CHECKING,
|
6
|
+
AsyncGenerator,
|
6
7
|
FrozenSet,
|
7
8
|
Optional,
|
8
9
|
Tuple,
|
@@ -27,14 +28,14 @@ class ConcurrencySlotAcquisitionService(QueueService):
|
|
27
28
|
self.concurrency_limit_names = sorted(list(concurrency_limit_names))
|
28
29
|
|
29
30
|
@asynccontextmanager
|
30
|
-
async def _lifespan(self):
|
31
|
+
async def _lifespan(self) -> AsyncGenerator[None, None]:
|
31
32
|
async with get_client() as client:
|
32
33
|
self._client = client
|
33
34
|
yield
|
34
35
|
|
35
36
|
async def _handle(
|
36
37
|
self, item: Tuple[int, str, Optional[float], concurrent.futures.Future]
|
37
|
-
):
|
38
|
+
) -> None:
|
38
39
|
occupy, mode, timeout_seconds, future = item
|
39
40
|
try:
|
40
41
|
response = await self.acquire_slots(occupy, mode, timeout_seconds)
|
prefect/concurrency/sync.py
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
from contextlib import contextmanager
|
2
|
-
from typing import
|
2
|
+
from typing import (
|
3
|
+
Any,
|
4
|
+
Awaitable,
|
5
|
+
Callable,
|
6
|
+
Generator,
|
7
|
+
List,
|
8
|
+
Optional,
|
9
|
+
TypeVar,
|
10
|
+
Union,
|
11
|
+
cast,
|
12
|
+
)
|
3
13
|
|
4
14
|
import pendulum
|
5
15
|
|
@@ -22,13 +32,15 @@ from .events import (
|
|
22
32
|
_emit_concurrency_release_events,
|
23
33
|
)
|
24
34
|
|
35
|
+
T = TypeVar("T")
|
36
|
+
|
25
37
|
|
26
38
|
@contextmanager
|
27
39
|
def concurrency(
|
28
40
|
names: Union[str, List[str]],
|
29
41
|
occupy: int = 1,
|
30
42
|
timeout_seconds: Optional[float] = None,
|
31
|
-
):
|
43
|
+
) -> Generator[None, None, None]:
|
32
44
|
"""A context manager that acquires and releases concurrency slots from the
|
33
45
|
given concurrency limits.
|
34
46
|
|
@@ -75,7 +87,7 @@ def concurrency(
|
|
75
87
|
_emit_concurrency_release_events(limits, occupy, emitted_events)
|
76
88
|
|
77
89
|
|
78
|
-
def rate_limit(names: Union[str, List[str]], occupy: int = 1):
|
90
|
+
def rate_limit(names: Union[str, List[str]], occupy: int = 1) -> None:
|
79
91
|
"""Block execution until an `occupy` number of slots of the concurrency
|
80
92
|
limits given in `names` are acquired. Requires that all given concurrency
|
81
93
|
limits have a slot decay.
|
@@ -91,11 +103,13 @@ def rate_limit(names: Union[str, List[str]], occupy: int = 1):
|
|
91
103
|
_emit_concurrency_acquisition_events(limits, occupy)
|
92
104
|
|
93
105
|
|
94
|
-
def _call_async_function_from_sync(
|
106
|
+
def _call_async_function_from_sync(
|
107
|
+
fn: Callable[..., Awaitable[T]], *args: Any, **kwargs: Any
|
108
|
+
) -> T:
|
95
109
|
loop = get_running_loop()
|
96
110
|
call = create_call(fn, *args, **kwargs)
|
97
111
|
|
98
112
|
if loop is not None:
|
99
113
|
return from_sync.call_soon_in_loop_thread(call).result()
|
100
114
|
else:
|
101
|
-
return call()
|
115
|
+
return call() # type: ignore [return-value]
|
prefect/context.py
CHANGED
@@ -9,6 +9,7 @@ For more user-accessible information about the current run, see [`prefect.runtim
|
|
9
9
|
import os
|
10
10
|
import sys
|
11
11
|
import warnings
|
12
|
+
import weakref
|
12
13
|
from contextlib import ExitStack, contextmanager
|
13
14
|
from contextvars import ContextVar, Token
|
14
15
|
from pathlib import Path
|
@@ -17,6 +18,7 @@ from typing import (
|
|
17
18
|
Any,
|
18
19
|
Dict,
|
19
20
|
Generator,
|
21
|
+
Mapping,
|
20
22
|
Optional,
|
21
23
|
Set,
|
22
24
|
Type,
|
@@ -32,6 +34,7 @@ from typing_extensions import Self
|
|
32
34
|
import prefect.logging
|
33
35
|
import prefect.logging.configuration
|
34
36
|
import prefect.settings
|
37
|
+
from prefect._internal.compatibility.migration import getattr_migration
|
35
38
|
from prefect.client.orchestration import PrefectClient, SyncPrefectClient, get_client
|
36
39
|
from prefect.client.schemas import FlowRun, TaskRun
|
37
40
|
from prefect.events.worker import EventsWorker
|
@@ -290,8 +293,12 @@ class EngineContext(RunContext):
|
|
290
293
|
# Counter for flow pauses
|
291
294
|
observed_flow_pauses: Dict[str, int] = Field(default_factory=dict)
|
292
295
|
|
293
|
-
# Tracking for result from task runs in this flow run
|
294
|
-
|
296
|
+
# Tracking for result from task runs in this flow run for dependency tracking
|
297
|
+
# Holds the ID of the object returned by the task run and task run state
|
298
|
+
# This is a weakref dictionary to avoid undermining garbage collection
|
299
|
+
task_run_results: Mapping[int, State] = Field(
|
300
|
+
default_factory=weakref.WeakValueDictionary
|
301
|
+
)
|
295
302
|
|
296
303
|
# Events worker to emit events to Prefect Cloud
|
297
304
|
events: Optional[EventsWorker] = None
|
@@ -608,3 +615,8 @@ def root_settings_context():
|
|
608
615
|
|
609
616
|
|
610
617
|
GLOBAL_SETTINGS_CONTEXT: SettingsContext = root_settings_context()
|
618
|
+
|
619
|
+
|
620
|
+
# 2024-07-02: This surfaces an actionable error message for removed objects
|
621
|
+
# in Prefect 3.0 upgrade.
|
622
|
+
__getattr__ = getattr_migration(__name__)
|
prefect/deployments/__init__.py
CHANGED
@@ -1,20 +1,33 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
1
2
|
from prefect._internal.compatibility.migration import getattr_migration
|
2
|
-
import prefect.deployments.base
|
3
|
-
import prefect.deployments.steps
|
4
|
-
from prefect.deployments.base import (
|
5
|
-
initialize_project,
|
6
|
-
)
|
7
3
|
|
8
|
-
from prefect.deployments.runner import (
|
9
|
-
RunnerDeployment,
|
10
|
-
deploy,
|
11
|
-
DockerImage,
|
12
|
-
EntrypointType,
|
13
|
-
)
|
14
4
|
|
5
|
+
if TYPE_CHECKING:
|
6
|
+
from .flow_runs import run_deployment
|
7
|
+
from .base import initialize_project
|
8
|
+
from .runner import deploy
|
15
9
|
|
16
|
-
|
17
|
-
|
18
|
-
)
|
10
|
+
_public_api: dict[str, tuple[str, str]] = {
|
11
|
+
"initialize_project": (__spec__.parent, ".base"),
|
12
|
+
"run_deployment": (__spec__.parent, ".flow_runs"),
|
13
|
+
"deploy": (__spec__.parent, ".runner"),
|
14
|
+
}
|
19
15
|
|
20
|
-
|
16
|
+
# Declare API for type-checkers
|
17
|
+
__all__ = ["initialize_project", "deploy", "run_deployment"]
|
18
|
+
|
19
|
+
|
20
|
+
def __getattr__(attr_name: str) -> object:
|
21
|
+
dynamic_attr = _public_api.get(attr_name)
|
22
|
+
if dynamic_attr is None:
|
23
|
+
return getattr_migration(__name__)(attr_name)
|
24
|
+
|
25
|
+
package, module_name = dynamic_attr
|
26
|
+
|
27
|
+
from importlib import import_module
|
28
|
+
|
29
|
+
if module_name == "__module__":
|
30
|
+
return import_module(f".{attr_name}", package=package)
|
31
|
+
else:
|
32
|
+
module = import_module(module_name, package=package)
|
33
|
+
return getattr(module, attr_name)
|
prefect/deployments/schedules.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
-
from typing import TYPE_CHECKING, Any, List, Optional
|
1
|
+
from typing import TYPE_CHECKING, Any, List, Optional, Sequence, Union
|
2
2
|
|
3
3
|
from prefect.client.schemas.actions import DeploymentScheduleCreate
|
4
4
|
from prefect.client.schemas.schedules import is_schedule_type
|
5
5
|
|
6
6
|
if TYPE_CHECKING:
|
7
7
|
from prefect.client.schemas.schedules import SCHEDULE_TYPES
|
8
|
-
|
8
|
+
|
9
|
+
FlexibleScheduleList = Sequence[
|
10
|
+
Union[DeploymentScheduleCreate, dict[str, Any], "SCHEDULE_TYPES"]
|
11
|
+
]
|
9
12
|
|
10
13
|
|
11
14
|
def create_deployment_schedule_create(
|
@@ -6,6 +6,7 @@ import os
|
|
6
6
|
from pathlib import Path
|
7
7
|
from typing import TYPE_CHECKING, Any, Optional
|
8
8
|
|
9
|
+
from prefect._internal.retries import retry_async_fn
|
9
10
|
from prefect.logging.loggers import get_logger
|
10
11
|
from prefect.runner.storage import BlockStorageAdapter, GitRepository, RemoteStorage
|
11
12
|
from prefect.utilities.asyncutils import sync_compatible
|
@@ -31,6 +32,12 @@ def set_working_directory(directory: str) -> dict:
|
|
31
32
|
return dict(directory=directory)
|
32
33
|
|
33
34
|
|
35
|
+
@retry_async_fn(
|
36
|
+
max_attempts=3,
|
37
|
+
base_delay=1,
|
38
|
+
max_delay=10,
|
39
|
+
retry_on_exceptions=(RuntimeError,),
|
40
|
+
)
|
34
41
|
@sync_compatible
|
35
42
|
async def git_clone(
|
36
43
|
repository: str,
|
@@ -187,18 +187,18 @@ class EventTrigger(ResourceTrigger):
|
|
187
187
|
within: Optional[timedelta] = data.get("within")
|
188
188
|
|
189
189
|
if isinstance(within, (int, float)):
|
190
|
-
|
190
|
+
within = timedelta(seconds=within)
|
191
191
|
|
192
192
|
if posture == Posture.Proactive:
|
193
193
|
if not within or within == timedelta(0):
|
194
|
-
|
194
|
+
within = timedelta(seconds=10.0)
|
195
195
|
elif within < timedelta(seconds=10.0):
|
196
196
|
raise ValueError(
|
197
197
|
"`within` for Proactive triggers must be greater than or equal to "
|
198
198
|
"10 seconds"
|
199
199
|
)
|
200
200
|
|
201
|
-
return data
|
201
|
+
return data | {"within": within} if within else data
|
202
202
|
|
203
203
|
def describe_for_cli(self, indent: int = 0) -> str:
|
204
204
|
"""Return a human-readable description of this trigger for the CLI"""
|
prefect/exceptions.py
CHANGED
@@ -178,7 +178,10 @@ class ParameterTypeError(PrefectException):
|
|
178
178
|
|
179
179
|
@classmethod
|
180
180
|
def from_validation_error(cls, exc: ValidationError) -> Self:
|
181
|
-
bad_params = [
|
181
|
+
bad_params = [
|
182
|
+
f'{".".join(str(item) for item in err["loc"])}: {err["msg"]}'
|
183
|
+
for err in exc.errors()
|
184
|
+
]
|
182
185
|
msg = "Flow run received invalid parameters:\n - " + "\n - ".join(bad_params)
|
183
186
|
return cls(msg)
|
184
187
|
|
prefect/filesystems.py
CHANGED
@@ -95,7 +95,7 @@ class LocalFileSystem(WritableFileSystem, WritableDeploymentStorage):
|
|
95
95
|
def cast_pathlib(cls, value):
|
96
96
|
return stringify_path(value)
|
97
97
|
|
98
|
-
def _resolve_path(self, path: str) -> Path:
|
98
|
+
def _resolve_path(self, path: str, validate: bool = False) -> Path:
|
99
99
|
# Only resolve the base path at runtime, default to the current directory
|
100
100
|
basepath = (
|
101
101
|
Path(self.basepath).expanduser().resolve()
|
@@ -114,11 +114,12 @@ class LocalFileSystem(WritableFileSystem, WritableDeploymentStorage):
|
|
114
114
|
resolved_path = basepath / resolved_path
|
115
115
|
else:
|
116
116
|
resolved_path = resolved_path.resolve()
|
117
|
+
|
118
|
+
if validate:
|
117
119
|
if basepath not in resolved_path.parents and (basepath != resolved_path):
|
118
120
|
raise ValueError(
|
119
121
|
f"Provided path {resolved_path} is outside of the base path {basepath}."
|
120
122
|
)
|
121
|
-
|
122
123
|
return resolved_path
|
123
124
|
|
124
125
|
@sync_compatible
|
@@ -184,7 +185,7 @@ class LocalFileSystem(WritableFileSystem, WritableDeploymentStorage):
|
|
184
185
|
Defaults to copying the entire contents of the current working directory to the block's basepath.
|
185
186
|
An `ignore_file` path may be provided that can include gitignore style expressions for filepaths to ignore.
|
186
187
|
"""
|
187
|
-
destination_path = self._resolve_path(to_path)
|
188
|
+
destination_path = self._resolve_path(to_path, validate=True)
|
188
189
|
|
189
190
|
if not local_path:
|
190
191
|
local_path = Path(".").absolute()
|