pivot 0.2.3__tar.gz → 0.2.4__tar.gz

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.
Files changed (107) hide show
  1. {pivot-0.2.3 → pivot-0.2.4}/PKG-INFO +1 -1
  2. {pivot-0.2.3 → pivot-0.2.4}/pyproject.toml +1 -1
  3. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/engine.py +39 -20
  4. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/fingerprint.py +12 -4
  5. {pivot-0.2.3 → pivot-0.2.4}/LICENSE +0 -0
  6. {pivot-0.2.3 → pivot-0.2.4}/README.md +0 -0
  7. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/__init__.py +0 -0
  8. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/ast_utils.py +0 -0
  9. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/AGENTS.md +0 -0
  10. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/CLAUDE.md +0 -0
  11. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/__init__.py +0 -0
  12. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/_run_common.py +0 -0
  13. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/check_ignore.py +0 -0
  14. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/checkout.py +0 -0
  15. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/commit.py +0 -0
  16. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/completion.py +0 -0
  17. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/config.py +0 -0
  18. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/console.py +0 -0
  19. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/dag.py +0 -0
  20. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/data.py +0 -0
  21. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/decorators.py +0 -0
  22. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/doctor.py +0 -0
  23. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/errors.py +0 -0
  24. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/fingerprint.py +0 -0
  25. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/helpers.py +0 -0
  26. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/history.py +0 -0
  27. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/import_cmd.py +0 -0
  28. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/init.py +0 -0
  29. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/list.py +0 -0
  30. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/metrics.py +0 -0
  31. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/params.py +0 -0
  32. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/plots.py +0 -0
  33. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/remote.py +0 -0
  34. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/repro.py +0 -0
  35. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/run.py +0 -0
  36. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/status.py +0 -0
  37. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/targets.py +0 -0
  38. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/track.py +0 -0
  39. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/update.py +0 -0
  40. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/cli/verify.py +0 -0
  41. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/compose.py +0 -0
  42. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/config/__init__.py +0 -0
  43. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/config/io.py +0 -0
  44. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/config/models.py +0 -0
  45. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/dag/__init__.py +0 -0
  46. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/dag/render.py +0 -0
  47. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/decorators.py +0 -0
  48. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/discovery.py +0 -0
  49. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/__init__.py +0 -0
  50. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/agent_rpc.py +0 -0
  51. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/graph.py +0 -0
  52. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/scheduler.py +0 -0
  53. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/sinks.py +0 -0
  54. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/sources.py +0 -0
  55. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/types.py +0 -0
  56. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/watch.py +0 -0
  57. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/engine/worker_pool.py +0 -0
  58. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/exceptions.py +0 -0
  59. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/executor/AGENTS.md +0 -0
  60. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/executor/CLAUDE.md +0 -0
  61. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/executor/__init__.py +0 -0
  62. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/executor/commit.py +0 -0
  63. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/executor/core.py +0 -0
  64. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/executor/worker.py +0 -0
  65. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/explain.py +0 -0
  66. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/git.py +0 -0
  67. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/ignore.py +0 -0
  68. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/import_artifact.py +0 -0
  69. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/loaders.py +0 -0
  70. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/metrics.py +0 -0
  71. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/names.py +0 -0
  72. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/outputs.py +0 -0
  73. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/parameters.py +0 -0
  74. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/path_policy.py +0 -0
  75. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/project.py +0 -0
  76. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/py.typed +0 -0
  77. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/registry.py +0 -0
  78. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/remote/AGENTS.md +0 -0
  79. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/remote/__init__.py +0 -0
  80. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/remote/config.py +0 -0
  81. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/remote/git_archive.py +0 -0
  82. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/remote/github.py +0 -0
  83. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/remote/storage.py +0 -0
  84. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/remote/sync.py +0 -0
  85. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/run_history.py +0 -0
  86. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/show/__init__.py +0 -0
  87. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/show/common.py +0 -0
  88. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/show/data.py +0 -0
  89. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/show/metrics.py +0 -0
  90. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/show/params.py +0 -0
  91. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/show/plots.py +0 -0
  92. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/skip.py +0 -0
  93. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/stage_def.py +0 -0
  94. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/status.py +0 -0
  95. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/AGENTS.md +0 -0
  96. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/CLAUDE.md +0 -0
  97. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/__init__.py +0 -0
  98. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/artifact_lock.py +0 -0
  99. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/cache.py +0 -0
  100. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/lock.py +0 -0
  101. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/presentation.py +0 -0
  102. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/restore.py +0 -0
  103. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/state.py +0 -0
  104. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/store.py +0 -0
  105. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/storage/track.py +0 -0
  106. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/types.py +0 -0
  107. {pivot-0.2.3 → pivot-0.2.4}/src/pivot/yaml_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pivot
3
- Version: 0.2.3
3
+ Version: 0.2.4
4
4
  Summary: High-performance Python pipeline tool with automatic code change detection
5
5
  Author: Sami Jawhar
6
6
  Author-email: Sami Jawhar <sami@metr.org>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pivot"
3
- version = "0.2.3"
3
+ version = "0.2.4"
4
4
  description = "High-performance Python pipeline tool with automatic code change detection"
5
5
  requires-python = ">=3.13,<3.14"
6
6
  readme = "README.md"
@@ -1306,26 +1306,6 @@ class Engine:
1306
1306
  run_id=run_id,
1307
1307
  )
1308
1308
 
1309
- # Check cancellation
1310
- if self._cancel_event.is_set():
1311
- cancelled = self._scheduler.apply_cancel()
1312
- for name, old_state in cancelled:
1313
- await self.emit(
1314
- StageStateChanged(
1315
- type="stage_state_changed",
1316
- stage=name,
1317
- state=StageExecutionState.COMPLETED,
1318
- previous_state=old_state,
1319
- )
1320
- )
1321
- await self._emit_terminal_stage(
1322
- name,
1323
- StageStatus.CANCELLED,
1324
- "cancelled",
1325
- results,
1326
- run_id=run_id,
1327
- )
1328
-
1329
1309
  # Start more stages if slots available
1330
1310
  if not self._scheduler.stop_starting_new:
1331
1311
  await self._start_ready_stages(
@@ -1389,6 +1369,9 @@ class Engine:
1389
1369
  "Sentinel send failed (will rely on shutdown_event): %s", exc
1390
1370
  )
1391
1371
 
1372
+ if self._cancel_event.is_set():
1373
+ await self._finalize_cancelled_stages(results, run_id)
1374
+
1392
1375
  finally:
1393
1376
  # Close StateDBs first (lightweight), then shut down worker pool.
1394
1377
  with anyio.CancelScope(shield=True):
@@ -1682,6 +1665,19 @@ class Engine:
1682
1665
  skipped = False
1683
1666
  if skipped:
1684
1667
  continue
1668
+ # A watch event may cancel this run while its coordinator skip check awaited.
1669
+ # Do not submit a stage to the pool after cancellation stopped admissions.
1670
+ if self._cancel_event.is_set():
1671
+ self._scheduler.release_resources(stage_name)
1672
+ await self._set_stage_state(stage_name, StageExecutionState.COMPLETED)
1673
+ await self._emit_terminal_stage(
1674
+ stage_name,
1675
+ StageStatus.CANCELLED,
1676
+ "cancelled",
1677
+ results,
1678
+ run_id=run_id,
1679
+ )
1680
+ continue
1685
1681
 
1686
1682
  # Stage must run — lazily create pool on first dispatch
1687
1683
  started += 1
@@ -2162,6 +2158,29 @@ class Engine:
2162
2158
 
2163
2159
  return duration_ms
2164
2160
 
2161
+ async def _finalize_cancelled_stages(
2162
+ self,
2163
+ results: dict[str, executor_core.ExecutionSummary],
2164
+ run_id: str,
2165
+ ) -> None:
2166
+ """Mark every ready or pending stage cancelled exactly once."""
2167
+ for stage_name, old_state in self._scheduler.apply_cancel():
2168
+ await self.emit(
2169
+ StageStateChanged(
2170
+ type="stage_state_changed",
2171
+ stage=stage_name,
2172
+ state=StageExecutionState.COMPLETED,
2173
+ previous_state=old_state,
2174
+ )
2175
+ )
2176
+ await self._emit_terminal_stage(
2177
+ stage_name,
2178
+ StageStatus.CANCELLED,
2179
+ "cancelled",
2180
+ results,
2181
+ run_id=run_id,
2182
+ )
2183
+
2165
2184
  async def _emit_terminal_stage(
2166
2185
  self,
2167
2186
  stage_name: str,
@@ -1217,6 +1217,11 @@ def _resolve_dotted_path(parts: tuple[str, ...], ns: dict[str, Any]) -> Any:
1217
1217
  return obj
1218
1218
 
1219
1219
 
1220
+ def _method_function(method: Callable[..., Any]) -> Callable[..., Any]:
1221
+ """Return the underlying function behind a bound method."""
1222
+ return cast("Callable[..., Any]", getattr(method, "__func__", method))
1223
+
1224
+
1220
1225
  def get_loader_fingerprint(loader: "loaders.Writer[Any] | loaders.Reader[Any]") -> dict[str, str]:
1221
1226
  """Generate fingerprint manifest for a loader instance.
1222
1227
 
@@ -1244,13 +1249,14 @@ def get_loader_fingerprint(loader: "loaders.Writer[Any] | loaders.Reader[Any]")
1244
1249
  if method is not None:
1245
1250
  _process_callable_dependency(f"{class_name}.{method_name}", method, manifest, visited)
1246
1251
 
1247
- # Fingerprint save() if this is a Writer
1252
+ # Use each method definition rather than a temporary bound method so the
1253
+ # weak AST hash cache retains the entry across loader fingerprint calls.
1248
1254
  if isinstance(loader, loaders_module.Writer):
1249
- manifest[f"loader:{class_name}:save"] = hash_function_ast(loader.save)
1255
+ manifest[f"loader:{class_name}:save"] = hash_function_ast(_method_function(loader.save))
1250
1256
 
1251
1257
  # Fingerprint load() if this is a Reader
1252
1258
  if isinstance(loader, loaders_module.Reader):
1253
- manifest[f"loader:{class_name}:load"] = hash_function_ast(loader.load)
1259
+ manifest[f"loader:{class_name}:load"] = hash_function_ast(_method_function(loader.load))
1254
1260
 
1255
1261
  # Only fingerprint empty() if it's overridden in a Loader subclass
1256
1262
  if isinstance(loader, loaders_module.Loader):
@@ -1262,7 +1268,9 @@ def get_loader_fingerprint(loader: "loaders.Writer[Any] | loaders.Reader[Any]")
1262
1268
  break
1263
1269
  if "empty" in cls.__dict__:
1264
1270
  # Found override in a subclass
1265
- manifest[f"loader:{class_name}:empty"] = hash_function_ast(typed_loader.empty)
1271
+ manifest[f"loader:{class_name}:empty"] = hash_function_ast(
1272
+ _method_function(typed_loader.empty)
1273
+ )
1266
1274
  break
1267
1275
 
1268
1276
  loader_any = cast("Any", loader)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes