prefect 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/server/AGENTS.md +2 -0
- prefect/workers/_worker_channel/_sync.py +16 -3
- {prefect-3.7.2.dev1.dist-info → prefect-3.7.2.dev2.dist-info}/METADATA +1 -1
- {prefect-3.7.2.dev1.dist-info → prefect-3.7.2.dev2.dist-info}/RECORD +12 -12
- {prefect-3.7.2.dev1.dist-info → prefect-3.7.2.dev2.dist-info}/WHEEL +0 -0
- {prefect-3.7.2.dev1.dist-info → prefect-3.7.2.dev2.dist-info}/entry_points.txt +0 -0
- {prefect-3.7.2.dev1.dist-info → prefect-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:19:42.711716+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)
|
prefect/server/AGENTS.md
CHANGED
|
@@ -45,6 +45,8 @@ alembic_revision("description") # Create a new migration
|
|
|
45
45
|
|
|
46
46
|
- **`_find_block_schema_via_checksum` dict-index miss is definitive — no linear-scan fallback.** In `models/block_schemas.py`, passing a `checksum_index` dict makes lookups O(1); a miss returns `None` without consulting the row list. Any code calling `_construct_full_block_schema` in a bulk loop must pre-build a `checksum_index` once and thread it through all recursive calls — omitting the kwarg on a recursive call silently downgrades every lookup to an O(N) scan, restoring O(N²) overall cost. The index uses first-wins semantics to match `next()` scan order.
|
|
47
47
|
|
|
48
|
+
- **`read_server_default_result_storage` bypasses the generic `Configuration` cache intentionally.** In `models/storage_defaults.py`, the read path uses a raw `sa.select` query instead of `models/configuration.read_configuration()` to avoid serving stale cached data when another server process updates the setting. Do not refactor this to use the generic `read_configuration()` helper — the cache isolation is the desired behavior.
|
|
49
|
+
|
|
48
50
|
- **`GlobalConcurrencyLimitResponse.active_slots` is computed at read time, not stored.** The DB column holds the raw accumulated count; the API joins `active_slots_after_decay()` (in `models/concurrency_limits_v2.py`) on every read. Never return ORM model fields directly from `api/concurrency_limits_v2.py` — use `_global_concurrency_limit_response()` which pulls `active_slots` from the JOIN result. `PATCH` operations do not persist the decayed count; after an update, `active_slots` in the DB remains the pre-decay value.
|
|
49
51
|
|
|
50
52
|
- **`force=True` routes through `MinimalFlowPolicy`, not a true bypass.** `MinimalFlowPolicy` is intentionally lightweight but still includes guards — notably `PreventResultDataLoss`, which rejects COMPLETED→COMPLETED transitions that would discard persisted result data. Do not assume `force=True` skips all orchestration rules.
|
|
@@ -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=y8nCiaDce5Xe86e6K7uQSacWjmCq9UX8eyOkv9_S200,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
|
|
@@ -139,11 +139,11 @@ prefect/blocks/__init__.py,sha256=D0hB72qMfgqnBB2EMZRxUxlX9yLfkab5zDChOwJZmkY,22
|
|
|
139
139
|
prefect/blocks/abstract.py,sha256=mpOAWopSR_RrzdxeurBTXVSKisP8ne-k8LYos-tp7go,17021
|
|
140
140
|
prefect/blocks/core.py,sha256=4LEd2x7vGCsIgSjOnfDYMZJj3ByhcP7n_YubkyJwIMM,85937
|
|
141
141
|
prefect/blocks/fields.py,sha256=1m507VVmkpOnMF_7N-qboRjtw4_ceIuDneX3jZ3Jm54,63
|
|
142
|
-
prefect/blocks/notifications.py,sha256=
|
|
142
|
+
prefect/blocks/notifications.py,sha256=u9cBEDr6taKcNZgXqcItrySj35MuZwo2G4-f0XPHx8w,41102
|
|
143
143
|
prefect/blocks/redis.py,sha256=fn6nR_ZoFfJqO_sWc-4WUY3D2No7RSXUntgZtRFNXYo,7682
|
|
144
144
|
prefect/blocks/system.py,sha256=9OE9gs0cH0bH6DV5dvf4oprthWBKA6Zh56aSnn2pIU4,2325
|
|
145
145
|
prefect/blocks/webhook.py,sha256=PQKPBEZa8yjIjWHHH3yAbNttuA_hlmrvf12QRmeSB_A,3482
|
|
146
|
-
prefect/bundles/__init__.py,sha256=
|
|
146
|
+
prefect/bundles/__init__.py,sha256=3LiZeX-QG-5NnKq2FaQZrPOI_2Ta-2bOUir4F9-tEV0,31104
|
|
147
147
|
prefect/bundles/_file_collector.py,sha256=15v9l1vtbSwSfgA9tHF3jJzJxf2vWCCEJRvFIR89OSw,21222
|
|
148
148
|
prefect/bundles/_ignore_filter.py,sha256=4p7Ku8h6LOVmagC0lLlK3SBo-FLrz2JXoJexFto5nKo,11067
|
|
149
149
|
prefect/bundles/_path_resolver.py,sha256=-3VCDZxD7aDFJlucOdTK4PneW4XEfxpFo1MSfLJe5Xk,18366
|
|
@@ -358,7 +358,7 @@ prefect/runner/_flow_run_executor.py,sha256=mMdFvvswtcsfydZuRcSCsqu-HrLytFg9LV0y
|
|
|
358
358
|
prefect/runner/_hook_runner.py,sha256=iVjZtVbJ8ltlXnlVTHqa9XFGSaMxpw0bjKXk_atwLUk,3346
|
|
359
359
|
prefect/runner/_limit_manager.py,sha256=WKZ9VrnX15cMdmwtWXKrICJ32A8TZ19_f2lavLy4KD8,3599
|
|
360
360
|
prefect/runner/_process_manager.py,sha256=upuMUIHKLZR0sxh_AbHp0SGyp_ZSOpoaro6zADpxsYI,7292
|
|
361
|
-
prefect/runner/_scheduled_run_poller.py,sha256=
|
|
361
|
+
prefect/runner/_scheduled_run_poller.py,sha256=F1cvSwy5eU2y205E4_jsFpYSCzGz6cAsdRNAG5CsFQg,9886
|
|
362
362
|
prefect/runner/_starter_bundle.py,sha256=ghYK0zufuW0B5u-EPNl8uP2OiUGCZQsuTgdFxQvvJlk,2591
|
|
363
363
|
prefect/runner/_starter_direct.py,sha256=8cneJRIBud3HXUaf93ut92rQJfWEqg7_y0r_EVGf0ps,2730
|
|
364
364
|
prefect/runner/_starter_engine.py,sha256=FjcHQe_iryk8BFpeiGw8MCWcLSRKhYwmb2iA4eou0zg,6507
|
|
@@ -372,7 +372,7 @@ prefect/runtime/__init__.py,sha256=JswiTlYRup2zXOYu8AqJ7czKtgcw9Kxo0tTbS6aWCqY,4
|
|
|
372
372
|
prefect/runtime/deployment.py,sha256=jYD-jkbb1dAl0wQuiY5mulVTqAEnF4xE3D5Iard6yms,5552
|
|
373
373
|
prefect/runtime/flow_run.py,sha256=KZRWdcqKOHvlK1g4OucFTH_o3XonoI1rzF81uQfjdu0,10793
|
|
374
374
|
prefect/runtime/task_run.py,sha256=zYBSs7QrAu7c2IjKomRzPXKyIXrjqclMTMrco-dwyOw,4212
|
|
375
|
-
prefect/server/AGENTS.md,sha256=
|
|
375
|
+
prefect/server/AGENTS.md,sha256=eh86Vbe6rpas80SdtNCvOvefnwgV_gFJKwHJb8NX2-g,8977
|
|
376
376
|
prefect/server/__init__.py,sha256=6x9SmgqYsTcAzrdIocQj1srMZHjJ5vC6gEnqvzI7xts,117
|
|
377
377
|
prefect/server/collection_blocks_data.json,sha256=R1Vx6FFjaUr-WzgqVwduDMWQTiDdPBMoLJQRmjyB58I,284238
|
|
378
378
|
prefect/server/exceptions.py,sha256=zfbfOdF-PsqnRUqxUXJ__zD905XJx1egFBrrUbIfkRA,735
|
|
@@ -1362,10 +1362,10 @@ prefect/workers/utilities.py,sha256=-re_0s1Jr98W5xhJr4Xvvz34OxN3T4ypSupxdBiC8aA,
|
|
|
1362
1362
|
prefect/workers/_worker_channel/__init__.py,sha256=vEc7NvC1sjxnCn5thQv5lSU1bDKRaT1sV7AA_6VRoCM,709
|
|
1363
1363
|
prefect/workers/_worker_channel/_protocol.py,sha256=Wo5QuQ9h7gJlq1FLzrHHGbqTRgPS7mJA31svSBkfHBY,15699
|
|
1364
1364
|
prefect/workers/_worker_channel/_state.py,sha256=eQTFZtAVDZH1vVWps3SdeY6aW3qu2wx1UKYQXK3AyuE,5369
|
|
1365
|
-
prefect/workers/_worker_channel/_sync.py,sha256=
|
|
1365
|
+
prefect/workers/_worker_channel/_sync.py,sha256=4VPYQbnhHeB2W4mJr-4qb-XR-YnaigUKjdAvJQqV0iw,14708
|
|
1366
1366
|
prefect/workers/_worker_channel/_transport.py,sha256=cgrtAENawDpIPB8gwILd62y2VduykUCmg1NaO1pL-tg,9021
|
|
1367
|
-
prefect-3.7.2.
|
|
1368
|
-
prefect-3.7.2.
|
|
1369
|
-
prefect-3.7.2.
|
|
1370
|
-
prefect-3.7.2.
|
|
1371
|
-
prefect-3.7.2.
|
|
1367
|
+
prefect-3.7.2.dev2.dist-info/METADATA,sha256=IX9yrobGVvgoKoKjlO0C_4QEmDm68c6LUXHOmvbXhfA,14005
|
|
1368
|
+
prefect-3.7.2.dev2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
1369
|
+
prefect-3.7.2.dev2.dist-info/entry_points.txt,sha256=HlY8up83iIq2vU2r33a0qSis4eOFSyb1mRH4l7Xt9X8,126
|
|
1370
|
+
prefect-3.7.2.dev2.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
|
|
1371
|
+
prefect-3.7.2.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|