prefect-client 3.7.2.dev1__py3-none-any.whl → 3.7.2.dev2__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/AGENTS.md +1 -0
- prefect/_build_info.py +3 -3
- prefect/blocks/notifications.py +18 -2
- prefect/bundles/__init__.py +16 -1
- prefect/runner/_scheduled_run_poller.py +1 -3
- prefect/workers/_worker_channel/_sync.py +16 -3
- {prefect_client-3.7.2.dev1.dist-info → prefect_client-3.7.2.dev2.dist-info}/METADATA +1 -1
- {prefect_client-3.7.2.dev1.dist-info → prefect_client-3.7.2.dev2.dist-info}/RECORD +10 -10
- {prefect_client-3.7.2.dev1.dist-info → prefect_client-3.7.2.dev2.dist-info}/WHEEL +0 -0
- {prefect_client-3.7.2.dev1.dist-info → prefect_client-3.7.2.dev2.dist-info}/licenses/LICENSE +0 -0
prefect/AGENTS.md
CHANGED
|
@@ -30,6 +30,7 @@ There is no formal public/private boundary beyond the `_` prefix convention. Mod
|
|
|
30
30
|
- **`ProcessPoolTaskRunner` instances may be deserialized without `__init__` running.** When a runner is pickled and restored in a subprocess, `__init__` is not called, so instance attributes set there may be absent. Access any such attribute via `getattr(self, "_attr", default)` rather than `self._attr` directly — both in the property getter and in `duplicate()`. The `subprocess_message_processor_factories` property demonstrates the required pattern.
|
|
31
31
|
- **`ThreadPoolTaskRunner` is cloudpickled when a flow run is dispatched to a subprocess.** `threading.Lock` and other non-picklable thread primitives must be dropped in `__getstate__` and rebuilt in `__setstate__`. Any new instance state added to this class must be evaluated for picklability.
|
|
32
32
|
- **Nested task submissions on a bounded `ThreadPoolTaskRunner` can deadlock.** When a worker task submits children and blocks on `.result()` while all `max_workers` threads are busy, the pool starves. `_warn_if_nested_submit_would_deadlock` detects this and emits a one-time warning — preserve this detection when changing pool management.
|
|
33
|
+
- **Result storage resolves through three tiers, potentially making an API call.** `get_default_result_storage()` checks: (1) `PREFECT_DEFAULT_RESULT_STORAGE_BLOCK` setting, (2) server-configured default block (API call to the server's Configuration store), (3) local storage path. The API call is silently suppressed on any HTTP error. Engines use `_get_default_persist_result()` — not `should_persist_result()` — at context setup time to auto-enable persistence when a server default block is configured. Do not swap `_get_default_persist_result()` back to `should_persist_result()` in engine initialization paths; `should_persist_result()` does not consult the server default.
|
|
33
34
|
- **`ResultRecordMetadata` tolerates unknown serializer types.** When loading persisted metadata, an unrecognized serializer `type` is converted to an `UnknownSerializer` placeholder rather than raising `ValidationError`. This allows inspecting result metadata when the serializer implementation is unavailable in the current environment. However, known serializer types with invalid fields still raise `ValidationError`. `UnknownSerializer.dumps()` and `UnknownSerializer.loads()` raise `RuntimeError` — the tolerance is for inspection only, not actual serialization/deserialization.
|
|
34
35
|
- **Flow-run suspension is enforced at orchestration boundaries.** Suspension is delivered via `FlowRunSuspensionRequest` and raised with `raise_if_flow_run_suspension_requested()`. When changing engines, futures, task runners, or generator execution, check before returning control to flow user code or proposing a flow state that could leave `Suspended`; keep sync/async paths aligned and avoid per-boundary API reads.
|
|
35
36
|
|
prefect/_build_info.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Generated by versioningit
|
|
2
|
-
__version__ = "3.7.2.
|
|
3
|
-
__build_date__ = "2026-05-
|
|
4
|
-
__git_commit__ = "
|
|
2
|
+
__version__ = "3.7.2.dev2"
|
|
3
|
+
__build_date__ = "2026-05-19 09:17:54.056516+00:00"
|
|
4
|
+
__git_commit__ = "68244724cf7a168f14f81bdf032689c912a404de"
|
|
5
5
|
__dirty__ = False
|
prefect/blocks/notifications.py
CHANGED
|
@@ -57,7 +57,15 @@ class AbstractAppriseNotificationBlock(NotificationBlock, ABC):
|
|
|
57
57
|
body: str,
|
|
58
58
|
subject: str | None = None,
|
|
59
59
|
) -> None:
|
|
60
|
-
|
|
60
|
+
apprise_logger = logging.getLogger("apprise")
|
|
61
|
+
root_logger = logging.getLogger()
|
|
62
|
+
|
|
63
|
+
if apprise_logger.level == logging.NOTSET:
|
|
64
|
+
log_level = root_logger.getEffectiveLevel()
|
|
65
|
+
else:
|
|
66
|
+
log_level = max(apprise_logger.level, root_logger.getEffectiveLevel())
|
|
67
|
+
|
|
68
|
+
with LogEavesdropper("apprise", level=log_level) as eavesdropper:
|
|
61
69
|
result = await self._apprise_client.async_notify( # pyright: ignore[reportUnknownMemberType] incomplete type hints in apprise
|
|
62
70
|
body=body,
|
|
63
71
|
title=subject or "",
|
|
@@ -72,7 +80,15 @@ class AbstractAppriseNotificationBlock(NotificationBlock, ABC):
|
|
|
72
80
|
body: str,
|
|
73
81
|
subject: str | None = None,
|
|
74
82
|
) -> None:
|
|
75
|
-
|
|
83
|
+
apprise_logger = logging.getLogger("apprise")
|
|
84
|
+
root_logger = logging.getLogger()
|
|
85
|
+
|
|
86
|
+
if apprise_logger.level == logging.NOTSET:
|
|
87
|
+
log_level = root_logger.getEffectiveLevel()
|
|
88
|
+
else:
|
|
89
|
+
log_level = max(apprise_logger.level, root_logger.getEffectiveLevel())
|
|
90
|
+
|
|
91
|
+
with LogEavesdropper("apprise", level=log_level) as eavesdropper:
|
|
76
92
|
result = self._apprise_client.notify( # pyright: ignore[reportUnknownMemberType] incomplete type hints in apprise
|
|
77
93
|
body=body,
|
|
78
94
|
title=subject or "",
|
prefect/bundles/__init__.py
CHANGED
|
@@ -593,8 +593,23 @@ def execute_bundle_in_subprocess(
|
|
|
593
593
|
|
|
594
594
|
# Install dependencies if necessary
|
|
595
595
|
if dependencies := bundle.get("dependencies"):
|
|
596
|
+
dep_lines = [line.strip() for line in dependencies.split("\n") if line.strip()]
|
|
597
|
+
validated_deps: list[str] = []
|
|
598
|
+
for dep_line in dep_lines:
|
|
599
|
+
if dep_line.startswith("-"):
|
|
600
|
+
raise ValueError(
|
|
601
|
+
f"Invalid dependency (command-line flags are not allowed): {dep_line!r}"
|
|
602
|
+
)
|
|
603
|
+
try:
|
|
604
|
+
Requirement(dep_line)
|
|
605
|
+
except InvalidRequirement as e:
|
|
606
|
+
raise ValueError(
|
|
607
|
+
f"Invalid PEP 508 dependency specifier: {dep_line!r}"
|
|
608
|
+
) from e
|
|
609
|
+
validated_deps.append(dep_line)
|
|
610
|
+
|
|
596
611
|
subprocess.check_call(
|
|
597
|
-
[_get_uv_path(), "pip", "install", *
|
|
612
|
+
[_get_uv_path(), "pip", "install", *validated_deps],
|
|
598
613
|
# Copy the current environment to ensure we install into the correct venv
|
|
599
614
|
env=os.environ,
|
|
600
615
|
)
|
|
@@ -126,7 +126,6 @@ class ScheduledRunPoller:
|
|
|
126
126
|
),
|
|
127
127
|
)
|
|
128
128
|
|
|
129
|
-
submitted_ids: set[UUID] = set()
|
|
130
129
|
for flow_run in submittable_flow_runs:
|
|
131
130
|
if flow_run.id in self._submitting_flow_run_ids:
|
|
132
131
|
continue
|
|
@@ -135,13 +134,12 @@ class ScheduledRunPoller:
|
|
|
135
134
|
except anyio.WouldBlock:
|
|
136
135
|
break # sorted: no earlier run fits
|
|
137
136
|
self._submitting_flow_run_ids.add(flow_run.id)
|
|
138
|
-
submitted_ids.add(flow_run.id)
|
|
139
137
|
task_group.start_soon(self._submit_run, flow_run, task_group, slot_token)
|
|
140
138
|
|
|
141
139
|
skipped_count = sum(
|
|
142
140
|
1
|
|
143
141
|
for r in submittable_flow_runs
|
|
144
|
-
if r.id not in self._submitting_flow_run_ids
|
|
142
|
+
if r.id not in self._submitting_flow_run_ids
|
|
145
143
|
)
|
|
146
144
|
if skipped_count > 0:
|
|
147
145
|
self._logger.info("%d scheduled runs skipped (at capacity)", skipped_count)
|
|
@@ -16,7 +16,7 @@ from prefect.client.base import ServerType
|
|
|
16
16
|
from prefect.client.orchestration import PrefectClient
|
|
17
17
|
from prefect.client.schemas.actions import WorkPoolCreate, WorkPoolUpdate
|
|
18
18
|
from prefect.client.schemas.objects import WorkerMetadata, WorkPool
|
|
19
|
-
from prefect.exceptions import ObjectNotFound
|
|
19
|
+
from prefect.exceptions import ObjectAlreadyExists, ObjectNotFound
|
|
20
20
|
from prefect.workers._cleanup import WorkerCleanupExecutor
|
|
21
21
|
from prefect.workers._worker_channel._protocol import WorkerChannelProtocolHandler
|
|
22
22
|
from prefect.workers._worker_channel._state import (
|
|
@@ -310,8 +310,21 @@ class WorkPoolWorkerChannel:
|
|
|
310
310
|
if self.base_job_template is not None:
|
|
311
311
|
wp.base_job_template = self.base_job_template
|
|
312
312
|
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
try:
|
|
314
|
+
work_pool = await self._client.create_work_pool(work_pool=wp)
|
|
315
|
+
self._logger.info(f"Work pool {self.work_pool_name!r} created.")
|
|
316
|
+
except ObjectAlreadyExists:
|
|
317
|
+
# Another worker created the pool between our read and
|
|
318
|
+
# create. Re-read so we can continue with the existing
|
|
319
|
+
# pool rather than failing setup.
|
|
320
|
+
self._logger.debug(
|
|
321
|
+
"Work pool %r was created concurrently by another "
|
|
322
|
+
"worker; re-reading.",
|
|
323
|
+
self.work_pool_name,
|
|
324
|
+
)
|
|
325
|
+
work_pool = await self._client.read_work_pool(
|
|
326
|
+
work_pool_name=self.work_pool_name
|
|
327
|
+
)
|
|
315
328
|
else:
|
|
316
329
|
self._logger.warning(f"Work pool {self.work_pool_name!r} not found!")
|
|
317
330
|
if self.base_job_template is not None:
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
|
|
2
|
-
prefect/AGENTS.md,sha256=
|
|
2
|
+
prefect/AGENTS.md,sha256=rOU4L6B0ZdvnVmLr_eL394QlwfJkPh1OZswHppAjUN4,10063
|
|
3
3
|
prefect/__init__.py,sha256=4e69hAJtYPoEeRxc8iNjzp5WIkmXP0i2SNiN3c97_Go,6703
|
|
4
4
|
prefect/__main__.py,sha256=WFjw3kaYJY6pOTA7WDOgqjsz8zUEUZHCcj3P5wyVa-g,66
|
|
5
|
-
prefect/_build_info.py,sha256=
|
|
5
|
+
prefect/_build_info.py,sha256=AHkrkaCbyqiZK5RthlP9QpdWA0STowHOiJKlYYABweI,185
|
|
6
6
|
prefect/_flow_run_suspension.py,sha256=5zTTB7ZIBHzoS0pVrhNn23-9hK51qZ3CQA6C-azluC0,4144
|
|
7
7
|
prefect/agent.py,sha256=dPvG1jDGD5HSH7aM2utwtk6RaJ9qg13XjkA0lAIgQmY,287
|
|
8
8
|
prefect/artifacts.py,sha256=ZdMLJeJGK82hibtRzbsVa-g95dMa0D2UP1LiESoXmf4,23951
|
|
@@ -138,11 +138,11 @@ prefect/blocks/__init__.py,sha256=D0hB72qMfgqnBB2EMZRxUxlX9yLfkab5zDChOwJZmkY,22
|
|
|
138
138
|
prefect/blocks/abstract.py,sha256=mpOAWopSR_RrzdxeurBTXVSKisP8ne-k8LYos-tp7go,17021
|
|
139
139
|
prefect/blocks/core.py,sha256=4LEd2x7vGCsIgSjOnfDYMZJj3ByhcP7n_YubkyJwIMM,85937
|
|
140
140
|
prefect/blocks/fields.py,sha256=1m507VVmkpOnMF_7N-qboRjtw4_ceIuDneX3jZ3Jm54,63
|
|
141
|
-
prefect/blocks/notifications.py,sha256=
|
|
141
|
+
prefect/blocks/notifications.py,sha256=u9cBEDr6taKcNZgXqcItrySj35MuZwo2G4-f0XPHx8w,41102
|
|
142
142
|
prefect/blocks/redis.py,sha256=fn6nR_ZoFfJqO_sWc-4WUY3D2No7RSXUntgZtRFNXYo,7682
|
|
143
143
|
prefect/blocks/system.py,sha256=9OE9gs0cH0bH6DV5dvf4oprthWBKA6Zh56aSnn2pIU4,2325
|
|
144
144
|
prefect/blocks/webhook.py,sha256=PQKPBEZa8yjIjWHHH3yAbNttuA_hlmrvf12QRmeSB_A,3482
|
|
145
|
-
prefect/bundles/__init__.py,sha256=
|
|
145
|
+
prefect/bundles/__init__.py,sha256=3LiZeX-QG-5NnKq2FaQZrPOI_2Ta-2bOUir4F9-tEV0,31104
|
|
146
146
|
prefect/bundles/_file_collector.py,sha256=15v9l1vtbSwSfgA9tHF3jJzJxf2vWCCEJRvFIR89OSw,21222
|
|
147
147
|
prefect/bundles/_ignore_filter.py,sha256=4p7Ku8h6LOVmagC0lLlK3SBo-FLrz2JXoJexFto5nKo,11067
|
|
148
148
|
prefect/bundles/_path_resolver.py,sha256=-3VCDZxD7aDFJlucOdTK4PneW4XEfxpFo1MSfLJe5Xk,18366
|
|
@@ -281,7 +281,7 @@ prefect/runner/_flow_run_executor.py,sha256=mMdFvvswtcsfydZuRcSCsqu-HrLytFg9LV0y
|
|
|
281
281
|
prefect/runner/_hook_runner.py,sha256=iVjZtVbJ8ltlXnlVTHqa9XFGSaMxpw0bjKXk_atwLUk,3346
|
|
282
282
|
prefect/runner/_limit_manager.py,sha256=WKZ9VrnX15cMdmwtWXKrICJ32A8TZ19_f2lavLy4KD8,3599
|
|
283
283
|
prefect/runner/_process_manager.py,sha256=upuMUIHKLZR0sxh_AbHp0SGyp_ZSOpoaro6zADpxsYI,7292
|
|
284
|
-
prefect/runner/_scheduled_run_poller.py,sha256=
|
|
284
|
+
prefect/runner/_scheduled_run_poller.py,sha256=F1cvSwy5eU2y205E4_jsFpYSCzGz6cAsdRNAG5CsFQg,9886
|
|
285
285
|
prefect/runner/_starter_bundle.py,sha256=ghYK0zufuW0B5u-EPNl8uP2OiUGCZQsuTgdFxQvvJlk,2591
|
|
286
286
|
prefect/runner/_starter_direct.py,sha256=8cneJRIBud3HXUaf93ut92rQJfWEqg7_y0r_EVGf0ps,2730
|
|
287
287
|
prefect/runner/_starter_engine.py,sha256=FjcHQe_iryk8BFpeiGw8MCWcLSRKhYwmb2iA4eou0zg,6507
|
|
@@ -436,9 +436,9 @@ prefect/workers/utilities.py,sha256=-re_0s1Jr98W5xhJr4Xvvz34OxN3T4ypSupxdBiC8aA,
|
|
|
436
436
|
prefect/workers/_worker_channel/__init__.py,sha256=vEc7NvC1sjxnCn5thQv5lSU1bDKRaT1sV7AA_6VRoCM,709
|
|
437
437
|
prefect/workers/_worker_channel/_protocol.py,sha256=Wo5QuQ9h7gJlq1FLzrHHGbqTRgPS7mJA31svSBkfHBY,15699
|
|
438
438
|
prefect/workers/_worker_channel/_state.py,sha256=eQTFZtAVDZH1vVWps3SdeY6aW3qu2wx1UKYQXK3AyuE,5369
|
|
439
|
-
prefect/workers/_worker_channel/_sync.py,sha256=
|
|
439
|
+
prefect/workers/_worker_channel/_sync.py,sha256=4VPYQbnhHeB2W4mJr-4qb-XR-YnaigUKjdAvJQqV0iw,14708
|
|
440
440
|
prefect/workers/_worker_channel/_transport.py,sha256=cgrtAENawDpIPB8gwILd62y2VduykUCmg1NaO1pL-tg,9021
|
|
441
|
-
prefect_client-3.7.2.
|
|
442
|
-
prefect_client-3.7.2.
|
|
443
|
-
prefect_client-3.7.2.
|
|
444
|
-
prefect_client-3.7.2.
|
|
441
|
+
prefect_client-3.7.2.dev2.dist-info/METADATA,sha256=Wy-hi6qLcnTmx0g139Ic9N6ArEO0VYVs5eXdOAkz3ec,7502
|
|
442
|
+
prefect_client-3.7.2.dev2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
443
|
+
prefect_client-3.7.2.dev2.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
|
|
444
|
+
prefect_client-3.7.2.dev2.dist-info/RECORD,,
|
|
File without changes
|
{prefect_client-3.7.2.dev1.dist-info → prefect_client-3.7.2.dev2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|