prefect-client 3.0.3__py3-none-any.whl → 3.0.5__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/retries.py +1 -3
- prefect/_internal/schemas/validators.py +1 -1
- prefect/cache_policies.py +1 -1
- prefect/client/cloud.py +9 -0
- prefect/client/orchestration.py +30 -2
- prefect/client/schemas/objects.py +17 -2
- prefect/client/subscriptions.py +3 -3
- prefect/context.py +11 -19
- prefect/deployments/base.py +12 -0
- prefect/deployments/flow_runs.py +8 -0
- prefect/events/clients.py +40 -22
- prefect/exceptions.py +22 -3
- prefect/filesystems.py +26 -1
- prefect/flow_engine.py +10 -7
- prefect/flows.py +11 -2
- prefect/logging/configuration.py +4 -8
- prefect/logging/handlers.py +3 -4
- prefect/results.py +73 -7
- prefect/runner/runner.py +5 -37
- prefect/settings.py +1364 -1582
- prefect/transactions.py +9 -9
- prefect/types/__init__.py +51 -1
- prefect/utilities/pydantic.py +2 -1
- prefect/utilities/text.py +13 -1
- prefect/workers/base.py +7 -56
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/METADATA +1 -1
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/RECORD +30 -31
- prefect/_internal/compatibility/experimental.py +0 -195
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/LICENSE +0 -0
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/WHEEL +0 -0
- {prefect_client-3.0.3.dist-info → prefect_client-3.0.5.dist-info}/top_level.txt +0 -0
prefect/_internal/retries.py
CHANGED
@@ -2,11 +2,9 @@ import asyncio
|
|
2
2
|
from functools import wraps
|
3
3
|
from typing import Any, Callable, Tuple, Type
|
4
4
|
|
5
|
-
from prefect.
|
5
|
+
from prefect._internal._logging import logger
|
6
6
|
from prefect.utilities.math import clamped_poisson_interval
|
7
7
|
|
8
|
-
logger = get_logger("retries")
|
9
|
-
|
10
8
|
|
11
9
|
def exponential_backoff_with_jitter(
|
12
10
|
attempt: int, base_delay: float, max_delay: float
|
@@ -70,7 +70,7 @@ def validate_schema(schema: dict):
|
|
70
70
|
try:
|
71
71
|
if schema is not None:
|
72
72
|
# Most closely matches the schemas generated by pydantic
|
73
|
-
jsonschema.
|
73
|
+
jsonschema.Draft202012Validator.check_schema(schema)
|
74
74
|
except jsonschema.SchemaError as exc:
|
75
75
|
raise ValueError(
|
76
76
|
"The provided schema is not a valid json schema. Schema error:"
|
prefect/cache_policies.py
CHANGED
@@ -219,7 +219,7 @@ class TaskSource(CachePolicy):
|
|
219
219
|
except TypeError:
|
220
220
|
lines = inspect.getsource(task_ctx.task.fn.__class__)
|
221
221
|
except OSError as exc:
|
222
|
-
if "
|
222
|
+
if "source code" in str(exc):
|
223
223
|
lines = task_ctx.task.fn.__code__.co_code
|
224
224
|
else:
|
225
225
|
raise
|
prefect/client/cloud.py
CHANGED
@@ -17,6 +17,7 @@ from prefect.client.schemas.objects import (
|
|
17
17
|
from prefect.exceptions import ObjectNotFound, PrefectException
|
18
18
|
from prefect.settings import (
|
19
19
|
PREFECT_API_KEY,
|
20
|
+
PREFECT_API_URL,
|
20
21
|
PREFECT_CLOUD_API_URL,
|
21
22
|
PREFECT_UNIT_TEST_MODE,
|
22
23
|
)
|
@@ -110,6 +111,14 @@ class CloudClient:
|
|
110
111
|
)
|
111
112
|
return workspaces
|
112
113
|
|
114
|
+
async def read_current_workspace(self) -> Workspace:
|
115
|
+
workspaces = await self.read_workspaces()
|
116
|
+
current_api_url = PREFECT_API_URL.value()
|
117
|
+
for workspace in workspaces:
|
118
|
+
if workspace.api_url() == current_api_url.rstrip("/"):
|
119
|
+
return workspace
|
120
|
+
raise ValueError("Current workspace not found")
|
121
|
+
|
113
122
|
async def read_worker_metadata(self) -> Dict[str, Any]:
|
114
123
|
response = await self.get(
|
115
124
|
f"{self.workspace_base_url}/collections/work_pool_types"
|
prefect/client/orchestration.py
CHANGED
@@ -1813,7 +1813,35 @@ class PrefectClient:
|
|
1813
1813
|
response = await self._client.get(f"/deployments/name/{name}")
|
1814
1814
|
except httpx.HTTPStatusError as e:
|
1815
1815
|
if e.response.status_code == status.HTTP_404_NOT_FOUND:
|
1816
|
-
|
1816
|
+
from prefect.utilities.text import fuzzy_match_string
|
1817
|
+
|
1818
|
+
deployments = await self.read_deployments()
|
1819
|
+
flow_name_map = {
|
1820
|
+
flow.id: flow.name
|
1821
|
+
for flow in await asyncio.gather(
|
1822
|
+
*[
|
1823
|
+
self.read_flow(flow_id)
|
1824
|
+
for flow_id in {d.flow_id for d in deployments}
|
1825
|
+
]
|
1826
|
+
)
|
1827
|
+
}
|
1828
|
+
|
1829
|
+
raise prefect.exceptions.ObjectNotFound(
|
1830
|
+
http_exc=e,
|
1831
|
+
help_message=(
|
1832
|
+
f"Deployment {name!r} not found; did you mean {fuzzy_match!r}?"
|
1833
|
+
if (
|
1834
|
+
fuzzy_match := fuzzy_match_string(
|
1835
|
+
name,
|
1836
|
+
[
|
1837
|
+
f"{flow_name_map[d.flow_id]}/{d.name}"
|
1838
|
+
for d in deployments
|
1839
|
+
],
|
1840
|
+
)
|
1841
|
+
)
|
1842
|
+
else f"Deployment {name!r} not found. Try `prefect deployment ls` to find available deployments."
|
1843
|
+
),
|
1844
|
+
) from e
|
1817
1845
|
else:
|
1818
1846
|
raise
|
1819
1847
|
|
@@ -2543,7 +2571,7 @@ class PrefectClient:
|
|
2543
2571
|
|
2544
2572
|
async def read_logs(
|
2545
2573
|
self,
|
2546
|
-
log_filter: LogFilter = None,
|
2574
|
+
log_filter: Optional[LogFilter] = None,
|
2547
2575
|
limit: Optional[int] = None,
|
2548
2576
|
offset: Optional[int] = None,
|
2549
2577
|
sort: LogSort = LogSort.TIMESTAMP_ASC,
|
@@ -231,7 +231,9 @@ class State(ObjectBaseModel, Generic[R]):
|
|
231
231
|
|
232
232
|
Args:
|
233
233
|
raise_on_failure: a boolean specifying whether to raise an exception
|
234
|
-
if the state is of type `FAILED` and the underlying data is an exception
|
234
|
+
if the state is of type `FAILED` and the underlying data is an exception. When flow
|
235
|
+
was run in a different memory space (using `run_deployment`), this will only raise
|
236
|
+
if `fetch` is `True`.
|
235
237
|
fetch: a boolean specifying whether to resolve references to persisted
|
236
238
|
results into data. For synchronous users, this defaults to `True`.
|
237
239
|
For asynchronous users, this defaults to `False` for backwards
|
@@ -297,6 +299,15 @@ class State(ObjectBaseModel, Generic[R]):
|
|
297
299
|
>>> state = await my_flow(return_state=True)
|
298
300
|
>>> await state.result()
|
299
301
|
hello
|
302
|
+
|
303
|
+
Get the result with `raise_on_failure` from a flow run in a different memory space
|
304
|
+
|
305
|
+
>>> @flow
|
306
|
+
>>> async def my_flow():
|
307
|
+
>>> raise ValueError("oh no!")
|
308
|
+
>>> my_flow.deploy("my_deployment/my_flow")
|
309
|
+
>>> flow_run = run_deployment("my_deployment/my_flow")
|
310
|
+
>>> await flow_run.state.result(raise_on_failure=True, fetch=True) # Raises `ValueError("oh no!")`
|
300
311
|
"""
|
301
312
|
from prefect.states import get_state_result
|
302
313
|
|
@@ -316,7 +327,11 @@ class State(ObjectBaseModel, Generic[R]):
|
|
316
327
|
results should be sent to the API. Other data is only available locally.
|
317
328
|
"""
|
318
329
|
from prefect.client.schemas.actions import StateCreate
|
319
|
-
from prefect.results import
|
330
|
+
from prefect.results import (
|
331
|
+
BaseResult,
|
332
|
+
ResultRecord,
|
333
|
+
should_persist_result,
|
334
|
+
)
|
320
335
|
|
321
336
|
if isinstance(self.data, BaseResult):
|
322
337
|
data = self.data
|
prefect/client/subscriptions.py
CHANGED
@@ -84,13 +84,13 @@ class Subscription(Generic[S]):
|
|
84
84
|
AssertionError,
|
85
85
|
websockets.exceptions.ConnectionClosedError,
|
86
86
|
) as e:
|
87
|
-
if isinstance(e, AssertionError) or e.code == WS_1008_POLICY_VIOLATION:
|
87
|
+
if isinstance(e, AssertionError) or e.rcvd.code == WS_1008_POLICY_VIOLATION:
|
88
88
|
if isinstance(e, AssertionError):
|
89
89
|
reason = e.args[0]
|
90
90
|
elif isinstance(e, websockets.exceptions.ConnectionClosedError):
|
91
|
-
reason = e.reason
|
91
|
+
reason = e.rcvd.reason
|
92
92
|
|
93
|
-
if isinstance(e, AssertionError) or e.code == WS_1008_POLICY_VIOLATION:
|
93
|
+
if isinstance(e, AssertionError) or e.rcvd.code == WS_1008_POLICY_VIOLATION:
|
94
94
|
raise Exception(
|
95
95
|
"Unable to authenticate to the subscription. Please "
|
96
96
|
"ensure the provided `PREFECT_API_KEY` you are using is "
|
prefect/context.py
CHANGED
@@ -11,7 +11,6 @@ import sys
|
|
11
11
|
import warnings
|
12
12
|
from contextlib import ExitStack, asynccontextmanager, contextmanager
|
13
13
|
from contextvars import ContextVar, Token
|
14
|
-
from pathlib import Path
|
15
14
|
from typing import (
|
16
15
|
TYPE_CHECKING,
|
17
16
|
Any,
|
@@ -40,7 +39,7 @@ from prefect.client.schemas import FlowRun, TaskRun
|
|
40
39
|
from prefect.events.worker import EventsWorker
|
41
40
|
from prefect.exceptions import MissingContextError
|
42
41
|
from prefect.results import ResultStore, get_default_persist_setting
|
43
|
-
from prefect.settings import
|
42
|
+
from prefect.settings import Profile, Settings
|
44
43
|
from prefect.states import State
|
45
44
|
from prefect.task_runners import TaskRunner
|
46
45
|
from prefect.utilities.services import start_client_metrics_server
|
@@ -166,11 +165,13 @@ class ContextModel(BaseModel):
|
|
166
165
|
new._token = None
|
167
166
|
return new
|
168
167
|
|
169
|
-
def serialize(self) -> Dict[str, Any]:
|
168
|
+
def serialize(self, include_secrets: bool = True) -> Dict[str, Any]:
|
170
169
|
"""
|
171
170
|
Serialize the context model to a dictionary that can be pickled with cloudpickle.
|
172
171
|
"""
|
173
|
-
return self.model_dump(
|
172
|
+
return self.model_dump(
|
173
|
+
exclude_unset=True, context={"include_secrets": include_secrets}
|
174
|
+
)
|
174
175
|
|
175
176
|
|
176
177
|
class SyncClientContext(ContextModel):
|
@@ -430,7 +431,7 @@ class TagsContext(ContextModel):
|
|
430
431
|
# Return an empty `TagsContext` instead of `None` if no context exists
|
431
432
|
return cls.__var__.get(TagsContext())
|
432
433
|
|
433
|
-
__var__ = ContextVar("tags")
|
434
|
+
__var__: ContextVar = ContextVar("tags")
|
434
435
|
|
435
436
|
|
436
437
|
class SettingsContext(ContextModel):
|
@@ -447,7 +448,7 @@ class SettingsContext(ContextModel):
|
|
447
448
|
profile: Profile
|
448
449
|
settings: Settings
|
449
450
|
|
450
|
-
__var__ = ContextVar("settings")
|
451
|
+
__var__: ContextVar = ContextVar("settings")
|
451
452
|
|
452
453
|
def __hash__(self) -> int:
|
453
454
|
return hash(self.settings)
|
@@ -459,14 +460,11 @@ class SettingsContext(ContextModel):
|
|
459
460
|
return_value = super().__enter__()
|
460
461
|
|
461
462
|
try:
|
462
|
-
prefect_home =
|
463
|
+
prefect_home = self.settings.home
|
463
464
|
prefect_home.mkdir(mode=0o0700, exist_ok=True)
|
464
465
|
except OSError:
|
465
466
|
warnings.warn(
|
466
|
-
(
|
467
|
-
"Failed to create the Prefect home directory at "
|
468
|
-
f"{self.settings.value_of(PREFECT_HOME)}"
|
469
|
-
),
|
467
|
+
(f"Failed to create the Prefect home directory at {prefect_home}"),
|
470
468
|
stacklevel=2,
|
471
469
|
)
|
472
470
|
|
@@ -609,12 +607,11 @@ def use_profile(
|
|
609
607
|
|
610
608
|
# Create a copy of the profiles settings as we will mutate it
|
611
609
|
profile_settings = profile.settings.copy()
|
612
|
-
|
613
610
|
existing_context = SettingsContext.get()
|
614
611
|
if existing_context and include_current_context:
|
615
612
|
settings = existing_context.settings
|
616
613
|
else:
|
617
|
-
settings =
|
614
|
+
settings = Settings()
|
618
615
|
|
619
616
|
if not override_environment_variables:
|
620
617
|
for key in os.environ:
|
@@ -662,12 +659,7 @@ def root_settings_context():
|
|
662
659
|
)
|
663
660
|
active_name = "ephemeral"
|
664
661
|
|
665
|
-
|
666
|
-
profiles[active_name],
|
667
|
-
# Override environment variables if the profile was set by the CLI
|
668
|
-
override_environment_variables=profile_source == "by command line argument",
|
669
|
-
) as settings_context:
|
670
|
-
return settings_context
|
662
|
+
return SettingsContext(profile=profiles[active_name], settings=Settings())
|
671
663
|
|
672
664
|
# Note the above context is exited and the global settings context is used by
|
673
665
|
# an override in the `SettingsContext.get` method.
|
prefect/deployments/base.py
CHANGED
@@ -20,6 +20,7 @@ import yaml
|
|
20
20
|
from ruamel.yaml import YAML
|
21
21
|
|
22
22
|
from prefect.client.schemas.actions import DeploymentScheduleCreate
|
23
|
+
from prefect.client.schemas.objects import ConcurrencyLimitStrategy
|
23
24
|
from prefect.client.schemas.schedules import IntervalSchedule
|
24
25
|
from prefect.logging import get_logger
|
25
26
|
from prefect.settings import PREFECT_DEBUG_MODE
|
@@ -277,6 +278,17 @@ def _format_deployment_for_saving_to_prefect_file(
|
|
277
278
|
|
278
279
|
deployment["schedules"] = schedules
|
279
280
|
|
281
|
+
if deployment.get("concurrency_limit"):
|
282
|
+
concurrency_limit = deployment["concurrency_limit"]
|
283
|
+
if isinstance(concurrency_limit, dict):
|
284
|
+
if isinstance(
|
285
|
+
concurrency_limit["collision_strategy"], ConcurrencyLimitStrategy
|
286
|
+
):
|
287
|
+
concurrency_limit["collision_strategy"] = str(
|
288
|
+
concurrency_limit["collision_strategy"].value
|
289
|
+
)
|
290
|
+
deployment["concurrency_limit"] = concurrency_limit
|
291
|
+
|
280
292
|
return deployment
|
281
293
|
|
282
294
|
|
prefect/deployments/flow_runs.py
CHANGED
@@ -5,10 +5,12 @@ from uuid import UUID
|
|
5
5
|
import anyio
|
6
6
|
import pendulum
|
7
7
|
|
8
|
+
import prefect
|
8
9
|
from prefect.client.schemas import FlowRun
|
9
10
|
from prefect.client.utilities import inject_client
|
10
11
|
from prefect.context import FlowRunContext, TaskRunContext
|
11
12
|
from prefect.logging import get_logger
|
13
|
+
from prefect.results import BaseResult, ResultRecordMetadata
|
12
14
|
from prefect.states import Pending, Scheduled
|
13
15
|
from prefect.tasks import Task
|
14
16
|
from prefect.utilities.asyncutils import sync_compatible
|
@@ -18,6 +20,12 @@ if TYPE_CHECKING:
|
|
18
20
|
from prefect.client.orchestration import PrefectClient
|
19
21
|
from prefect.client.schemas.objects import FlowRun
|
20
22
|
|
23
|
+
prefect.client.schemas.StateCreate.model_rebuild(
|
24
|
+
_types_namespace={
|
25
|
+
"BaseResult": BaseResult,
|
26
|
+
"ResultRecordMetadata": ResultRecordMetadata,
|
27
|
+
}
|
28
|
+
)
|
21
29
|
|
22
30
|
logger = get_logger(__name__)
|
23
31
|
|
prefect/events/clients.py
CHANGED
@@ -34,6 +34,7 @@ from prefect.settings import (
|
|
34
34
|
PREFECT_API_KEY,
|
35
35
|
PREFECT_API_URL,
|
36
36
|
PREFECT_CLOUD_API_URL,
|
37
|
+
PREFECT_DEBUG_MODE,
|
37
38
|
PREFECT_SERVER_ALLOW_EPHEMERAL_MODE,
|
38
39
|
)
|
39
40
|
|
@@ -67,6 +68,18 @@ EVENT_WEBSOCKET_CHECKPOINTS = Counter(
|
|
67
68
|
logger = get_logger(__name__)
|
68
69
|
|
69
70
|
|
71
|
+
def http_to_ws(url: str):
|
72
|
+
return url.replace("https://", "wss://").replace("http://", "ws://").rstrip("/")
|
73
|
+
|
74
|
+
|
75
|
+
def events_in_socket_from_api_url(url: str):
|
76
|
+
return http_to_ws(url) + "/events/in"
|
77
|
+
|
78
|
+
|
79
|
+
def events_out_socket_from_api_url(url: str):
|
80
|
+
return http_to_ws(url) + "/events/out"
|
81
|
+
|
82
|
+
|
70
83
|
def get_events_client(
|
71
84
|
reconnection_attempts: int = 10,
|
72
85
|
checkpoint_every: int = 700,
|
@@ -251,12 +264,7 @@ class PrefectEventsClient(EventsClient):
|
|
251
264
|
"api_url must be provided or set in the Prefect configuration"
|
252
265
|
)
|
253
266
|
|
254
|
-
self._events_socket_url = (
|
255
|
-
api_url.replace("https://", "wss://")
|
256
|
-
.replace("http://", "ws://")
|
257
|
-
.rstrip("/")
|
258
|
-
+ "/events/in"
|
259
|
-
)
|
267
|
+
self._events_socket_url = events_in_socket_from_api_url(api_url)
|
260
268
|
self._connect = connect(self._events_socket_url)
|
261
269
|
self._websocket = None
|
262
270
|
self._reconnection_attempts = reconnection_attempts
|
@@ -285,11 +293,26 @@ class PrefectEventsClient(EventsClient):
|
|
285
293
|
self._websocket = None
|
286
294
|
await self._connect.__aexit__(None, None, None)
|
287
295
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
296
|
+
try:
|
297
|
+
self._websocket = await self._connect.__aenter__()
|
298
|
+
# make sure we have actually connected
|
299
|
+
pong = await self._websocket.ping()
|
300
|
+
await pong
|
301
|
+
except Exception as e:
|
302
|
+
# The client is frequently run in a background thread
|
303
|
+
# so we log an additional warning to ensure
|
304
|
+
# surfacing the error to the user.
|
305
|
+
logger.warning(
|
306
|
+
"Unable to connect to %r. "
|
307
|
+
"Please check your network settings to ensure websocket connections "
|
308
|
+
"to the API are allowed. Otherwise event data (including task run data) may be lost. "
|
309
|
+
"Reason: %s. "
|
310
|
+
"Set PREFECT_DEBUG_MODE=1 to see the full error.",
|
311
|
+
self._events_socket_url,
|
312
|
+
str(e),
|
313
|
+
exc_info=PREFECT_DEBUG_MODE,
|
314
|
+
)
|
315
|
+
raise
|
293
316
|
|
294
317
|
events_to_resend = self._unconfirmed_events
|
295
318
|
# Clear the unconfirmed events here, because they are going back through emit
|
@@ -412,7 +435,6 @@ class PrefectCloudEventsClient(PrefectEventsClient):
|
|
412
435
|
reconnection_attempts=reconnection_attempts,
|
413
436
|
checkpoint_every=checkpoint_every,
|
414
437
|
)
|
415
|
-
|
416
438
|
self._connect = connect(
|
417
439
|
self._events_socket_url,
|
418
440
|
extra_headers={"Authorization": f"bearer {api_key}"},
|
@@ -468,11 +490,7 @@ class PrefectEventSubscriber:
|
|
468
490
|
self._filter = filter or EventFilter() # type: ignore[call-arg]
|
469
491
|
self._seen_events = TTLCache(maxsize=SEEN_EVENTS_SIZE, ttl=SEEN_EVENTS_TTL)
|
470
492
|
|
471
|
-
socket_url = (
|
472
|
-
api_url.replace("https://", "wss://")
|
473
|
-
.replace("http://", "ws://")
|
474
|
-
.rstrip("/")
|
475
|
-
) + "/events/out"
|
493
|
+
socket_url = events_out_socket_from_api_url(api_url)
|
476
494
|
|
477
495
|
logger.debug("Connecting to %s", socket_url)
|
478
496
|
|
@@ -527,11 +545,11 @@ class PrefectEventSubscriber:
|
|
527
545
|
f"Reason: {e.args[0]}"
|
528
546
|
)
|
529
547
|
except ConnectionClosedError as e:
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
) from e
|
548
|
+
reason = getattr(e.rcvd, "reason", None)
|
549
|
+
msg = "Unable to authenticate to the event stream. Please ensure the "
|
550
|
+
msg += "provided api_key you are using is valid for this environment. "
|
551
|
+
msg += f"Reason: {reason}" if reason else ""
|
552
|
+
raise Exception(msg) from e
|
535
553
|
|
536
554
|
from prefect.events.filters import EventOccurredFilter
|
537
555
|
|
prefect/exceptions.py
CHANGED
@@ -5,7 +5,7 @@ Prefect-specific exceptions.
|
|
5
5
|
import inspect
|
6
6
|
import traceback
|
7
7
|
from types import ModuleType, TracebackType
|
8
|
-
from typing import Callable, Dict, Iterable, List, Optional, Type
|
8
|
+
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type
|
9
9
|
|
10
10
|
from httpx._exceptions import HTTPStatusError
|
11
11
|
from pydantic import ValidationError
|
@@ -227,9 +227,19 @@ class ObjectNotFound(PrefectException):
|
|
227
227
|
Raised when the client receives a 404 (not found) from the API.
|
228
228
|
"""
|
229
229
|
|
230
|
-
def __init__(
|
230
|
+
def __init__(
|
231
|
+
self,
|
232
|
+
http_exc: Exception,
|
233
|
+
help_message: Optional[str] = None,
|
234
|
+
*args,
|
235
|
+
**kwargs,
|
236
|
+
):
|
231
237
|
self.http_exc = http_exc
|
232
|
-
|
238
|
+
self.help_message = help_message
|
239
|
+
super().__init__(help_message, *args, **kwargs)
|
240
|
+
|
241
|
+
def __str__(self):
|
242
|
+
return self.help_message or super().__str__()
|
233
243
|
|
234
244
|
|
235
245
|
class ObjectAlreadyExists(PrefectException):
|
@@ -424,3 +434,12 @@ class ConfigurationError(PrefectException):
|
|
424
434
|
"""
|
425
435
|
Raised when a configuration is invalid.
|
426
436
|
"""
|
437
|
+
|
438
|
+
|
439
|
+
class ProfileSettingsValidationError(PrefectException):
|
440
|
+
"""
|
441
|
+
Raised when a profile settings are invalid.
|
442
|
+
"""
|
443
|
+
|
444
|
+
def __init__(self, errors: List[Tuple[Any, ValidationError]]) -> None:
|
445
|
+
self.errors = errors
|
prefect/filesystems.py
CHANGED
@@ -5,7 +5,7 @@ from typing import Any, Dict, Optional
|
|
5
5
|
|
6
6
|
import anyio
|
7
7
|
import fsspec
|
8
|
-
from pydantic import Field, SecretStr, field_validator
|
8
|
+
from pydantic import BaseModel, Field, SecretStr, field_validator
|
9
9
|
|
10
10
|
from prefect._internal.schemas.validators import (
|
11
11
|
stringify_path,
|
@@ -519,4 +519,29 @@ class SMB(WritableFileSystem, WritableDeploymentStorage):
|
|
519
519
|
return await self.filesystem.write_path(path=path, content=content)
|
520
520
|
|
521
521
|
|
522
|
+
class NullFileSystem(BaseModel):
|
523
|
+
"""
|
524
|
+
A file system that does not store any data.
|
525
|
+
"""
|
526
|
+
|
527
|
+
async def read_path(self, path: str) -> None:
|
528
|
+
pass
|
529
|
+
|
530
|
+
async def write_path(self, path: str, content: bytes) -> None:
|
531
|
+
pass
|
532
|
+
|
533
|
+
async def get_directory(
|
534
|
+
self, from_path: Optional[str] = None, local_path: Optional[str] = None
|
535
|
+
) -> None:
|
536
|
+
pass
|
537
|
+
|
538
|
+
async def put_directory(
|
539
|
+
self,
|
540
|
+
local_path: Optional[str] = None,
|
541
|
+
to_path: Optional[str] = None,
|
542
|
+
ignore_file: Optional[str] = None,
|
543
|
+
) -> None:
|
544
|
+
pass
|
545
|
+
|
546
|
+
|
522
547
|
__getattr__ = getattr_migration(__name__)
|
prefect/flow_engine.py
CHANGED
@@ -290,13 +290,16 @@ class FlowRunEngine(Generic[P, R]):
|
|
290
290
|
result_store: Optional[ResultStore] = None,
|
291
291
|
) -> State:
|
292
292
|
context = FlowRunContext.get()
|
293
|
-
terminal_state =
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
293
|
+
terminal_state = cast(
|
294
|
+
State,
|
295
|
+
run_coro_as_sync(
|
296
|
+
exception_to_failed_state(
|
297
|
+
exc,
|
298
|
+
message=msg or "Flow run encountered an exception:",
|
299
|
+
result_store=result_store or getattr(context, "result_store", None),
|
300
|
+
write_result=True,
|
301
|
+
)
|
302
|
+
),
|
300
303
|
)
|
301
304
|
state = self.set_state(terminal_state)
|
302
305
|
if self.state.is_scheduled():
|
prefect/flows.py
CHANGED
@@ -535,7 +535,7 @@ class Flow(Generic[P, R]):
|
|
535
535
|
|
536
536
|
def resolve_block_reference(data: Any) -> Any:
|
537
537
|
if isinstance(data, dict) and "$ref" in data:
|
538
|
-
return Block.load_from_ref(data["$ref"])
|
538
|
+
return Block.load_from_ref(data["$ref"], _sync=True)
|
539
539
|
return data
|
540
540
|
|
541
541
|
try:
|
@@ -593,7 +593,7 @@ class Flow(Generic[P, R]):
|
|
593
593
|
# Get the updated parameter dict with cast values from the model
|
594
594
|
cast_parameters = {
|
595
595
|
k: v
|
596
|
-
for k, v in
|
596
|
+
for k, v in model.__dict__.items()
|
597
597
|
if k in model.model_fields_set or model.model_fields[k].default_factory
|
598
598
|
}
|
599
599
|
return cast_parameters
|
@@ -1256,6 +1256,15 @@ class Flow(Generic[P, R]):
|
|
1256
1256
|
) -> T:
|
1257
1257
|
...
|
1258
1258
|
|
1259
|
+
@overload
|
1260
|
+
def __call__(
|
1261
|
+
self: "Flow[P, Coroutine[Any, Any, T]]",
|
1262
|
+
*args: P.args,
|
1263
|
+
return_state: Literal[True],
|
1264
|
+
**kwargs: P.kwargs,
|
1265
|
+
) -> Awaitable[State[T]]:
|
1266
|
+
...
|
1267
|
+
|
1259
1268
|
@overload
|
1260
1269
|
def __call__(
|
1261
1270
|
self: "Flow[P, T]",
|
prefect/logging/configuration.py
CHANGED
@@ -13,7 +13,7 @@ import yaml
|
|
13
13
|
from prefect.settings import (
|
14
14
|
PREFECT_LOGGING_EXTRA_LOGGERS,
|
15
15
|
PREFECT_LOGGING_SETTINGS_PATH,
|
16
|
-
|
16
|
+
get_current_settings,
|
17
17
|
)
|
18
18
|
from prefect.utilities.collections import dict_to_flatdict, flatdict_to_dict
|
19
19
|
|
@@ -31,18 +31,14 @@ def load_logging_config(path: Path) -> dict:
|
|
31
31
|
"""
|
32
32
|
Loads logging configuration from a path allowing override from the environment
|
33
33
|
"""
|
34
|
+
current_settings = get_current_settings()
|
34
35
|
template = string.Template(path.read_text())
|
36
|
+
|
35
37
|
with warnings.catch_warnings():
|
36
38
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
37
39
|
config = yaml.safe_load(
|
38
40
|
# Substitute settings into the template in format $SETTING / ${SETTING}
|
39
|
-
template.substitute(
|
40
|
-
{
|
41
|
-
setting.name: str(setting.value())
|
42
|
-
for setting in SETTING_VARIABLES.values()
|
43
|
-
if setting.value() is not None
|
44
|
-
}
|
45
|
-
)
|
41
|
+
template.substitute(current_settings.to_environment_variables())
|
46
42
|
)
|
47
43
|
|
48
44
|
# Load overrides from the environment
|
prefect/logging/handlers.py
CHANGED
@@ -6,7 +6,7 @@ import traceback
|
|
6
6
|
import uuid
|
7
7
|
import warnings
|
8
8
|
from contextlib import asynccontextmanager
|
9
|
-
from typing import Any, Dict, List, Type, Union
|
9
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
10
10
|
|
11
11
|
import pendulum
|
12
12
|
from rich.console import Console
|
@@ -30,7 +30,6 @@ from prefect.settings import (
|
|
30
30
|
PREFECT_LOGGING_MARKUP,
|
31
31
|
PREFECT_LOGGING_TO_API_BATCH_INTERVAL,
|
32
32
|
PREFECT_LOGGING_TO_API_BATCH_SIZE,
|
33
|
-
PREFECT_LOGGING_TO_API_ENABLED,
|
34
33
|
PREFECT_LOGGING_TO_API_MAX_LOG_SIZE,
|
35
34
|
PREFECT_LOGGING_TO_API_WHEN_MISSING_FLOW,
|
36
35
|
)
|
@@ -134,7 +133,7 @@ class APILogHandler(logging.Handler):
|
|
134
133
|
try:
|
135
134
|
profile = prefect.context.get_settings_context()
|
136
135
|
|
137
|
-
if not
|
136
|
+
if not profile.settings.logging_to_api_enabled:
|
138
137
|
return # Respect the global settings toggle
|
139
138
|
if not getattr(record, "send_to_api", True):
|
140
139
|
return # Do not send records that have opted out
|
@@ -242,7 +241,7 @@ class PrefectConsoleHandler(logging.StreamHandler):
|
|
242
241
|
self,
|
243
242
|
stream=None,
|
244
243
|
highlighter: Highlighter = PrefectConsoleHighlighter,
|
245
|
-
styles: Dict[str, str] = None,
|
244
|
+
styles: Optional[Dict[str, str]] = None,
|
246
245
|
level: Union[int, str] = logging.NOTSET,
|
247
246
|
):
|
248
247
|
"""
|