Uh oh!
There was an error while loading. Please reload this page.
chore: extend ruff rule selection for the analytics package (#4942) - #4943
Conversation
Adds the flake8-comprehensions, flake8-pie and pyupgrade rule categories to the analytics ruff selection, bringing it closer to the selection used in clevercanary/hca-validation-tools. All three are already clean, so this commit changes no code. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the flake8-simplify rule category to the analytics ruff selection and collapses the one `if`/`else` block it flags into a ternary. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the ruff-specific rule category to the analytics ruff selection, replacing a single-element tuple concatenation with unpacking and sorting the `static_site` package's `__all__`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the flake8-return rule category to the analytics ruff selection, returning expressions directly instead of assigning them first and dropping two `else` branches that follow a `return`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the flake8-use-pathlib rule category to the analytics ruff selection and moves the static site's path handling and file I/O from `os.path`, `os` and `glob` to `pathlib`. `output_dir` is normalized to a `Path` where it enters `generate_site` and `export_data`, so callers can keep passing strings. `os.makedirs` becomes `Path.mkdir(parents=True, ...)` to preserve parent creation, and the absolute path printed at the end of a run now comes from `Path.resolve()`, which additionally resolves symlinks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Extends Ruff coverage for the analytics package and applies lint-driven refactors, including migration of static-site file handling to pathlib.
Changes:
- Adds seven Ruff rule categories.
- Simplifies control flow, returns, tuple construction, and exports.
- Refactors static-site path and file operations to use
pathlib.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.
Show a summary per file
| File | Summary |
|---|---|
analytics/pyproject.toml | Expands Ruff rule selection. |
analytics/analytics_package/analytics/static_site/generator.py | Uses Path for site generation paths. |
analytics/analytics_package/analytics/static_site/export.py | Uses Path for exports and cleanup. |
analytics/analytics_package/analytics/static_site/__init__.py | Sorts __all__. |
analytics/analytics_package/analytics/report_elements.py | Removes redundant else branches. |
analytics/analytics_package/analytics/api.py | Applies tuple, conditional, and return simplifications. |
analytics/analytics_package/analytics/_report_utils.py | Returns computed expressions directly. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| change_col: Source column name for the change metric (may be absent). | ||
| filename: Output JSON filename. | ||
| output_dir: Output directory. | ||
| output_dir: Output directory, as a Path. |
There was a problem hiding this comment.
export_data gained str-tolerance below (output_dir = Path(output_dir)), but export_df_as_json did not — and this docstring now narrows its contract to "as a Path". A caller passing a string, which worked before this PR, would hit TypeError: unsupported operand type(s) for /: 'str' and 'str' at (output_dir / filename).
Latent rather than live: the only in-repo caller is export_data, which has already normalized, and this function isn't in static_site.__all__. But a one-line output_dir = Path(output_dir) at the top would make the two public export entry points consistent.
Uh oh!
There was an error while loading. Please reload this page.
(Text via Claude)
Closes#4942
What changed
Extends the ruff
selectlist inanalytics/pyproject.tomlfrom["E4", "E7", "E9", "F", "I", "W", "B"]to addC4,PIE,PTH,RET,RUF,SIMandUP, and fixes every violation the new categories surface.One commit per rule category, each enabling the category and fixing its violations together, so each reviews independently and a regression traces back to one rule:
C4,PIE,UPSIMSIM108:if/else→ ternary inapi.pyRUFRUF005tuple unpacking inapi.py;RUF022sorted__all__instatic_site/__init__.pyRETRET504(assign-then-return) in_report_utils.pyandapi.py; 2×RET505(elseafterreturn) inreport_elements.pyPTHos.path/os/glob→pathlibinstatic_site/export.py(26) andstatic_site/generator.py(7)The
PTHcommit is the bulk of the work and the only real refactor.output_diris normalized to aPathat each public entry point (generate_siteandexport_data), so the fourgenerate_static_site.pyscripts keep passing plain strings;os.makedirsbecomesPath.mkdir(parents=True, exist_ok=True); the stale-detail-file sweep becomesPath.glob+Path.unlink; and everyopen(os.path.join(...))becomes(output_dir / name).open(...).Why
#4934 / #4936 added ruff to the analytics package with a deliberately minimal rule selection — enough to catch the star-import re-export leak that motivated it, but well short of what
clevercanary/hca-validation-toolsalready runs on Python. Two Clever Canary Python codebases linting to two different standards means review habits don't transfer between them, and this package silently accumulates patterns that wouldn't survive review in the other repo.Assumptions I made
Estays narrowed toE4/E7/E9rather than taking all ofE, which is the one intentional deviation from hca-validation-tools' selection. The non-preview rules fullEwould add over that subset are exactlyE501(line-too-long) andE101(mixed-spaces-and-tabs) — both formatting concerns, and thereforeruff format's job. Keeping them out means the linter never duplicates or fights the formatter.selectlist only. hca-validation-tools' other lint settings (per-file-ignores,isort.known-first-party,line-length,target-version) are deliberately out of scope. Noper-file-ignoresentry was needed — every violation had a real fix, and no# noqasuppressions were added anywhere.PTHcommit is path-handling only. A review pass suggested collapsing the ~10 near-identicalopen→json.dump→print(f" Wrote …")blocks inexport.pyinto a shared_write_jsonhelper (net ≈ −16 lines). That repetition is pre-existing — this diff converted it, it didn't create it — so it was deliberately left alone to keep the commit reviewable against the rule it's named for. Filed as a follow-up instead.output_diraccepting eitherstrorPathwas chosen over converting the four caller scripts. Normalizing at the library boundary keeps those scripts as declarative literals and avoids touching four out-of-scope files. The docstrings now state the accepted types, since every sibling argument in them already did.os.path.abspath()→Path.resolve()also resolves symlinks, so the finalFiles written to: …line may differ on a symlinked path.Path("./site")stringifies assite, soanvil-catalog's closingcd ./site && …hint now printscd site && ….How to verify
The issue's definition of done, mapped to steps:
1.
npm run lint:pythonandnpm run check-format:pythonpass clean.2. The same steps pass in
run-checks.ymlCI. Check theanalyticsjob on this PR. To reproduce its exact sequence locally:3. Fresh-venv
generate_static_site.pyrun includinghistoric_data_path, with generated site output confirmed unchanged (per the #4913 verification convention).PTHrewrites path construction and file I/O inexport.pyandgenerator.py, so this checks output equality, not just importability:analytics/readme.md—.credentials/hca_ga4_credentials.jsonfor LungMAP.CURRENT_MONTHinanalytics/lungmap/constants.pyto the month you want.analytics/lungmap, runuv run python generate_static_site.pyand complete the browser OAuth flow. LungMAP is the right app here because it passeshistoric_data_path(HISTORIC_UA_DATA_PATH) and writes intogh-pages/lungmap, exercising the real output path.gh-pages/lungmaptree against the same run onmain. Expected: every file byte-identical exceptdata/meta.json'sgenerated_attimestamp.cd gh-pages && python -m http.server 8080, then open http://localhost:8080.Verification already performed:
historic_data_path: onlygenerated_atdiffers.generate_site(withfetch_datastubbed to canned data, includinghistoric_data_path) andexport_datadirectly, then fingerprinted every output file. Output is byte-identical betweenmainat e738381 and this branch across all 13 JSON files plusindex.html, with onlymeta.json'sgenerated_atnormalized. The harness also covers the two behavior-sensitive spots:mkdir(parents=True)creating a missing intermediate directory, and theglob/unlinksweep removing a staleevent_*_detail.jsonwhile leaving a neighbouring file untouched. Both matchmain.