labnotes 0.0.79__tar.gz → 0.0.80__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 (145) hide show
  1. {labnotes-0.0.79 → labnotes-0.0.80}/PKG-INFO +1 -1
  2. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/context.md +3 -3
  3. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/group_runs.py +6 -1
  4. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/run_experiment.py +6 -4
  5. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/labnotes.css +1 -0
  6. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/templates/experiment.html +4 -0
  7. {labnotes-0.0.79 → labnotes-0.0.80}/tests/integration/conftest.py +14 -0
  8. {labnotes-0.0.79 → labnotes-0.0.80}/.gitignore +0 -0
  9. {labnotes-0.0.79 → labnotes-0.0.80}/CHANGELOG.md +0 -0
  10. {labnotes-0.0.79 → labnotes-0.0.80}/MIGRATIONS.md +0 -0
  11. {labnotes-0.0.79 → labnotes-0.0.80}/README.md +0 -0
  12. {labnotes-0.0.79 → labnotes-0.0.80}/pyproject.toml +0 -0
  13. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/__init__.py +0 -0
  14. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/__main__.py +0 -0
  15. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/asset_url.py +0 -0
  16. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/check_counts.py +0 -0
  17. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/cli.py +0 -0
  18. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/context_guide.py +0 -0
  19. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/count_groups.py +0 -0
  20. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/count_runs.py +0 -0
  21. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/create_app.py +0 -0
  22. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/create_experiment.py +0 -0
  23. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/create_note.py +0 -0
  24. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/__init__.py +0 -0
  25. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/control_run.py +0 -0
  26. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/daemon_app.py +0 -0
  27. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/daemon_client.py +0 -0
  28. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/daemon_running.py +0 -0
  29. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/daemon_server.py +0 -0
  30. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/describe_status.py +0 -0
  31. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/ensure_daemon.py +0 -0
  32. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/exit_when_idle.py +0 -0
  33. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/remote_start.py +0 -0
  34. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/run_daemon.py +0 -0
  35. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/run_logs.py +0 -0
  36. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/serve_daemon.py +0 -0
  37. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/socket_path.py +0 -0
  38. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/stop_runs.py +0 -0
  39. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/watch_daemon.py +0 -0
  40. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon/watch_status.py +0 -0
  41. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/daemon_command.py +0 -0
  42. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/data_html.py +0 -0
  43. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/default_config_text.py +0 -0
  44. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/default_counts.py +0 -0
  45. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/drain_watch.py +0 -0
  46. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/experiment_notes.py +0 -0
  47. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/experiment_runs.py +0 -0
  48. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/experiments.py +0 -0
  49. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/file_changes.py +0 -0
  50. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/format_duration.py +0 -0
  51. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/format_started.py +0 -0
  52. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/format_value.py +0 -0
  53. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/init.py +0 -0
  54. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/invocation_started.py +0 -0
  55. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/is_positive_int.py +0 -0
  56. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/list_experiments.py +0 -0
  57. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/list_runs.py +0 -0
  58. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/load_config.py +0 -0
  59. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/load_experiment.py +0 -0
  60. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/load_run.py +0 -0
  61. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/models.py +0 -0
  62. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/modes.py +0 -0
  63. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/nests_runs.py +0 -0
  64. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/note_path.py +0 -0
  65. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/notebook_kernel.py +0 -0
  66. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/output_html.py +0 -0
  67. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/parse_run_view.py +0 -0
  68. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/print_rows.py +0 -0
  69. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/python_blocks.py +0 -0
  70. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/read_config.py +0 -0
  71. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/read_path.py +0 -0
  72. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/render_markdown.py +0 -0
  73. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/run_command.py +0 -0
  74. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/run_control.py +0 -0
  75. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/run_output.py +0 -0
  76. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/run_status.py +0 -0
  77. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/__init__.py +0 -0
  78. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/describe_event.py +0 -0
  79. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/execute_run.py +0 -0
  80. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/hold_folder.py +0 -0
  81. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/new_param_run_id.py +0 -0
  82. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/new_run_id.py +0 -0
  83. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  84. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/param_args.py +0 -0
  85. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/param_grid.py +0 -0
  86. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/param_run_name.py +0 -0
  87. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/plan_runs.py +0 -0
  88. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/prepare_experiment.py +0 -0
  89. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/read_experiment_config.py +0 -0
  90. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/run_folder.py +0 -0
  91. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/run_headless.py +0 -0
  92. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/runs_parent.py +0 -0
  93. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/shell_command.py +0 -0
  94. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/signal_tree.py +0 -0
  95. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/stop_then_kill.py +0 -0
  96. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/wait_for_tree.py +0 -0
  97. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/runner/write_run_json.py +0 -0
  98. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/save_conflict_copy.py +0 -0
  99. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/save_note.py +0 -0
  100. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/saved_outputs.py +0 -0
  101. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/serve_app.py +0 -0
  102. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/server.py +0 -0
  103. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/shared_params.py +0 -0
  104. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/sort_href.py +0 -0
  105. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/sql_quote.py +0 -0
  106. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/experiment.js +0 -0
  107. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/local-time.js +0 -0
  108. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/new-note.js +0 -0
  109. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/note-edit.js +0 -0
  110. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/note-editor.js +0 -0
  111. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/row-link.js +0 -0
  112. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/run-cell.js +0 -0
  113. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/run-output.js +0 -0
  114. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/static/view-toggle.js +0 -0
  115. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/summarize_config.py +0 -0
  116. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/__init__.py +0 -0
  117. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/experiment_rows.py +0 -0
  118. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/experiment_run_ids.py +0 -0
  119. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/file_prefix.py +0 -0
  120. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/file_text.py +0 -0
  121. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/iso_time.py +0 -0
  122. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/list_experiment_runs.py +0 -0
  123. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/note_text.py +0 -0
  124. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/open_tables.py +0 -0
  125. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/path_glob.py +0 -0
  126. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/path_pattern.py +0 -0
  127. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/read_experiment.py +0 -0
  128. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/read_file.py +0 -0
  129. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/read_log.py +0 -0
  130. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/read_run.py +0 -0
  131. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tables/summarize_experiments.py +0 -0
  132. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/templates/base.html +0 -0
  133. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/templates/editor_imports.html +0 -0
  134. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/templates/index.html +0 -0
  135. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/templates/note.html +0 -0
  136. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/templates/run.html +0 -0
  137. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tui/__init__.py +0 -0
  138. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tui/dim.py +0 -0
  139. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tui/format_elapsed.py +0 -0
  140. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tui/run_app.py +0 -0
  141. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tui/run_tui.py +0 -0
  142. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/tui/runs_table.py +0 -0
  143. {labnotes-0.0.79 → labnotes-0.0.80}/src/labnotes/wait_for_note.py +0 -0
  144. {labnotes-0.0.79 → labnotes-0.0.80}/testing-conventions.toml +0 -0
  145. {labnotes-0.0.79 → labnotes-0.0.80}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.79
3
+ Version: 0.0.80
4
4
  Summary: Browse experiment designs, notes, and run data from a folder.
5
5
  Project-URL: Repository, https://github.com/thekevinscott/labnotes
6
6
  Author-email: Kevin Scott <me@thekevinscott.com>
@@ -63,9 +63,9 @@ so running again fills in what failed or never ran. `mode` under
63
63
  `[experiments]` in `.labnotes.toml` sets it for every experiment that doesn't
64
64
  set its own.
65
65
  Each run gets a new run folder. In `fill` mode it is named for the UTC time it was planned
66
- (`runs/20261020-000429-brave-fox`). In `adhoc` mode each `run` command gets one folder named
67
- that way, and its runs nest inside it (`runs/20261020-000429-brave-fox/quiet-river`); the run id
68
- is that path under `runs/`. Either way, folder listings sort by start. `command` runs in a shell from the root, with
66
+ (`runs/20261020-000429-brave-fox`). In `adhoc` mode a `run` command that plans one run names
67
+ its folder the same way. One that plans more gets one folder named that way, and its runs nest
68
+ inside it (`runs/20261020-000429-brave-fox/quiet-river`); the run id is that path under `runs/`. Either way, folder listings sort by start. `command` runs in a shell from the root, with
69
69
  `{run_dir}` replaced by the run folder's absolute path. Pass it to the
70
70
  script so it writes its outputs there.
71
71
  Each param is appended as `--name value`, with the name as written (use
@@ -10,11 +10,16 @@ def group_runs(
10
10
  totals: Mapping[str, int],
11
11
  ) -> list[dict[str, object]]:
12
12
  groups: dict[str, list[dict[str, object]]] = {}
13
+ flat: set[str] = set()
13
14
  for run in runs:
14
- groups.setdefault(str(run["id"]).partition("/")[0], []).append(run)
15
+ name, nested, _ = str(run["id"]).partition("/")
16
+ groups.setdefault(name, []).append(run)
17
+ if not nested:
18
+ flat.add(name)
15
19
  return [
16
20
  {
17
21
  "name": name,
22
+ "flat": name in flat,
18
23
  "started": invocation_started(name),
19
24
  "runs": members,
20
25
  "total": totals.get(name, len(members)),
@@ -45,7 +45,11 @@ async def run_experiment(
45
45
  def taken(run: str) -> bool:
46
46
  return run in planned or run in existing
47
47
 
48
- group = new_stamped_run_id(taken) if experiment.mode == "adhoc" else None
48
+ cells = plan_runs(param_grid(experiment.params), experiment.repeats, finished)
49
+ group = None
50
+ match experiment.mode:
51
+ case "adhoc" if len(cells) > 1:
52
+ group = new_stamped_run_id(taken)
49
53
 
50
54
  def new_id(params: dict[str, object]) -> str:
51
55
  if group is None:
@@ -55,9 +59,7 @@ async def run_experiment(
55
59
  )
56
60
 
57
61
  plan: list[Entry] = []
58
- for params in plan_runs(
59
- param_grid(experiment.params), experiment.repeats, finished
60
- ):
62
+ for params in cells:
61
63
  run = new_id(params)
62
64
  planned.add(run)
63
65
  plan.append((run, params))
@@ -295,6 +295,7 @@ h3 {
295
295
  .group-toggle[aria-expanded="true"]::before { content: "▾"; }
296
296
  .group:not(.open) > :not(.group-row) { display: none; }
297
297
  .group.open .run-row td:nth-child(2) { padding-left: 2rem; }
298
+ .lone .run-row td:nth-child(2) { padding-left: 1.75rem; }
298
299
  .run-row { cursor: pointer; }
299
300
  .run-link { text-decoration: none; }
300
301
 
@@ -88,6 +88,9 @@
88
88
  {% endmacro %}
89
89
  {% if nested %}
90
90
  {% for group in e.run_groups %}
91
+ {% if group.flat %}
92
+ <tbody class="lone">{{ run_rows(group.runs[0]) }}</tbody>
93
+ {% else %}
91
94
  <tbody class="group{{ ' open' if narrowed }}" data-name="{{ group.name }}">
92
95
  <tr class="group-row">
93
96
  <td class="num" title="{{ group.passed }} passed, {{ group.failed }} failed"><span class="good">{{ group.passed }}✓</span> <span class="bad">{{ group.failed }}✕</span></td>
@@ -97,6 +100,7 @@
97
100
  </tr>
98
101
  {% for run in group.runs %}{{ run_rows(run) }}{% endfor %}
99
102
  </tbody>
103
+ {% endif %}
100
104
  {% else %}
101
105
  <tbody><tr><td class="empty" colspan="{{ width }}">No runs match these filters.</td></tr></tbody>
102
106
  {% endfor %}
@@ -83,6 +83,20 @@ def browse(
83
83
  asyncio.run(drive())
84
84
 
85
85
 
86
+ @pytest.fixture
87
+ def tmp_path(tmp_path: Path) -> Path:
88
+ # Real project roots run long; pytest's short default hides path-length
89
+ # limits such as the 107-byte AF_UNIX socket path.
90
+ deep = (
91
+ tmp_path
92
+ / "a-fairly-long-folder-name-for-research"
93
+ / "and-another-nested-one"
94
+ / "experiments-and-their-notes-live-here"
95
+ )
96
+ deep.mkdir(parents=True)
97
+ return deep
98
+
99
+
86
100
  @pytest.fixture
87
101
  def site(tmp_path: Path) -> Iterator[str]:
88
102
  yield from serve(project(tmp_path))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes