runspec 0.76.1__tar.gz → 0.76.2__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 (84) hide show
  1. {runspec-0.76.1 → runspec-0.76.2}/PKG-INFO +1 -1
  2. {runspec-0.76.1 → runspec-0.76.2}/pyproject.toml +1 -1
  3. {runspec-0.76.1 → runspec-0.76.2}/runspec/logs.py +32 -2
  4. {runspec-0.76.1 → runspec-0.76.2}/runspec.egg-info/PKG-INFO +1 -1
  5. {runspec-0.76.1 → runspec-0.76.2}/tests/test_logs.py +6 -1
  6. {runspec-0.76.1 → runspec-0.76.2}/runspec/__init__.py +0 -0
  7. {runspec-0.76.1 → runspec-0.76.2}/runspec/ansiblegen.py +0 -0
  8. {runspec-0.76.1 → runspec-0.76.2}/runspec/auditlog.py +0 -0
  9. {runspec-0.76.1 → runspec-0.76.2}/runspec/become.py +0 -0
  10. {runspec-0.76.1 → runspec-0.76.2}/runspec/cli.py +0 -0
  11. {runspec-0.76.1 → runspec-0.76.2}/runspec/denylist.py +0 -0
  12. {runspec-0.76.1 → runspec-0.76.2}/runspec/discovery.py +0 -0
  13. {runspec-0.76.1 → runspec-0.76.2}/runspec/emitutil.py +0 -0
  14. {runspec-0.76.1 → runspec-0.76.2}/runspec/env.py +0 -0
  15. {runspec-0.76.1 → runspec-0.76.2}/runspec/errors.py +0 -0
  16. {runspec-0.76.1 → runspec-0.76.2}/runspec/execute.py +0 -0
  17. {runspec-0.76.1 → runspec-0.76.2}/runspec/finder.py +0 -0
  18. {runspec-0.76.1 → runspec-0.76.2}/runspec/inbox.py +0 -0
  19. {runspec-0.76.1 → runspec-0.76.2}/runspec/inference.py +0 -0
  20. {runspec-0.76.1 → runspec-0.76.2}/runspec/jump.py +0 -0
  21. {runspec-0.76.1 → runspec-0.76.2}/runspec/loader.py +0 -0
  22. {runspec-0.76.1 → runspec-0.76.2}/runspec/logging_setup.py +0 -0
  23. {runspec-0.76.1 → runspec-0.76.2}/runspec/mock.py +0 -0
  24. {runspec-0.76.1 → runspec-0.76.2}/runspec/models.py +0 -0
  25. {runspec-0.76.1 → runspec-0.76.2}/runspec/parser.py +0 -0
  26. {runspec-0.76.1 → runspec-0.76.2}/runspec/py.typed +0 -0
  27. {runspec-0.76.1 → runspec-0.76.2}/runspec/rebrand.py +0 -0
  28. {runspec-0.76.1 → runspec-0.76.2}/runspec/rundeckgen.py +0 -0
  29. {runspec-0.76.1 → runspec-0.76.2}/runspec/runexec.py +0 -0
  30. {runspec-0.76.1 → runspec-0.76.2}/runspec/runsdb.py +0 -0
  31. {runspec-0.76.1 → runspec-0.76.2}/runspec/runspec.toml +0 -0
  32. {runspec-0.76.1 → runspec-0.76.2}/runspec/schemacheck.py +0 -0
  33. {runspec-0.76.1 → runspec-0.76.2}/runspec/serve.py +0 -0
  34. {runspec-0.76.1 → runspec-0.76.2}/runspec/stubgen.py +0 -0
  35. {runspec-0.76.1 → runspec-0.76.2}/runspec/testing.py +0 -0
  36. {runspec-0.76.1 → runspec-0.76.2}/runspec/types.py +0 -0
  37. {runspec-0.76.1 → runspec-0.76.2}/runspec/validator.py +0 -0
  38. {runspec-0.76.1 → runspec-0.76.2}/runspec.egg-info/SOURCES.txt +0 -0
  39. {runspec-0.76.1 → runspec-0.76.2}/runspec.egg-info/dependency_links.txt +0 -0
  40. {runspec-0.76.1 → runspec-0.76.2}/runspec.egg-info/entry_points.txt +0 -0
  41. {runspec-0.76.1 → runspec-0.76.2}/runspec.egg-info/requires.txt +0 -0
  42. {runspec-0.76.1 → runspec-0.76.2}/runspec.egg-info/top_level.txt +0 -0
  43. {runspec-0.76.1 → runspec-0.76.2}/setup.cfg +0 -0
  44. {runspec-0.76.1 → runspec-0.76.2}/tests/__init__.py +0 -0
  45. {runspec-0.76.1 → runspec-0.76.2}/tests/conftest.py +0 -0
  46. {runspec-0.76.1 → runspec-0.76.2}/tests/test_agent.py +0 -0
  47. {runspec-0.76.1 → runspec-0.76.2}/tests/test_ansiblegen.py +0 -0
  48. {runspec-0.76.1 → runspec-0.76.2}/tests/test_audit.py +0 -0
  49. {runspec-0.76.1 → runspec-0.76.2}/tests/test_become.py +0 -0
  50. {runspec-0.76.1 → runspec-0.76.2}/tests/test_cli_discover.py +0 -0
  51. {runspec-0.76.1 → runspec-0.76.2}/tests/test_cli_emit.py +0 -0
  52. {runspec-0.76.1 → runspec-0.76.2}/tests/test_cli_init.py +0 -0
  53. {runspec-0.76.1 → runspec-0.76.2}/tests/test_cli_test.py +0 -0
  54. {runspec-0.76.1 → runspec-0.76.2}/tests/test_denylist.py +0 -0
  55. {runspec-0.76.1 → runspec-0.76.2}/tests/test_discovery.py +0 -0
  56. {runspec-0.76.1 → runspec-0.76.2}/tests/test_env.py +0 -0
  57. {runspec-0.76.1 → runspec-0.76.2}/tests/test_exec.py +0 -0
  58. {runspec-0.76.1 → runspec-0.76.2}/tests/test_finder.py +0 -0
  59. {runspec-0.76.1 → runspec-0.76.2}/tests/test_inbox.py +0 -0
  60. {runspec-0.76.1 → runspec-0.76.2}/tests/test_inference.py +0 -0
  61. {runspec-0.76.1 → runspec-0.76.2}/tests/test_jump.py +0 -0
  62. {runspec-0.76.1 → runspec-0.76.2}/tests/test_loader.py +0 -0
  63. {runspec-0.76.1 → runspec-0.76.2}/tests/test_log_resilience.py +0 -0
  64. {runspec-0.76.1 → runspec-0.76.2}/tests/test_logging_setup.py +0 -0
  65. {runspec-0.76.1 → runspec-0.76.2}/tests/test_mock.py +0 -0
  66. {runspec-0.76.1 → runspec-0.76.2}/tests/test_models.py +0 -0
  67. {runspec-0.76.1 → runspec-0.76.2}/tests/test_parser.py +0 -0
  68. {runspec-0.76.1 → runspec-0.76.2}/tests/test_password.py +0 -0
  69. {runspec-0.76.1 → runspec-0.76.2}/tests/test_rebrand.py +0 -0
  70. {runspec-0.76.1 → runspec-0.76.2}/tests/test_run_as_enforce.py +0 -0
  71. {runspec-0.76.1 → runspec-0.76.2}/tests/test_run_summary.py +0 -0
  72. {runspec-0.76.1 → runspec-0.76.2}/tests/test_rundeckgen.py +0 -0
  73. {runspec-0.76.1 → runspec-0.76.2}/tests/test_runsdb.py +0 -0
  74. {runspec-0.76.1 → runspec-0.76.2}/tests/test_runsdb_events.py +0 -0
  75. {runspec-0.76.1 → runspec-0.76.2}/tests/test_runsdb_sync.py +0 -0
  76. {runspec-0.76.1 → runspec-0.76.2}/tests/test_schemacheck.py +0 -0
  77. {runspec-0.76.1 → runspec-0.76.2}/tests/test_secret_argv.py +0 -0
  78. {runspec-0.76.1 → runspec-0.76.2}/tests/test_serve.py +0 -0
  79. {runspec-0.76.1 → runspec-0.76.2}/tests/test_serve_e2e.py +0 -0
  80. {runspec-0.76.1 → runspec-0.76.2}/tests/test_stubgen.py +0 -0
  81. {runspec-0.76.1 → runspec-0.76.2}/tests/test_testing.py +0 -0
  82. {runspec-0.76.1 → runspec-0.76.2}/tests/test_types.py +0 -0
  83. {runspec-0.76.1 → runspec-0.76.2}/tests/test_typing.py +0 -0
  84. {runspec-0.76.1 → runspec-0.76.2}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runspec
3
- Version: 0.76.1
3
+ Version: 0.76.2
4
4
  Summary: A language-agnostic, TOML-based interface specification for anything runnable
5
5
  Author-email: Jason Finestone <jason@finestone.dev>
6
6
  Project-URL: Homepage, https://runspec.app
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "runspec"
3
- version = "0.76.1"
3
+ version = "0.76.2"
4
4
  description = "A language-agnostic, TOML-based interface specification for anything runnable"
5
5
  readme = "README.md"
6
6
  license = { file = "../../LICENSE" }
@@ -342,8 +342,38 @@ def resolve_run_id(run: str, run_ids: Iterable[str], runnable: str | None = None
342
342
 
343
343
  def runnables_with_logs(dirs: Iterable[Path]) -> list[str]:
344
344
  """Sorted names of the runnables that have log files in ``dirs`` (per-run,
345
- archive or single-mode, including the inbox) — what ``runspec logs`` can show."""
346
- return sorted({_runnable_of(f) for f in _discover(dirs, None, include_inbox=True)} - {""})
345
+ archive or single-mode, including the inbox) — what ``runspec logs`` can show.
346
+
347
+ ``~/logs`` is shared with whatever else logs there, so a plain ``{name}.log``
348
+ counts only when it holds runspec records (see :func:`_is_runspec_log`);
349
+ per-run files and archives are known by their names."""
350
+ names: set[str] = set()
351
+ for f in _discover(dirs, None, include_inbox=True):
352
+ name = _runnable_of(f)
353
+ if name and name not in names and (_is_managed(f) or _is_runspec_log(f)):
354
+ names.add(name)
355
+ return sorted(names)
356
+
357
+
358
+ def _is_runspec_log(path: Path, *, max_lines: int = 5) -> bool:
359
+ """True when one of the first ``max_lines`` lines of ``path`` is a runspec
360
+ JSON record — an object with ``ts``/``level``/``message`` and an
361
+ ``extra.run_id``, which runspec stamps on every record it writes."""
362
+ try:
363
+ with path.open("r", encoding="utf-8", errors="replace") as fh:
364
+ for _ in range(max_lines):
365
+ line = fh.readline(65536)
366
+ if not line:
367
+ return False
368
+ try:
369
+ rec = json.loads(line)
370
+ except ValueError:
371
+ continue
372
+ if isinstance(rec, dict) and {"ts", "level", "message"} <= rec.keys() and _line_extra(rec).get("run_id"):
373
+ return True
374
+ except OSError:
375
+ pass
376
+ return False
347
377
 
348
378
 
349
379
  def _file_run_id(name: str) -> str | None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: runspec
3
- Version: 0.76.1
3
+ Version: 0.76.2
4
4
  Summary: A language-agnostic, TOML-based interface specification for anything runnable
5
5
  Author-email: Jason Finestone <jason@finestone.dev>
6
6
  Project-URL: Homepage, https://runspec.app
@@ -134,8 +134,13 @@ class TestView:
134
134
  def test_runnables_with_logs(self, tmp_path):
135
135
  _write_run(tmp_path, "deploy", "r1", "20260601T100000Z", _summary("r1", "deploy", ts="2026-06-01T10:00:00+00:00"))
136
136
  _write_run(tmp_path, "backup.web", "r2", "20260601T100000Z", _summary("r2", "backup.web", ts="2026-06-01T10:00:00+00:00"))
137
- (tmp_path / "single.log").write_text("", encoding="utf-8")
137
+ (tmp_path / "single.log").write_text(_line(_id("r3"), "hi", ts="2026-06-01T10:00:00+00:00") + "\n", encoding="utf-8")
138
138
  (tmp_path / "audit.jsonl").write_text("", encoding="utf-8")
139
+ # Other scripts' logs (shared ~/logs) aren't runnables: plain text, JSON
140
+ # without a run id, empty.
141
+ (tmp_path / "backup-script.log").write_text("2026-06-01 10:00 starting backup\n", encoding="utf-8")
142
+ (tmp_path / "other.log").write_text(json.dumps({"ts": "x", "level": "INFO", "message": "m"}) + "\n", encoding="utf-8")
143
+ (tmp_path / "empty.log").write_text("", encoding="utf-8")
139
144
  assert logs.runnables_with_logs([tmp_path]) == ["backup.web", "deploy", "single"]
140
145
 
141
146
  def test_json_passthrough(self, tmp_path):
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