labnotes 0.0.49__tar.gz → 0.0.50__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 (99) hide show
  1. {labnotes-0.0.49 → labnotes-0.0.50}/PKG-INFO +1 -1
  2. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/create_app.py +2 -0
  3. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/experiment_runs.py +4 -1
  4. labnotes-0.0.50/src/labnotes/format_duration.py +5 -0
  5. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/load_experiment.py +1 -1
  6. labnotes-0.0.50/src/labnotes/static/experiment.js +61 -0
  7. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/templates/experiment.html +1 -1
  8. labnotes-0.0.49/src/labnotes/static/experiment.js +0 -21
  9. {labnotes-0.0.49 → labnotes-0.0.50}/.gitignore +0 -0
  10. {labnotes-0.0.49 → labnotes-0.0.50}/CHANGELOG.md +0 -0
  11. {labnotes-0.0.49 → labnotes-0.0.50}/MIGRATIONS.md +0 -0
  12. {labnotes-0.0.49 → labnotes-0.0.50}/README.md +0 -0
  13. {labnotes-0.0.49 → labnotes-0.0.50}/pyproject.toml +0 -0
  14. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/__init__.py +0 -0
  15. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/asset_url.py +0 -0
  16. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/cell_progress.py +0 -0
  17. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/check_counts.py +0 -0
  18. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/cli.py +0 -0
  19. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/context.md +0 -0
  20. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/context_guide.py +0 -0
  21. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/count_runs.py +0 -0
  22. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/create_experiment.py +0 -0
  23. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/data_html.py +0 -0
  24. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/default_config_text.py +0 -0
  25. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/default_counts.py +0 -0
  26. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/drain_watch.py +0 -0
  27. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/experiment_notes.py +0 -0
  28. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/experiment_progress.py +0 -0
  29. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/experiments.py +0 -0
  30. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/format_value.py +0 -0
  31. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/init.py +0 -0
  32. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/is_positive_int.py +0 -0
  33. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/list_experiments.py +0 -0
  34. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/list_runs.py +0 -0
  35. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/load_config.py +0 -0
  36. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/models.py +0 -0
  37. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/modes.py +0 -0
  38. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/notebook_kernel.py +0 -0
  39. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/output_html.py +0 -0
  40. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/parse_run_view.py +0 -0
  41. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/print_rows.py +0 -0
  42. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/python_blocks.py +0 -0
  43. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/read_config.py +0 -0
  44. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/read_path.py +0 -0
  45. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/render_markdown.py +0 -0
  46. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/__init__.py +0 -0
  47. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/describe_event.py +0 -0
  48. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/execute_run.py +0 -0
  49. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/hold_folder.py +0 -0
  50. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/new_run_id.py +0 -0
  51. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  52. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/param_args.py +0 -0
  53. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/param_grid.py +0 -0
  54. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/plan_runs.py +0 -0
  55. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/read_experiment_config.py +0 -0
  56. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/run_experiment.py +0 -0
  57. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/run_folder.py +0 -0
  58. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/run_headless.py +0 -0
  59. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/runs_parent.py +0 -0
  60. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/shell_command.py +0 -0
  61. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/start_experiment.py +0 -0
  62. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/stop_then_kill.py +0 -0
  63. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/wait_for_group.py +0 -0
  64. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/runner/write_run_json.py +0 -0
  65. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/saved_outputs.py +0 -0
  66. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/serve_app.py +0 -0
  67. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/server.py +0 -0
  68. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/sort_href.py +0 -0
  69. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/sql_quote.py +0 -0
  70. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/static/labnotes.css +0 -0
  71. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/static/row-link.js +0 -0
  72. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/static/run-cell.js +0 -0
  73. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/static/view-toggle.js +0 -0
  74. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/stop_idle_kernels.py +0 -0
  75. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/summarize_config.py +0 -0
  76. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/__init__.py +0 -0
  77. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/experiment_rows.py +0 -0
  78. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/file_prefix.py +0 -0
  79. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/iso_time.py +0 -0
  80. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/list_experiment_runs.py +0 -0
  81. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/open_tables.py +0 -0
  82. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/path_glob.py +0 -0
  83. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/path_pattern.py +0 -0
  84. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/read_experiment.py +0 -0
  85. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/read_file.py +0 -0
  86. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/read_run.py +0 -0
  87. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tables/summarize_experiments.py +0 -0
  88. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/templates/base.html +0 -0
  89. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/templates/index.html +0 -0
  90. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/templates/macros.html +0 -0
  91. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tui/__init__.py +0 -0
  92. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tui/dim.py +0 -0
  93. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tui/format_elapsed.py +0 -0
  94. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tui/run_app.py +0 -0
  95. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tui/run_tui.py +0 -0
  96. {labnotes-0.0.49 → labnotes-0.0.50}/src/labnotes/tui/runs_table.py +0 -0
  97. {labnotes-0.0.49 → labnotes-0.0.50}/testing-conventions.toml +0 -0
  98. {labnotes-0.0.49 → labnotes-0.0.50}/tests/integration/conftest.py +0 -0
  99. {labnotes-0.0.49 → labnotes-0.0.50}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.49
3
+ Version: 0.0.50
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>
@@ -14,6 +14,7 @@ from starlette.templating import Jinja2Templates
14
14
  from labnotes.asset_url import asset_url
15
15
  from labnotes.drain_watch import drain_watch
16
16
  from labnotes.experiment_notes import experiment_notes
17
+ from labnotes.format_duration import format_duration
17
18
  from labnotes.format_value import format_value
18
19
  from labnotes.list_experiments import list_experiments
19
20
  from labnotes.load_experiment import load_experiment
@@ -27,6 +28,7 @@ from labnotes.tables.open_tables import open_tables
27
28
  HERE = Path(__file__).parent
28
29
  templates = Jinja2Templates(directory=HERE / "templates")
29
30
  templates.env.filters["value"] = format_value
31
+ templates.env.filters["duration"] = format_duration
30
32
  templates.env.globals["asset"] = asset_url
31
33
 
32
34
 
@@ -1,5 +1,8 @@
1
1
  from labnotes.sql_quote import quote_identifier, quote_string
2
2
 
3
+ STARTED = "strftime('%Y-%m-%d %H:%M:%S', created)"
4
+ DURATION = "CAST(round((julianday(ended) - julianday(created)) * 86400) AS INTEGER)"
5
+
3
6
 
4
7
  def experiment_runs(experiment: str, params: list[str]) -> str:
5
8
  columns = "".join(
@@ -8,6 +11,6 @@ def experiment_runs(experiment: str, params: list[str]) -> str:
8
11
  for key in params
9
12
  )
10
13
  return (
11
- f"(SELECT *{columns} FROM runs"
14
+ f"(SELECT *{columns}, {STARTED} AS started, {DURATION} AS duration FROM runs"
12
15
  f" WHERE experiment = {quote_string(experiment)})"
13
16
  )
@@ -0,0 +1,5 @@
1
+ from labnotes.tui.format_elapsed import format_elapsed
2
+
3
+
4
+ def format_duration(seconds: int | None) -> str:
5
+ return "—" if seconds is None else format_elapsed(seconds)
@@ -42,7 +42,7 @@ async def load_experiment(
42
42
  varies = cast(dict[str, list[object]], summary["varies"])
43
43
  source = experiment_runs(name, list(varies))
44
44
  summary = await check_counts(tables, source, summary)
45
- shown = ["id", *(key for key in varies if key != "id")]
45
+ shown = ["id", *(key for key in varies if key != "id"), "started", "duration"]
46
46
  view = parse_run_view(params, shown)
47
47
  [totals] = await count_runs(tables, source, str(summary["completed_run"]))
48
48
  notes = await experiment_notes(tables, root, name)
@@ -0,0 +1,61 @@
1
+ const summary = document.querySelector(".config-summary");
2
+ const open = document.querySelector(".config-open");
3
+ const dialog = document.querySelector(".config-file");
4
+
5
+ if (summary) {
6
+ const fit = () => {
7
+ open.hidden = summary.scrollWidth <= summary.clientWidth;
8
+ };
9
+ new ResizeObserver(fit).observe(summary);
10
+ open.addEventListener("click", () => dialog.showModal());
11
+ dialog.querySelector(".config-close").addEventListener("click", () => dialog.close());
12
+ }
13
+
14
+ document.addEventListener("click", (event) => {
15
+ const row = event.target.closest(".run-row");
16
+ if (!row || getSelection().toString()) return;
17
+ const record = row.nextElementSibling;
18
+ record.hidden = !record.hidden;
19
+ row.setAttribute("aria-expanded", String(!record.hidden));
20
+ });
21
+
22
+ const filters = document.querySelector("#run-filters");
23
+
24
+ if (filters) {
25
+ const runs = document.querySelector("#runs");
26
+ let pending;
27
+ let latest;
28
+
29
+ const refresh = async () => {
30
+ clearTimeout(pending);
31
+ const params = new URLSearchParams();
32
+ for (const [key, value] of new FormData(filters)) {
33
+ if (value) params.append(key, value);
34
+ }
35
+ const url = new URL(location.href);
36
+ url.search = params;
37
+ latest = url.href;
38
+ const response = await fetch(url);
39
+ if (latest !== url.href) return;
40
+ const fresh = new DOMParser()
41
+ .parseFromString(await response.text(), "text/html")
42
+ .querySelector("#runs");
43
+ runs.querySelector(".narrowed")?.remove();
44
+ const narrowed = fresh.querySelector(".narrowed");
45
+ if (narrowed) runs.prepend(narrowed);
46
+ for (const part of ["thead tr:first-child", "tbody"]) {
47
+ runs.querySelector(part).replaceWith(fresh.querySelector(part));
48
+ }
49
+ history.replaceState(null, "", url);
50
+ };
51
+
52
+ filters.addEventListener("submit", (event) => {
53
+ event.preventDefault();
54
+ refresh();
55
+ });
56
+ document.addEventListener("input", (event) => {
57
+ if (event.target.form !== filters || event.target.type !== "search") return;
58
+ clearTimeout(pending);
59
+ pending = setTimeout(refresh, 250);
60
+ });
61
+ }
@@ -78,7 +78,7 @@
78
78
  {% for run in e.runs_list %}
79
79
  <tr class="run-row" aria-expanded="false">
80
80
  <td><span class="status {{ 'good' if run.labnotes_completed else 'bad' }}" title="{{ 'complete' if run.labnotes_completed else 'not complete' }}">{{ '✓' if run.labnotes_completed else '✕' }}</span></td>
81
- {% for column in e.run_columns %}<td class="{{ 'mono' if loop.first }}">{{ run[column] | value }}</td>{% endfor %}
81
+ {% for column in e.run_columns %}<td class="{{ 'mono' if loop.first }}{{ 'num' if column == 'duration' }}">{{ run[column] | duration if column == 'duration' else run[column] | value }}</td>{% endfor %}
82
82
  </tr>
83
83
  <tr class="record-row" hidden>
84
84
  <td colspan="{{ e.run_columns | length + 1 }}">
@@ -1,21 +0,0 @@
1
- const summary = document.querySelector(".config-summary");
2
- const open = document.querySelector(".config-open");
3
- const dialog = document.querySelector(".config-file");
4
-
5
- if (summary) {
6
- const fit = () => {
7
- open.hidden = summary.scrollWidth <= summary.clientWidth;
8
- };
9
- new ResizeObserver(fit).observe(summary);
10
- open.addEventListener("click", () => dialog.showModal());
11
- dialog.querySelector(".config-close").addEventListener("click", () => dialog.close());
12
- }
13
-
14
- for (const row of document.querySelectorAll(".run-row")) {
15
- row.addEventListener("click", () => {
16
- if (getSelection().toString()) return;
17
- const record = row.nextElementSibling;
18
- record.hidden = !record.hidden;
19
- row.setAttribute("aria-expanded", String(!record.hidden));
20
- });
21
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes