Modernize the package: upgrade all dependencies, fix silent correctness bugs, add tests and CI/CD - #7
Merged
Conversation
Trustee's dependency set dated from 2022 and no longer resolved against
current releases. Upgrade the whole stack and fix the breakages it exposed.
Correctness fixes (silent, no crash):
* scikit-learn 1.3 changed `tree_.value` for classifiers from raw sample
counts to per-node class fractions. Every percentage derived from it was
wrong -- reports showed class shares of 200% and 1400%, and "Data Split %"
was pinned at a constant for every node. Add `get_node_counts()` to
de-normalize across versions and route `get_dt_info()` plus the TrustReport
call sites through it.
* `TrustReport(X=..., y=...)` raised AttributeError on `X_train.shape[1]`.
This is the documented entry point, so the shipped
`trust_classification.py` example could not run.
* Leaf `prob` was always 0.00%: the guard tested `.ndim > 1` on a 1-D array.
* A stray trailing comma rendered class names as `(np.str_('virginica'),)`.
* pandas 3.0 rejects `df.drop(columns=..., axis=1)`.
* pandas >= 2 returns bool from `get_dummies`, which forced the returned
array to `object` dtype and made the `np.nan_to_num` scrub a no-op. Pin
the dummy dtype to uint8 to restore numeric output.
Dependency audit -- the manifest was wrong in both directions:
* `six` and `joblib` were imported but never declared (latent ImportError).
`six` is a Python-2 shim and is dropped outright.
* `scipy`, `termcolor`, `setuptools` and all four Sphinx packages were
declared as runtime dependencies but unused, so `pip install trustee`
pulled in the whole of Sphinx. Moved to a docs group or removed.
Packaging:
* pyproject.toml converted to PEP 621 with the poetry-core backend; the old
`poetry.masonry.api` backend and `dev-dependencies` table are both removed
in Poetry 2.x. Note that PEP 735 `[dependency-groups]` is silently not
locked by Poetry 2.1, so docs/dev use Poetry's native group syntax.
* Python floor raised 3.7 -> 3.11; verified on 3.12 and 3.14.
* Fix an invalid `\w` escape that warned on import under Python 3.12.
Locked: numpy 2.5.2, pandas 3.0.5, scikit-learn 1.9.0, matplotlib 3.11.1,
prettytable 3.18.0, graphviz 0.21, Sphinx 9.1, furo 2025.12.19.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The repo had no tests at all and CI only built the docs, which is why the dependency rot in the previous commit went unnoticed. Add the suite first so the pipeline has something to run. Tests (67, ~15s): * test_tree.py pins the semantics that scikit-learn 1.3 changed. These assertions check magnitudes, not shapes -- the regression they guard leaves the numbers finite and plausible-looking, so a shape check would miss it. Verified against the pre-fix code: 6 of them fail. * test_trustee.py covers both explainers, the fit/explain contract, the accessors and the not-yet-fitted guard. * test_dataset.py covers the conversion helpers and `read()`, including the numeric-dtype guarantee that the pandas dummies change broke. * test_report.py covers TrustReport construction through both documented entry points, rendering, and the save/load round trip. The percentage and class-name assertions guard the report bugs fixed in the previous commit. Two more bugs surfaced while writing them: * TrustReport.__getstate__ detached `trustee.expert` from the live object rather than from the pickled copy -- `__dict__.copy()` is shallow. The first save() corrupted the report in memory and the second raised AttributeError. * Four plot helpers called plt.figure() immediately before plt.subplots(), which supersedes it. plt.close() then only closed the second figure, leaking the first on every call. The discarded figsize was never applied, so dropping the stray calls changes nothing visually. Pipelines: * test.yml -- lint (black, flake8, poetry check --lock), tests across Python 3.11-3.14 on Linux plus a macOS and Windows spot-check, the shipped examples (they are rendered into the docs, so breakage there breaks the published site), and a build job that checks metadata with twine and verifies the wheel imports standalone. * release.yml -- builds, re-runs the tests, refuses to publish if the tag, pyproject.toml and trustee/_version.py disagree, then publishes via PyPI Trusted Publishing. TestPyPI is available through workflow_dispatch. Requires a one-time trusted-publisher setup on PyPI. * sphinx.yml -- replaced the third-party action pinned to @master, which supplies its own Python and would not pick up the pinned Sphinx 9 stack, with a direct build. Warnings are now errors, and deploys are gated to master so pull requests no longer publish. Lint is green rather than merely configured: fixed trailing whitespace, an unused pandas import and a bare except, and gave .flake8 the black-compatible ignore set with documented per-file exceptions. pytest now fails on DeprecationWarning and FutureWarning, so the next dependency drift surfaces as a test failure instead of silently wrong numbers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
checkout v5->v7, setup-python v6->v7, upload-artifact v4->v7 and download-artifact v5->v8. The artifact majors changed how direct (unzipped) uploads are handled and now error on digest mismatch; both are opt-in or transparent for the directory upload used here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Trustee's dependencies dated from 2022 and no longer resolved against current releases. This upgrades the whole stack to the latest of everything, fixes the breakages that exposed, and adds the test suite and CI/CD that would have caught them.
The headline finding: the package imported and ran fine on modern scikit-learn but produced wrong numbers. Nothing crashed — the reports just printed nonsense.
Silent correctness bugs
scikit-learn 1.3 changed classifier
tree_.valuefrom raw sample counts to per-node class fractions. Every percentage derived from it was wrong:Fixed with a version-agnostic
get_node_counts()helper. Five more real bugs surfaced along the way:TrustReport(X=..., y=...)raisedAttributeErrortrust_classification.pyexample could not runprobtested.ndim > 1on a 1-D array0.00%(np.str_('virginica'),)TrustReport.__getstate__used a shallow copytrustee.expertfrom the live object; firstsave()corrupted the report, second raisedplt.figure()immediately superseded byplt.subplots()Plus two pandas 3.0 breaks:
df.drop(columns=..., axis=1)now raises, andget_dummiesreturning bool forced the output array toobjectdtype, silently making thenp.nan_to_numscrub a no-op.Dependency audit
The manifest was wrong in both directions:
sixandjoblibwere imported but never declared — a latentImportError.sixis a Python-2 shim and is now gone.scipy,termcolor,setuptoolsand all four Sphinx packages were declared as runtime deps but unused.pip install trusteewas pulling in the whole of Sphinx.Locked: numpy 2.5.2 · pandas 3.0.5 · scikit-learn 1.9.0 · matplotlib 3.11.1 · prettytable 3.18.0 · graphviz 0.21 · Sphinx 9.1 · furo 2025.12.19
pyproject.tomlmoved to PEP 621 withpoetry-core(the oldpoetry.masonry.apibackend anddev-dependenciesare both removed in Poetry 2.x). Python floor3.7→3.11. Version1.1.6→1.2.0.Tests and CI/CD
The repo had no tests, and CI only built docs — which is why the rot went unnoticed.
67 tests, ~15s. The tree tests assert on magnitudes, not shapes, because the regression they guard leaves numbers finite and plausible-looking. Verified against the pre-fix code: 6 of them fail.
test.yml— lint (black, flake8,poetry check --lock); tests on Python 3.11–3.14 across Linux, macOS and Windows; the shipped examples (they render into the docs, so breakage there breaks the published site); and a build job that runstwine checkand verifies the wheel imports standalone.release.yml— builds, re-runs tests, refuses to publish if tag /pyproject.toml/_version.pydisagree, then publishes via PyPI Trusted Publishing. TestPyPI available viaworkflow_dispatch.sphinx.yml— replaced the third-party action pinned to@master(it supplies its own Python and would not pick up the pinned Sphinx 9 stack) with a direct build. Warnings are now errors; deploys gated tomasterso PRs no longer publish.pytest now fails on
DeprecationWarning/FutureWarning, so the next round of dependency drift surfaces as a test failure rather than silently wrong numbers.release.ymlneeds a one-time PyPI trusted-publisher setup or the release will fail. On PyPI → Manage → Publishing, add: ownerTrusteeML, repotrustee, workflowrelease.yml, environmentpypi. Same for TestPyPI with environmenttestpypi. (Alternative: swap in aPYPI_API_TOKENsecret.)Also worth noting GitHub currently flags 174 vulnerabilities on
master(9 critical, 75 high) — this upgrade should clear the bulk of them.Verification
All green locally: 67 tests on Python 3.12 and 3.14 · all four shipped examples · clean-venv wheel and sdist installs · Sphinx 9 docs build with
-Wand zero warnings ·black --checkandflake8clean ·poetry check --lockclean.Two things deliberately left alone as pre-existing and out of scope: cosmetic matplotlib
findfont/tight_layoutwarnings, and a benign sklearnUserWarningin the regression report path (allowlisted in the pytest config rather than silenced globally).🤖 Generated with Claude Code