Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions examples/load_tsqa.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Print a local dataset's summary and sample signal values.
"""Load a local TimeF dataset and print its summary and a sample.

Build the dataset into a local registry, then run this script::
Build the dataset into a local registry first, then run this script::

uv run timenet-build build "chengsenwang/tsqa" --out .timenet-registry
uv run timenet-build build chengsenwang/tsqa --local-registry .timenet-registry
uv run python examples/load_tsqa.py

Pass a dataset ID and a registry path as arguments to load another dataset.
Pass another dataset ID and registry path as the first and second arguments.
"""

from pathlib import Path
Expand Down
357 changes: 193 additions & 164 deletions packages/timenet-connectors/src/timenet_connectors/builder/cli.py

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
The connector imports the recordings and their patient metadata from the pinned PTB-XL TimeF
parent. The per-template answer options come from the ``Jwoo5/ecg-qa`` GitHub repo. The precomputed
CoT rows (question, answer, rationale, template) come from the OpenTSLM release, the only public
source for the rationales. Build the PTB-XL parent first, or pass ``--build-parents``.
source for the rationales. If the local registry lacks the PTB-XL parent, a build copies it from the remote
registry or builds it from its connector first.

The clinical context comes from the annotations of the imported PTB-XL records. It states the patient's
age and sex, the recording device, and the signal-quality issues, extra beats, and pacemaker that PTB-XL
Expand Down
31 changes: 20 additions & 11 deletions packages/timenet-connectors/tests/test_build_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_build_runs_isolated_by_default(monkeypatch, tmp_path):
calls = []
monkeypatch.setattr(backend_module, "run_isolated_build", _isolated_child(calls))

result = runner.invoke(app, ["build", "timenet/hello-world", "--out", str(tmp_path)])
result = runner.invoke(app, ["build", "timenet/hello-world", "--local-registry", str(tmp_path)])

assert result.exit_code == 0, result.output
assert [(call["release"], call["store_root"]) for call in calls] == [(HELLO, tmp_path)]
Expand All @@ -70,7 +70,14 @@ def test_build_runs_in_process_without_isolation(monkeypatch, tmp_path, how):
monkeypatch.setenv("TIMENET_ISOLATION", "off")

result = runner.invoke(
app, ["build", "timenet/hello-world", "--out", str(tmp_path), *(["--no-isolation"] if how == "flag" else [])]
app,
[
"build",
"timenet/hello-world",
"--local-registry",
str(tmp_path),
*(["--no-isolation"] if how == "flag" else []),
],
)

assert result.exit_code == 0, result.output
Expand All @@ -82,7 +89,7 @@ def test_build_isolates_when_the_flag_overrides_the_setting(monkeypatch, tmp_pat
calls = []
monkeypatch.setattr(backend_module, "run_isolated_build", _isolated_child(calls))

result = runner.invoke(app, ["build", "timenet/hello-world", "--out", str(tmp_path), "--isolation"])
result = runner.invoke(app, ["build", "timenet/hello-world", "--local-registry", str(tmp_path), "--isolation"])

assert result.exit_code == 0, result.output
assert len(calls) == 1
Expand All @@ -92,7 +99,9 @@ def test_build_without_isolation_shows_each_build_line_once(tmp_path):
# In this process, the coordinator sends the build step, and the renderer shows it. Nothing else
# prints a build line.

result = runner.invoke(app, ["build", "timenet/hello-world", "--out", str(tmp_path / "out"), "--no-isolation"])
result = runner.invoke(
app, ["build", "timenet/hello-world", "--local-registry", str(tmp_path / "out"), "--no-isolation"]
)

assert result.exit_code == 0, result.output
assert result.stderr.count("🔧 Building 'timenet/hello-world'…") == 1
Expand All @@ -104,7 +113,7 @@ def test_a_build_that_reuses_the_release_says_so_on_stderr(monkeypatch, tmp_path
monkeypatch.setattr(cli_module.console, "rich", Console(stderr=True, width=1000))
calls = []
monkeypatch.setattr(backend_module, "run_isolated_build", _isolated_child(calls))
command = ["build", "timenet/hello-world", "--out", str(tmp_path)]
command = ["build", "timenet/hello-world", "--local-registry", str(tmp_path)]
assert runner.invoke(app, command).exit_code == 0

result = runner.invoke(app, command)
Expand All @@ -119,11 +128,11 @@ def test_a_build_that_reuses_the_release_says_so_on_stderr(monkeypatch, tmp_path
def test_build_takes_an_exact_release_and_refuses_another_version(monkeypatch, tmp_path):
calls = []
monkeypatch.setattr(backend_module, "run_isolated_build", _isolated_child(calls))
result = runner.invoke(app, ["build", "timenet/hello-world@1.0.0", "--out", str(tmp_path)])
result = runner.invoke(app, ["build", "timenet/hello-world@1.0.0", "--local-registry", str(tmp_path)])

assert result.exit_code == 0, result.output
assert [call["release"] for call in calls] == [HELLO]
result = runner.invoke(app, ["build", "timenet/hello-world@9.9.9", "--out", str(tmp_path)])
result = runner.invoke(app, ["build", "timenet/hello-world@9.9.9", "--local-registry", str(tmp_path)])
assert isinstance(result.exception, TimeNetVersionUnavailableError)
assert len(calls) == 1

Expand All @@ -137,7 +146,7 @@ def test_build_rejects_an_unknown_id_without_importing_anything(monkeypatch, tmp
# a connector class by identity still sees the same object.
monkeypatch.delitem(sys.modules, name)

result = runner.invoke(app, ["build", "nope/nothing", "--out", str(tmp_path)])
result = runner.invoke(app, ["build", "nope/nothing", "--local-registry", str(tmp_path)])

assert result.exit_code != 0
assert "no connector for" in result.output
Expand All @@ -147,7 +156,7 @@ def test_build_rejects_an_unknown_id_without_importing_anything(monkeypatch, tmp

def test_build_rejects_an_id_that_no_card_declares_and_names_the_known_ids(tmp_path):
# The module path of the connector accepts this spelling, but a dataset ID must match its card.
result = runner.invoke(app, ["build", "timenet/hello_world", "--out", str(tmp_path)])
result = runner.invoke(app, ["build", "timenet/hello_world", "--local-registry", str(tmp_path)])

assert result.exit_code == 2
assert "no connector for 'timenet/hello_world'" in result.output
Expand All @@ -162,7 +171,7 @@ def test_build_forwards_quiet_to_the_isolated_child(monkeypatch, tmp_path, quiet
monkeypatch.setattr(backend_module, "run_isolated_build", _isolated_child(calls))

result = runner.invoke(
app, [*(["--quiet"] if quiet else []), "build", "timenet/hello-world", "--out", str(tmp_path)]
app, [*(["--quiet"] if quiet else []), "build", "timenet/hello-world", "--local-registry", str(tmp_path)]
)

assert result.exit_code == 0, result.output
Expand All @@ -180,7 +189,7 @@ def test_isolated_build_prints_the_version_dir_and_narrates_once(tmp_path):
env.pop("TIMENET_ISOLATION", None) # default is "on"; make sure nothing forces it off
run_cli = [sys.executable, "-c", "from timenet_connectors.builder.cli import main; main()"]
result = subprocess.run(
[*run_cli, "build", "timenet/hello-world", "--out", str(registry)],
[*run_cli, "build", "timenet/hello-world", "--local-registry", str(registry)],
env=env,
cwd=tmp_path, # away from the repo tree so the CLI does not read the repo .env
capture_output=True,
Expand Down
2 changes: 1 addition & 1 deletion packages/timenet-connectors/tests/test_build_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_uv_command_passes_the_requirements_file():


def test_uv_command_puts_the_child_argv_last():
argv = ["timenet-build", "build", "x/y", "--out", "/fake/reg"]
argv = ["timenet-build", "build", "x/y", "--local-registry", "/fake/reg"]
assert uv_command(_spec(), argv)[-len(argv) :] == argv


Expand Down
Loading
Loading