Add packaging metadata, CI, tests and a PyPI release workflow - #1
Merged
Conversation
added 2 commits
July 27, 2026 16:56
Prepares the package for a PyPI release and adds the automation to keep it working. Packaging: - requires-python moves >=3.9 -> >=3.10. torch, transformers, peft and lightning all require >=3.10 in their current releases, so a 3.9 install could never resolve a working dependency set. - Adopt the PEP 639 license fields. The SPDX id PolyForm-Noncommercial-1.0.0 now appears as License-Expression, and LICENSE, WEIGHTS_LICENSE.md and THIRD_PARTY_LICENSES.md all ship in the distribution. - Add classifiers, keywords, and the paper/weights URLs so the PyPI page is usable, plus `wsi`, `test` and `dev` extras. Tests (44, none of which need the gated weights): - The checkpoint is gated on the Hugging Face Hub, so CI can never load a real model. Every test runs against the packaged assets or a randomly-initialised module instead, which keeps the suite runnable on a fork with no secrets. - Cover the decoder's (expression, gene_features, attn_weights) contract and gene_indices subsetting/ordering, which is what downstream integrations such as TIAToolbox unpack. - Assert tokens.csv still carries the 19338-gene panel the model reports, and that the assets resolve from an installed wheel rather than a source tree. CI: - Install and test on 3.10-3.13 using the CPU torch wheel. - An import-check job installs the built wheel outside the source tree, so a missing package-data entry fails loudly instead of at user runtime. - twine check --strict on every build. - Lint is scoped to tests/ for now; running ruff over src/ and examples/ reports 4 findings and would reformat 8 files, which belongs in its own change rather than buried here. Release: - Tag-triggered publish via PyPI Trusted Publishing (OIDC), so no API token is stored in the repository, with a TestPyPI path for dry runs and a check that the tag matches the project version. - Dependabot for pip and github-actions, grouped to one PR per week. Also: - config.py uses importlib.resources.files() instead of the deprecated path(), which returned a context-managed path already released on exit. - The TCGA example notebook said `pip install deepspotm`, which fails because the package is not on PyPI yet; it now points at the git install like the README does.
timm and lightning pull torchvision from PyPI, which does not match the CPU torch wheel installed from the pytorch index. The mismatched pair fails at import with 'operator torchvision::nms does not exist', so both have to come from the same index.
Member
Author
|
Exact Trusted Publishing setup, for the record.
The environment name has to match the Suggested order once this is merged:
Unrelated but worth knowing: the name |
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.
What
Prepares
deepspotmfor a PyPI release and adds the automation to keep it working. No behaviour change to the model.Packaging
requires-pythonmoves>=3.9to>=3.10. This one is a real bug, not a tidy-up:torch,transformers,peftandlightningall declare>=3.10in their current releases, so a 3.9 user could never resolve a working dependency set. We were advertising support that cannot exist.PolyForm-Noncommercial-1.0.0is a valid SPDX identifier, so it now emitsLicense-Expression:in the metadata, andLICENSE,WEIGHTS_LICENSE.mdandTHIRD_PARTY_LICENSES.mdall ship in the distribution. This needssetuptools>=77, which the build-system requirement now pins.wsi/test/devextras.Verified locally:
python -m buildsucceeds andtwine check --strictpasses on both the wheel and the sdist. Resulting metadata isMetadata-Version: 2.4withLicense-Expression: PolyForm-Noncommercial-1.0.0, threeLicense-Fileentries, and all four asset files present in the wheel.Tests
44 tests, none of which need the gated weights. The checkpoint is gated on the Hub, so CI can never load a real model. Everything here runs against the packaged assets or a randomly-initialised module, which means the suite passes on a fork with no secrets configured.
(expression, gene_features, attn_weights)return contract, and thatgene_indicesboth subsets and orders the output columns. This is precisely what downstream integrations unpack, including the TIAToolbox wrapper.tokens.csvstill carries the 19,338-gene panel the model reports, with dense token ids.check_state_dict_loadstill raises on unexplained key drift, which is the guard that stops a mismatched checkpoint from silently predicting from uninitialised weights.CI
package-dataentry would otherwise only surface for users, since it works fine from a checkout.twine check --stricton every build.tests/. Running ruff oversrc/andexamples/currently reports 4 findings and would reformat 8 files; widening it is worth doing but belongs in its own PR rather than buried in this one.Release
Tag-triggered publish through PyPI Trusted Publishing (OIDC), so no API token is stored in the repo. There is a TestPyPI path via
workflow_dispatchfor a dry run, and a guard that the tag matches the version inpyproject.toml. Dependabot watches pip and github-actions, grouped to one PR per week per ecosystem.Before the first release you need to register the workflow at https://pypi.org/manage/account/publishing/ with owner
ratschlab, repoDeepSpotM, workflowrelease.yml, environmentpypi. I would run the TestPyPI path first, since version1.0.0can never be re-uploaded once it is published.Also
config.pynow usesimportlib.resources.files()instead ofpath(), which is deprecated since 3.11 and returned a context-managed path that was already released by the time it was stored.examples/predict_tcga_skcm.ipynbtold users to runpip install deepspotm, which fails today because the package is not on PyPI. It now points at the git install, matching the README.