Skip to content

Make the docs and the demo data reachable from a pip install - #105

Merged
AlexisJanin merged 9 commits into
mainfrom
example-and-docs-for-pip-installs
Sep 17, 2026
Merged

AlexisJanin merged 9 commits into
mainfrom
example-and-docs-for-pip-installs

Conversation

@AlexisJanin

Copy link
Copy Markdown
Collaborator

The tutorial and the example are easy to find with a source checkout (the repo is on disk) and with the standalone application (assemble_bundle.py copies both in next to the executable). A pip install is the third channel, and it has neither: the wheel packages src/ and nothing else. This branch closes that gap.

What a pip user had before this

  • The PyPI project page is their only entry point, and its description is the README — whose six links were all repo-relative. PyPI resolves those against pypi.org, so every one of them 404'd and the demo GIF did not render.
  • The app itself offered no link to the tutorial.
  • Quickstart step 3 told them to point at demo_database/demo_patient/, a folder they do not have, with no alternative named.

Four commits

1. Make the documentation reachable — [project.urls] gains Documentation, Changelog and Issues (Warehouse renders these as the project page's sidebar); every README link is absolute; a grey 📖 Docs pill joins the version badge and settings gear in the top-right stack. A test fails on any relative link reintroduced into the README.

2. Publish the example dataset as a release asset — an example job in build.yml zips example/ into clinical-scope-example.zip and attaches it to the same draft release as the executables. The tree is staged through a filtered copy first, so the per-run parquet cache and macOS metadata stay out: 2.2 MB rather than the 10 MB an unfiltered archive would carry. example/ itself does not move — the bundle keeps its own copy and the test fixtures keep their paths.

3. clinical-scope --demo — downloads that asset into ~/.clinical_scope/example/ and prints the data-folder and config paths the app asks for. clinical-scope -h now describes it, and --version prints the installed version.

4. Document it — a Trying the demo section in the README and Trying the Demo Dataset in the user guide, covering both the bundled copy and the download. The guide is where the new Docs link lands, so that is where a pip user discovers the demo is a download away.

Two design notes

The parser lives in a new cli.py, not in core_api.main. core_api.py stays the PyInstaller entry point and must keep ignoring sys.argv — macOS hands a Finder-launched bundle a -psn_… argument that argparse would reject. Keeping it separate also means --help and --demo never build the Dash layout, which happens at import time.

Fetch rather than ship in the wheel. Bundling the demo as package data would force example/ under src/clinical_scope/, breaking assemble_bundle.py and the test fixtures, and would add ~6 MB to every install for the majority of users who bring their own ICU data. The download is bounded in size, staged in a sibling folder so an interrupted run cannot leave a half-filled demo behind, and refuses any archive member that would write outside its destination.

Testing

  • tests/unit/test_demo_data.py — 16 tests covering the happy path, idempotency, --force, zip-slip, oversize, bad zip, and offline. No network: urlopen is replaced by a fake serving bytes built in the test.
  • tests/unit/test_readme_links.py — guards the README against relative links.
  • Verified by hand: the CI archive step builds a 2.23 MB zip with no cache or macOS leakage, and that real archive round-trips through the extractor to all nine datasource folders plus the config.

Known limitation

--demo cannot work until a release ships with the asset — releases/latest/download/clinical-scope-example.zip 404s today. Until then the command reports a readable message and exits 1 rather than raising. docs/RELEASING.md step 3 now includes checking that the asset is attached, and warns that renaming it breaks the command for every version already installed.

The in-app Docs link points at the online tutorial, so it is dead for a standalone-bundle user with no network, even though the PDF sits next to their executable. Left alone for now; say the word if that case is worth handling.

🤖 Generated with Claude Code

alexisj-inria and others added 7 commits September 16, 2026 17:11
A `pip install clinical-scope` puts nothing but `src/` on disk: neither
`docs/` nor `example/` is packaged, so the PyPI project page is the only
entry point a pip user has. That page rendered the README with six
repo-relative links, which PyPI resolves against pypi.org — every one of
them 404'd, and the demo GIF did not render at all. The app itself
offered no way to reach the tutorial either.

- `[project.urls]` gains Documentation, Changelog and Issues, which
  Warehouse renders as the project page's sidebar.
- Every README link is now absolute, so the page works off-repo.
- A grey "Docs" pill joins the version badge and settings gear in the
  top-right stack, opening the online tutorial.
- A test fails on any relative link reintroduced into the README.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The standalone bundle carries `example/` because assemble_bundle.py
copies it in, and a source checkout has it by definition. A pip install
has neither, so the demo data needs somewhere to be fetched from.

Adds an `example` job to build.yml that zips `example/` into
`clinical-scope-example.zip` and attaches it to the same draft release as
the executables. The tree is staged through a filtered copy first, so the
per-run parquet cache and macOS metadata stay out — 2.2 MB rather than
the 10 MB an unfiltered archive of the folder would carry.

`example/` itself does not move: the bundle keeps its own copy and the
test fixtures keep their paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A pip install has no example data and, until now, no way to get any: the
Quickstart pointed at a folder only a checkout or the standalone bundle
has. `--demo` downloads the release asset into ~/.clinical_scope/example
and prints the two paths the app asks for.

The parser lives in a new `cli.py` rather than in `core_api.main`, which
stays the PyInstaller entry point and must keep ignoring sys.argv —
macOS hands a Finder-launched bundle a `-psn_…` argument that argparse
would reject. Keeping it separate also means `--help` and `--demo` never
build the Dash layout, which happens at import time. `clinical-scope -h`
now describes the demo, so the command is discoverable from the terminal.

The download is bounded in size, staged in a sibling folder so an
interrupted run cannot leave a half-filled demo behind, and refuses any
archive member that would write outside its destination. Every failure
leaves as one exception: with no asset published yet, `--demo` reports a
readable message and exits 1 rather than raising.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Quickstart told every reader to point at `demo_database/demo_patient/`,
a folder only a source checkout or the standalone application has. For a
pip user that was a dead end with no alternative named.

- README gains a *Trying the demo* section, and step 3 of the Quickstart
  sends readers to it instead of promising a bundled folder.
- The user guide gains *Trying the Demo Dataset*, covering both routes —
  it is where the new in-app Docs link lands, so that is where someone who
  installed with pip will find out the demo is a download away.
- The guide's interface tour mentions the Docs link next to Settings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The standalone bundle has no `example/` wrapper — assemble_bundle.py copies
each asset to the bundle root — but the new demo sections in the README and
the user guide sent standalone users to `example/demo_database/`. Both now
name `demo_database/`, which is what is actually next to the executable.

`--force` was declared at top level and read only inside the `--demo`
branch, so `clinical-scope --force` launched the app and dropped the flag.
It is now a usage error. `cli.py` also gains the tests it shipped without:
the printed paths, both failure exits, and that neither `--demo` nor a
refused `--force` ever reaches the dashboard.

The demo tree's folder names moved to constants and are now pinned from
both ends — test_cli.py asserts the literal paths the command prints, and
test_example_assets.py asserts those same literals exist under `example/`.
Renaming either side fails a test, instead of surfacing only once a tag is
pushed and a user runs `--demo`.

Two comments narrated how the code came to look as it does rather than what
a reader needs: the docs-link style note restated its own dict, and the
demo-download test docstring explained another module's design — untruly,
since patching `urlopen` does not depend on that module being Dash-free.
The top-right stack's 10/40/70px offsets became a top and a pitch.

Also: assemble_bundle.py now filters the same names the release archive
does, build.yml names the constant its asset name is pinned by, and the
`.gitignore` entry for an `example.zip` nothing produces is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ruff check .` and `ruff format --check .` have both been failing since
`cli.py` and `demo_data.py` were added, so CI's lint job was red for
reasons unrelated to what the code does.

`demo_data.py` now raises the way the rest of `src/` does — `msg = …`
then `raise DemoDownloadError(msg)` — which is what TRY003 and EM102 are
asking for, and incidentally puts the over-long unpack message inside the
line limit. The `# noqa: S202` on `extractall` was never doing anything:
S202 is a tarfile rule, so the reason it carried is now a plain comment.
The one raise that must happen inside the `try`, so the handler deletes
the part-file, keeps a narrow `# noqa: TRY301`.

`cli.py` gets the per-file ignores the config already grants `scripts/*`
and `build_info/*`: a console script prints to stdout by design, and its
imports are deferred on purpose so `--help` and `--demo` never build the
Dash layout.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The lint job on this branch was red for four commits without anyone
noticing, because running the checks meant remembering three commands and
which venv had the pinned ruff.

`.github/ci-local.sh` runs what `ci.yml` runs, next to the file it
mirrors. It keeps CI's shape: every check runs and the failures are
reported together, since CI splits lint and test into two jobs precisely
so one cannot hide the other. It prints the ruff version first, as CI
does, and says so when that version is outside the capped 0.16.x line —
the mismatch CONTRIBUTING already warns about. It covers the active
interpreter only; CI still runs the suite on 3.11 and 3.13.

The PR checklist now points at it instead of listing the commands.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@AlexisJanin AlexisJanin left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 additional comment: i suggest to take this PR opportunity to also clarify the term tutorial vs user-guide. Most obvious conflict is the tutorial.md being transformed to user_guide.pdf

Comment thread .github/ci-local.sh Outdated
Comment thread docs/user_guide/tutorial.md Outdated
Comment thread docs/user_guide/tutorial.md Outdated
Comment thread docs/RELEASING.md Outdated
Comment thread docs/RELEASING.md Outdated
Comment thread CONTRIBUTING.md
Comment thread pyproject.toml
Comment thread pyproject.toml Outdated
Comment thread src/clinical_scope/constants.py Outdated
Comment thread src/clinical_scope/dash_api/core_api.py
alexisj-inria and others added 2 commits September 17, 2026 11:45
Drops `.github/ci-local.sh` and restores the CONTRIBUTING checklist: running
the checks locally already works with the plain commands, and a mirror of
ci.yml takes on its job of being one controlled environment for everyone.

Corrects the demo walkthrough. The EIT day and the EDF recording start are
not there to line up recordings made at different times — neither file
carries a recording date at all, so both formats expect one to be supplied.
As written it read as a defect in the data. The walkthrough also gains an
Inspect data step, which is the cheaper first look at any new folder.

Points the in-app Docs link at `ClinicalScope_UserGuide.pdf` on the newest
release rather than the markdown on `main`, and adds the build.yml job that
attaches it, so a click lands on a released guide instead of edits for a
version nobody is running. `TUTORIAL_URL` becomes `USER_GUIDE_URL`.

Moves the Docs pill between the version badge and the settings gear.

Removes the comments the review called narration, in core_api, styles,
pyproject and constants. The release-asset naming warning leaves RELEASING
entirely: build.yml already carries it, next to the name it pins, where the
person who would break it is actually reading.

Rewrites cli.py's docstring to lead with the reason the module exists — the
console script and the Finder-launched bundle have different argv contracts
— rather than with the startup cost, which only justifies the deferred
imports and is now stated where those happen.

RELEASING step 5 gets `--demo --force`: without --force the demo folder left
by the previous release is kept, so nothing is downloaded and the asset is
never actually checked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The document already called itself one thing and was filed under another:
its own frontmatter title is "Clinical Scope -- User Guide", it builds to
ClinicalScope_UserGuide.pdf and README linked it as "the user guide", while
the file was tutorial.md and CLAUDE.md, CONTRIBUTING and the skills all
called it the tutorial. Two names for one document, and a reader tracing a
reference had to work out they were the same.

Renames it to docs/user_guide/user_guide.md and settles every reference on
"user guide" — links, prose, the build script, three skills, ADR-0008, and
the test that reads the file by path.

CHANGELOG entries keep saying "tutorial": they record what shipped in past
releases, and that is a history rather than an inconsistency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AlexisJanin

Copy link
Copy Markdown
Collaborator Author

Review addressed

All fifteen threads answered inline. Two commits, split the way you asked.

tutorial vs user-guide — taken up, own commit

You were right that this PR was the moment. The document already disagreed with itself in three places: its own frontmatter title is "Clinical Scope -- User Guide", it builds to ClinicalScope_UserGuide.pdf, and README linked it as "the user guide" — while the file was tutorial.md and CLAUDE.md, CONTRIBUTING and the three skills all called it the tutorial. The filename was the odd one out, not the prose.

docs/user_guide/tutorial.md → docs/user_guide/user_guide.md, and every reference settled on "user guide": links, prose, build_pdf.sh, three skills, ADR-0008, and the test that reads the file by path. The rename commit is 30 lines changed and a content-identical move, so it reviews in one pass.

CHANGELOG.md deliberately still says "tutorial" — entries 56 and 57 describe what shipped in past releases, which is a historical record rather than an inconsistency. Shout if you'd rather they match.

One pushback worth your eyes

The cli.py naming thread — I kept cli.py over api.py and explained why, along with an honest answer to your "is it a workaround?" question. Short version: the macOS -psn_ constraint is real and load-bearing, but you correctly sensed something else, which is that core_api builds its layout at import time. The docstring now says the true reason instead of the flattering one.

Known limitations — one is new

The description's Known limitation section needs a second entry: the in-app Docs link now resolves to releases/latest/download/ClinicalScope_UserGuide.pdf, so like --demo, it 404s until a release ships carrying that asset. build.yml attaches it from now on, and RELEASING step 3 lists it among what to check on the draft.

The offline-bundle-user case I raised originally is unchanged: they have the PDF next to their executable but the link goes online regardless.

🤖 Generated with Claude Code

@AlexisJanin AlexisJanin added documentation Improvements or additions to documentation status: done This issue is considered done, soon closed Code quality Improve overall code quality (maintainability, robustness, readability) build distribution labels Sep 17, 2026
@AlexisJanin AlexisJanin self-assigned this Sep 17, 2026
@AlexisJanin
AlexisJanin merged commit a89c272 into main Sep 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

build Code quality Improve overall code quality (maintainability, robustness, readability) distribution documentation Improvements or additions to documentation status: done This issue is considered done, soon closed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants