labnotes 0.0.67__tar.gz → 0.0.69__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 (113) hide show
  1. {labnotes-0.0.67 → labnotes-0.0.69}/PKG-INFO +1 -1
  2. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/create_app.py +8 -0
  3. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/experiment_notes.py +2 -2
  4. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/read_experiment_config.py +4 -3
  5. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/run_experiment.py +6 -6
  6. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/start_experiment.py +5 -2
  7. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/static/note-edit.js +1 -4
  8. labnotes-0.0.69/src/labnotes/tables/experiment_run_ids.py +11 -0
  9. labnotes-0.0.69/src/labnotes/tables/file_text.py +12 -0
  10. labnotes-0.0.69/src/labnotes/tables/note_text.py +10 -0
  11. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/open_tables.py +1 -1
  12. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/read_file.py +2 -0
  13. labnotes-0.0.69/src/labnotes/wait_for_note.py +20 -0
  14. {labnotes-0.0.67 → labnotes-0.0.69}/.gitignore +0 -0
  15. {labnotes-0.0.67 → labnotes-0.0.69}/CHANGELOG.md +0 -0
  16. {labnotes-0.0.67 → labnotes-0.0.69}/MIGRATIONS.md +0 -0
  17. {labnotes-0.0.67 → labnotes-0.0.69}/README.md +0 -0
  18. {labnotes-0.0.67 → labnotes-0.0.69}/pyproject.toml +0 -0
  19. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/__init__.py +0 -0
  20. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/asset_url.py +0 -0
  21. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/check_counts.py +0 -0
  22. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/cli.py +0 -0
  23. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/context.md +0 -0
  24. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/context_guide.py +0 -0
  25. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/count_runs.py +0 -0
  26. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/create_experiment.py +0 -0
  27. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/create_note.py +0 -0
  28. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/data_html.py +0 -0
  29. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/default_config_text.py +0 -0
  30. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/default_counts.py +0 -0
  31. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/drain_watch.py +0 -0
  32. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/experiment_runs.py +0 -0
  33. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/experiments.py +0 -0
  34. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/file_changes.py +0 -0
  35. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/format_duration.py +0 -0
  36. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/format_value.py +0 -0
  37. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/group_runs.py +0 -0
  38. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/init.py +0 -0
  39. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/invocation_started.py +0 -0
  40. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/is_positive_int.py +0 -0
  41. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/list_experiments.py +0 -0
  42. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/list_runs.py +0 -0
  43. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/load_config.py +0 -0
  44. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/load_experiment.py +0 -0
  45. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/models.py +0 -0
  46. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/modes.py +0 -0
  47. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/nests_runs.py +0 -0
  48. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/note_path.py +0 -0
  49. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/notebook_kernel.py +0 -0
  50. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/output_html.py +0 -0
  51. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/parse_run_view.py +0 -0
  52. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/print_rows.py +0 -0
  53. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/python_blocks.py +0 -0
  54. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/read_config.py +0 -0
  55. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/read_path.py +0 -0
  56. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/render_markdown.py +0 -0
  57. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/__init__.py +0 -0
  58. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/describe_event.py +0 -0
  59. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/execute_run.py +0 -0
  60. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/hold_folder.py +0 -0
  61. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/new_param_run_id.py +0 -0
  62. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/new_run_id.py +0 -0
  63. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/new_stamped_run_id.py +0 -0
  64. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/param_args.py +0 -0
  65. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/param_grid.py +0 -0
  66. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/param_run_name.py +0 -0
  67. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/plan_runs.py +0 -0
  68. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/run_folder.py +0 -0
  69. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/run_headless.py +0 -0
  70. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/runs_parent.py +0 -0
  71. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/shell_command.py +0 -0
  72. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/stop_then_kill.py +0 -0
  73. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/wait_for_group.py +0 -0
  74. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/runner/write_run_json.py +0 -0
  75. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/save_conflict_copy.py +0 -0
  76. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/save_note.py +0 -0
  77. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/saved_outputs.py +0 -0
  78. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/serve_app.py +0 -0
  79. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/server.py +0 -0
  80. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/sort_href.py +0 -0
  81. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/sql_quote.py +0 -0
  82. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/static/experiment.js +0 -0
  83. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/static/labnotes.css +0 -0
  84. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/static/new-note.js +0 -0
  85. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/static/note-editor.js +0 -0
  86. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/static/row-link.js +0 -0
  87. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/static/run-cell.js +0 -0
  88. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/static/view-toggle.js +0 -0
  89. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/summarize_config.py +0 -0
  90. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/__init__.py +0 -0
  91. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/experiment_rows.py +0 -0
  92. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/file_prefix.py +0 -0
  93. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/iso_time.py +0 -0
  94. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/list_experiment_runs.py +0 -0
  95. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/path_glob.py +0 -0
  96. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/path_pattern.py +0 -0
  97. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/read_experiment.py +0 -0
  98. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/read_run.py +0 -0
  99. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tables/summarize_experiments.py +0 -0
  100. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/templates/base.html +0 -0
  101. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/templates/editor_imports.html +0 -0
  102. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/templates/experiment.html +0 -0
  103. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/templates/index.html +0 -0
  104. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/templates/note.html +0 -0
  105. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tui/__init__.py +0 -0
  106. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tui/dim.py +0 -0
  107. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tui/format_elapsed.py +0 -0
  108. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tui/run_app.py +0 -0
  109. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tui/run_tui.py +0 -0
  110. {labnotes-0.0.67 → labnotes-0.0.69}/src/labnotes/tui/runs_table.py +0 -0
  111. {labnotes-0.0.67 → labnotes-0.0.69}/testing-conventions.toml +0 -0
  112. {labnotes-0.0.67 → labnotes-0.0.69}/tests/integration/conftest.py +0 -0
  113. {labnotes-0.0.67 → labnotes-0.0.69}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: labnotes
3
- Version: 0.0.67
3
+ Version: 0.0.69
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>
@@ -36,6 +36,7 @@ from labnotes.save_conflict_copy import save_conflict_copy
36
36
  from labnotes.save_note import save_note
37
37
  from labnotes.saved_outputs import saved_outputs
38
38
  from labnotes.tables.open_tables import open_tables
39
+ from labnotes.wait_for_note import wait_for_note
39
40
 
40
41
  HERE = Path(__file__).parent
41
42
  templates = Jinja2Templates(directory=HERE / "templates")
@@ -149,6 +150,13 @@ def create_app(config: LabnotesConfig) -> Starlette:
149
150
  current = save_note(path, base, content)
150
151
  if current is not None:
151
152
  return JSONResponse({"content": current}, status_code=409)
153
+ await wait_for_note(
154
+ request.app.state.tables,
155
+ config.root,
156
+ request.path_params["name"],
157
+ request.path_params["note"],
158
+ content,
159
+ )
152
160
  return Response(status_code=204)
153
161
 
154
162
  async def post_conflict(request: Request) -> Response:
@@ -12,13 +12,13 @@ async def experiment_notes(
12
12
  if note is not None:
13
13
  where += f" AND filename = {quote_string(note)}"
14
14
  rows = await tables.query(
15
- f"SELECT filename, path FROM files WHERE {where} ORDER BY filename"
15
+ f"SELECT filename, path, content FROM files WHERE {where} ORDER BY filename"
16
16
  )
17
17
  return [
18
18
  {
19
19
  "name": str(row["filename"]),
20
20
  "path": str(root / str(row["path"])),
21
- "content": (root / str(row["path"])).read_text(encoding="utf-8"),
21
+ "content": str(row["content"] or ""),
22
22
  }
23
23
  for row in rows
24
24
  ]
@@ -10,10 +10,11 @@ from labnotes.modes import MODES
10
10
  SETTINGS = {"name", "command", "repeats", "params", "counts", "mode", "concurrency"}
11
11
 
12
12
 
13
- def read_experiment_config(path: Path, mode: str, concurrency: int) -> ExperimentConfig:
13
+ def read_experiment_config(
14
+ path: Path, source: str, mode: str, concurrency: int
15
+ ) -> ExperimentConfig:
14
16
  try:
15
- with path.open("rb") as file:
16
- values = tomllib.load(file)
17
+ values = tomllib.loads(source)
17
18
  except tomllib.TOMLDecodeError as error:
18
19
  raise ValueError(f"{path}: {error}") from error
19
20
  unknown = sorted(values.keys() - SETTINGS)
@@ -17,6 +17,7 @@ from labnotes.runner.new_stamped_run_id import new_stamped_run_id
17
17
  from labnotes.runner.param_grid import param_grid
18
18
  from labnotes.runner.plan_runs import plan_runs
19
19
  from labnotes.runner.run_folder import run_folder
20
+ from labnotes.tables.experiment_run_ids import experiment_run_ids
20
21
  from labnotes.tables.list_experiment_runs import list_experiment_runs
21
22
  from labnotes.tables.open_tables import open_tables
22
23
 
@@ -31,18 +32,17 @@ async def run_experiment(
31
32
  report: Report,
32
33
  listen: Listen,
33
34
  ) -> list[dict[str, object]]:
35
+ tables = open_tables(config)
36
+ existing = await experiment_run_ids(tables, name)
34
37
  finished: list[dict[str, object]] = []
35
38
  match experiment.mode:
36
39
  case "fill":
37
- tables = open_tables(config)
38
- existing = await list_experiment_runs(tables, name, experiment.counts)
39
- finished = [json.loads(str(r["params"])) for r in existing if r["counted"]]
40
+ counted = await list_experiment_runs(tables, name, experiment.counts)
41
+ finished = [json.loads(str(r["params"])) for r in counted if r["counted"]]
40
42
  planned: set[str] = set()
41
43
 
42
44
  def taken(run: str) -> bool:
43
- return (
44
- run in planned or run_folder(config.root, config.runs, name, run).exists()
45
- )
45
+ return run in planned or run in existing
46
46
 
47
47
  group = new_stamped_run_id(taken) if experiment.mode == "adhoc" else None
48
48
 
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  from collections.abc import Callable
2
3
  from functools import partial
3
4
  from pathlib import Path
@@ -7,6 +8,7 @@ from labnotes.runner.hold_folder import hold_folder
7
8
  from labnotes.runner.read_experiment_config import read_experiment_config
8
9
  from labnotes.runner.run_experiment import run_experiment
9
10
  from labnotes.runner.runs_parent import runs_parent
11
+ from labnotes.tables.file_text import file_text
10
12
 
11
13
 
12
14
  def start_experiment(
@@ -19,10 +21,11 @@ def start_experiment(
19
21
  if experiment_config is None:
20
22
  folder = config.experiments.replace("{experiment}", name)
21
23
  experiment_config = config.root / folder / "experiment.toml"
22
- if not experiment_config.is_file():
24
+ source = asyncio.run(file_text(config.root, experiment_config))
25
+ if source is None:
23
26
  raise ValueError(f"No experiment.toml for {name} at {experiment_config}.")
24
27
  experiment = read_experiment_config(
25
- experiment_config, config.mode, config.concurrency
28
+ experiment_config, source, config.mode, config.concurrency
26
29
  )
27
30
  if concurrency is None:
28
31
  concurrency = experiment.concurrency
@@ -11,10 +11,7 @@ export function attach(note, openNow) {
11
11
 
12
12
  editor.then(
13
13
  () => prose.classList.add("editable"),
14
- () => {
15
- status.textContent = "Editing needs the network: the editor loads from esm.sh.";
16
- status.hidden = false;
17
- },
14
+ () => {},
18
15
  );
19
16
 
20
17
  const open = async (event) => {
@@ -0,0 +1,11 @@
1
+ from dirsql import DirSQL
2
+
3
+ from labnotes.sql_quote import quote_string
4
+
5
+
6
+ async def experiment_run_ids(tables: DirSQL, name: str) -> set[str]:
7
+ rows = await tables.query(
8
+ f"SELECT id FROM runs WHERE experiment = {quote_string(name)}"
9
+ )
10
+ ids = {str(row["id"]) for row in rows}
11
+ return ids | {run.split("/")[0] for run in ids}
@@ -0,0 +1,12 @@
1
+ from pathlib import Path
2
+
3
+ from dirsql import DirSQL
4
+
5
+ from labnotes.sql_quote import quote_string
6
+
7
+
8
+ async def file_text(root: Path, path: Path) -> str | None:
9
+ rows = await DirSQL(str(root), tables=[], no_ignore=True).query(
10
+ f"SELECT content FROM {quote_string(str(path))}"
11
+ )
12
+ return next((str(row["content"] or "") for row in rows), None)
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+
4
+ def note_text(path: Path) -> str | None:
5
+ if path.suffix != ".md":
6
+ return None
7
+ try:
8
+ return path.read_text(encoding="utf-8")
9
+ except UnicodeDecodeError:
10
+ return None
@@ -29,7 +29,7 @@ CREATE VIEW experiments AS
29
29
  FILES = """
30
30
  CREATE TABLE files (
31
31
  experiment TEXT, filename TEXT, path TEXT PRIMARY KEY, ctime TEXT, mtime TEXT,
32
- mtime_ns INTEGER
32
+ mtime_ns INTEGER, content TEXT
33
33
  )
34
34
  """
35
35
  RUNS = """
@@ -2,6 +2,7 @@ from pathlib import Path
2
2
 
3
3
  from labnotes.tables.file_prefix import file_prefix
4
4
  from labnotes.tables.iso_time import iso_time
5
+ from labnotes.tables.note_text import note_text
5
6
  from labnotes.tables.path_pattern import path_pattern
6
7
 
7
8
 
@@ -20,5 +21,6 @@ def read_file(root: Path, template: str, path: str) -> list[dict[str, object]]:
20
21
  "ctime": iso_time(stat.st_ctime),
21
22
  "mtime": iso_time(stat.st_mtime),
22
23
  "mtime_ns": stat.st_mtime_ns,
24
+ "content": note_text(Path(path)),
23
25
  }
24
26
  ]
@@ -0,0 +1,20 @@
1
+ import asyncio
2
+ from pathlib import Path
3
+
4
+ from dirsql import DirSQL
5
+
6
+ from labnotes.experiment_notes import experiment_notes
7
+
8
+
9
+ async def wait_for_note(
10
+ tables: DirSQL,
11
+ root: Path,
12
+ experiment: str,
13
+ note: str,
14
+ content: str | None = None,
15
+ ) -> None:
16
+ for _ in range(40):
17
+ found = await experiment_notes(tables, root, experiment, note)
18
+ if found and (content is None or found[0]["content"] == content):
19
+ return
20
+ await asyncio.sleep(0.05)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes