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 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.dev1"
3
- __build_date__ = "2026-05-18 09:21:49.710405+00:00"
4
- __git_commit__ = "25ff793e11f525e8f7043525165e0ded57abaea9"
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
@@ -57,7 +57,15 @@ class AbstractAppriseNotificationBlock(NotificationBlock, ABC):
57
57
  body: str,
58
58
  subject: str | None = None,
59
59
  ) -> None:
60
- with LogEavesdropper("apprise", level=logging.DEBUG) as eavesdropper:
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
- with LogEavesdropper("apprise", level=logging.DEBUG) as eavesdropper:
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 "",
@@ -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", *dependencies.split("\n")],
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 and r.id not in submitted_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
- work_pool = await self._client.create_work_pool(work_pool=wp)
314
- self._logger.info(f"Work pool {self.work_pool_name!r} created.")
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prefect
3
- Version: 3.7.2.dev1
3
+ Version: 3.7.2.dev2
4
4
  Summary: Workflow orchestration and management.
5
5
  Project-URL: Changelog, https://github.com/PrefectHQ/prefect/releases
6
6
  Project-URL: Documentation, https://docs.prefect.io
@@ -1,8 +1,8 @@
1
1
  prefect/.prefectignore,sha256=awSprvKT0vI8a64mEOLrMxhxqcO-b0ERQeYpA2rNKVQ,390
2
- prefect/AGENTS.md,sha256=WDlzAm3yttvSqmvVM61Yd2YCZGeScQQkzb1J8SKL-SA,9379
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=z7dRkMzCIzwjnUcxIHZ2re88hOxUHc9TAlf3QjwEVmo,185
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=4F8yQX1gML0GL4XP5hc-BwRwKnpCnb6fn9sUSAaDr6c,40506
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=xpG423Xcg4P-kfNof8cQ9MwGi2v5KscdJmfu7i9eVxo,30489
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=MTB5KzVcsraVmEy9BjlSt3PKMjk4iYebByLawzxR37s,10000
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=92UZiiTGJZ34Fu2LynKcsGMNaI4v-XNv5QPako9py28,8541
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=eMndCMwe9dyJKIZqji58bxdI3NMFyCXhYh3y_z1AHpQ,14032
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.dev1.dist-info/METADATA,sha256=9Nc36ABBEbObTgwYcsuWfsLoIj1wUTC_pHCXFQ5J1HU,14005
1368
- prefect-3.7.2.dev1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
1369
- prefect-3.7.2.dev1.dist-info/entry_points.txt,sha256=HlY8up83iIq2vU2r33a0qSis4eOFSyb1mRH4l7Xt9X8,126
1370
- prefect-3.7.2.dev1.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
1371
- prefect-3.7.2.dev1.dist-info/RECORD,,
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,,