Modernize packaging + CI (setup.cfg -> pyproject, uv-stub CI) and fix two latent threading bugs - #2
Merged
Merged
Conversation
Migrates this repo from the legacy setup.cfg/setup.py + hand-rolled GitHub Actions workflow to the current wads standard: a hatchling pyproject.toml as the single source of truth, and a 5-line ci.yml stub that calls the reusable workflow i2mint/wads/.github/workflows/uv-ci.yml. Everything declared in setup.cfg is carried across: name, version (0.1.3, matching the released PyPI version), description, url -> [project.urls].Homepage, long_description(+content_type) -> readme, license apache-2.0 -> SPDX `license = "Apache-2.0"`, install_requires (psutil, stream2py) -> [project].dependencies, packages = find: / include_package_data -> hatchling wheel target. There were no entry points, console scripts, package data files, extras, MANIFEST.in or requirements.txt to preserve. `zip_safe` and the wads-specific `root_url` / `display_name` / `description_file` keys have no pyproject equivalent and are dropped intentionally. Also: - `testpaths = ["pchealthstream2py"]` (not the generated default `["tests"]`). wads CI runs `pytest --doctest-modules` with no path argument, so collection is driven entirely by testpaths; the repo has no top-level tests/ dir, so `["tests"]` would have silently collected nothing while still reporting green. Verified locally: 1 passed. - `[tool.wads.ci].project_name` set to the real package name (the migration tool emits an empty string, which is the target of CI's ruff-check and coverage steps). - SPDX string licence form instead of the deprecated `[project.license] text = ...` table the tool emits. - Added the ecosystem-standard [tool.ruff] block, keywords, classifiers, authors, requires-python and Repository/Documentation URLs. - Added the standard .editorconfig. PYPI_USERNAME is no longer referenced: the uv CI uses token-only PyPI auth via PYPI_PASSWORD, which the stub passes through explicitly. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475
Both were surfaced while validating the packaging migration and are
reproducible on the released code.
1. The stop flag was named `_stop`, which shadows `threading.Thread._stop`
— a real method CPython calls internally from `_wait_for_tstate_lock()`,
reached via `join()` and `is_alive()`. Consequences on the released
version:
reader.join(timeout=2)
TypeError: 'Event' object is not callable
and `is_alive()` could never transition to False once the worker had
finished, because the interpreter's own bookkeeping call blew up.
Renamed to `_stop_event`; `Thread._stop` is left alone.
2. `_index`, `_data`, `_stop` and `_bt` were *class* attributes, so every
StatusInfoReader instance shared one SyncQueue and one stop Event.
Opening a second reader cleared the first reader's buffer, closing
either reader stopped both, and the two readers' items interleaved in
a single queue. Moved to per-instance state in `__init__`.
Also corrected `network_download_speed`'s return annotation: `dict or None`
evaluates to plain `dict` at class-creation time, so the "or None" part was
silently discarded. It is now `Optional[dict]`, which is what the body
actually returns.
Added focused regression tests for both fixes. Note that the class still
cannot be re-opened after `close()` (a `threading.Thread` can only be
started once) even though `SourceReader`'s documented contract allows it —
that needs a larger refactor and is filed separately.
Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475
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.
Part of the fleet-wide wads repo-modernization pass. This repo was still on the
legacy generation (setup.cfg + setup.py + a hand-rolled ~90-line workflow using
actions/setup-python@v2); it now matches the current standard: a hatchlingpyproject.tomlas the single source of truth plus a 5-lineci.ymlstub thatcalls the reusable workflow
i2mint/wads/.github/workflows/uv-ci.yml@master.masterruns the publish job, which bumps the version from the declared
0.1.3anduploads.
0.1.3is deliberately the version currently on PyPI, so the automaticbump lands on the correct next patch rather than re-publishing a version behind
the released one.
1. Packaging: setup.cfg -> pyproject.toml
Full inventory of what was in
setup.cfgand where it now lives:name = pchealthstream2py[project].nameversion = 0.1.3[project].versiondescription[project].descriptionlong_description = file:README.md+long_description_content_type[project].readme = "README.md"url[project.urls].Homepagelicense = apache-2.0[project].license = "Apache-2.0"(SPDX string form;LICENSEfile kept, and it is a real Apache-2.0 text)install_requires = psutil, stream2py[project].dependenciespackages = find:/include_package_data = True[tool.hatch.build.targets.wheel].packagesplatforms = anyOperating System :: OS Independentclassifierzip_safe,root_url,display_name,description_fileThere were no entry points / console scripts, no package data files, no
extras, no
MANIFEST.inand norequirements.txt— nothing else to carry over.Verified against real imports with
wads-deps: the only module-levelthird-party imports in the package are
psutilandstream2py, which isexactly what is declared.
Added while here:
requires-python = ">=3.10", keywords, classifiers, authors,Repository/DocumentationURLs, the ecosystem-standard[tool.ruff]block(without it the repo falls through to ruff's moving default and goes red on
unrelated style drift), and the standard
.editorconfig.Manual fixes to the three known rough edges of
wads-migrate setup-to-pyproject:the deprecated
[project.license] text = ...table -> SPDX string; the empty[tool.wads.ci].project_name->"pchealthstream2py"(it is the target of CI'sruff-check and coverage steps); and
testpaths— see next section.testpaths — this one actually mattered
wads CI runs
pytest --doctest-moduleswith no path argument, so collection isdriven entirely by
testpaths. The migration tool writes["tests"]unconditionally, and this repo has no top-level
tests/dir — its tests live inpchealthstream2py/tests/. Left as generated, CI would have collected nothingand still reported green. Set to
["pchealthstream2py"], which also matches whatthe old workflow ran (
pytest --doctest-modules -v $PROJECT_NAME).Verified in the branch CI run:
collected 3 items,3 passed.2. CI: legacy workflow -> uv stub
wads-migrate ci-to-uvthenwads-migrate ci-to-stub. The old workflowreferenced
secrets.PYPI_USERNAMEandsecrets.PYPI_PASSWORD; the uv CI usestoken-only PyPI auth, so only
PYPI_PASSWORDis passed through by the stub andPYPI_USERNAMEis no longer needed. No other secrets were referenced, so[tool.wads.ci.env]stays empty.3. Two latent threading bugs fixed (separate commit)
Both are reproducible on the released code and were surfaced while validating
the migration.
a. The stop flag shadowed
threading.Thread._stop.StatusInfoReadersubclasses
threading.Threadand declared_stop: threading.Event. ButThread._stopis a method CPython calls internally from_wait_for_tstate_lock(), which is reached from bothjoin()andis_alive().On the released version:
and
is_alive()could never flip to False after the worker finished, becausethe interpreter's own bookkeeping call raised. Renamed to
_stop_event.b. All readers shared one queue and one stop flag.
_index,_data,_stopand_btwere class attributes, so everyStatusInfoReaderinstanceshared a single
SyncQueueand a singleEvent: opening a second readercleared the first one's buffer, closing either stopped both, and both readers'
items interleaved in one queue. Moved to per-instance state in
__init__.Also corrected
network_download_speed's return annotation:dict or Noneevaluates to plain
dictat class-creation time, so the "or None" was silentlydiscarded. Now
Optional[dict], which is what the body actually returns.Two focused regression tests were added alongside the fixes (the only new tests
in this PR — no test-coverage sweep was attempted here).
Deliberately left undone
StatusInfoReaderstill cannot be re-opened afterclose()— athreading.Threadcan only be started once, whileSourceReader's documentedcontract (its own class docstring) allows close-then-reopen. Fixing it means
having a thread rather than being one, which changes
isinstance(reader, threading.Thread), so it is out of scope for a packagingpass. Filed as StatusInfoReader cannot be re-opened after close() (SourceReader contract violation) #1 with a repro and a suggested fix.
ruff formatsweep. The code is still single-quoted; CI's publish jobformats and pushes back on merge, as it does fleet-wide. Keeping it out here
keeps this diff reviewable.
py.typedor agent-skills work — separatedimensions of the rollout.
Verification
pchealthstream2py pass 4.1s— 1 suite: 1 pass, 0 failpchealthstream2py pass 4.4s— 1 suite: 1 pass, 0 failpytest pchealthstream2py --doctest-modules -qruff check pchealthstream2py(0.16.0)uv buildLICENSEBranch CI jobs: Read Configuration ✅, Validation (3.10) ✅, Validation (3.12) ✅,
Windows Tests ✅, Publish skipped (branch is not the default branch), Publish
GitHub Pages skipped.
Repo audit (
python -m wads.repo_audit) before -> after: the HIGHno pyproject.toml, MEDIUMlegacy packaging files presentand MEDIUMCI is the legacy generationfindings all clear, as does LOWmissing .editorconfig.ci_formatgoeslegacy->uv-stub,legacy_filesgoes to empty.The only new finding is LOW
GitHub topics (none) != slugified pyproject keywords— a direct consequence of this PR filling in the previously-emptykeywords. The repo's GitHub topics and homepage have been aligned to match(repo settings, not part of this diff). The remaining LOW findings (
no agent skills) are other dimensions of the rollout, untouched here.