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 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:19:56.215908+00:00"
4
- __git_commit__ = "25ff793e11f525e8f7043525165e0ded57abaea9"
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
@@ -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)
@@ -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-client
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=dLfD9lm8eU0Ry0yXo1JPxGJg6AT2WwOCsBrPLFh-XrU,185
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=4F8yQX1gML0GL4XP5hc-BwRwKnpCnb6fn9sUSAaDr6c,40506
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=xpG423Xcg4P-kfNof8cQ9MwGi2v5KscdJmfu7i9eVxo,30489
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=MTB5KzVcsraVmEy9BjlSt3PKMjk4iYebByLawzxR37s,10000
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=eMndCMwe9dyJKIZqji58bxdI3NMFyCXhYh3y_z1AHpQ,14032
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.dev1.dist-info/METADATA,sha256=WpnctDX6-zy1IcH43tVUR6dxb5fSPKsLF3mwl0aFPcs,7502
442
- prefect_client-3.7.2.dev1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
443
- prefect_client-3.7.2.dev1.dist-info/licenses/LICENSE,sha256=MCxsn8osAkzfxKC4CC_dLcUkU8DZLkyihZ8mGs3Ah3Q,11357
444
- prefect_client-3.7.2.dev1.dist-info/RECORD,,
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,,