Modernize packaging + CI so a release can actually reach PyPI (#14) - #15
Merged
Merged
Conversation
The user-visible point of this change: the README's "use `ho` instead" pointer has never reached PyPI. 0.1.31 was uploaded at 11:46:58Z on 2026-03-06; the commit adding that note landed 7 seconds later, and every publish since has failed, so `pip install http2py` still hands people a package that never mentions its successor. Getting a green, publishable CI is what ships the deprecation notice. Why Publish was red: the job ran `twine upload && epythet make . github` as a single step. The upload succeeded (hence 0.1.31 on PyPI) and the docs build failed, because an earlier commit had deleted docs/ and docsrc/. The failure also skipped the push-back and tag steps, which is why the tree still said 0.1.30. Packaging - pyproject.toml (hatchling) carries the metadata ported from setup.cfg: deps, keywords, the api-pkg-maker console script, SPDX `license = "Apache-2.0"` with no License:: classifier. setup.py and setup.cfg deleted. - Version 0.1.32: 0.1.31 is burned on PyPI. Verified the CI bump lands on 0.1.33 from here, safely past it. CI - .github/workflows/ci.yml replaced with the current uv-based standard: checkout@v6, setup-uv@v7, the wads setup-python-uv / install-deps-uv / run-tests-uv composites. Drops checkout@v2, setup-python@v2, and the dead SCRIPTS_REPOSITORY_URL env pointing at a host that no longer resolves. - No docs builder: `[tool.wads.ci.docs].enabled = false` gates the pages job off. docs/ and docsrc/ are gone; re-enabling it re-creates the exact failure above. - Tested on 3.10 and 3.12. Tests (there were none that ran) - New tests/ with a real smoke surface: the package imports, its public names are exported, mk_request_function fills a url_template from path args (via the injectable dispatch seam, so no network), and HttpClient binds the routes an OpenAPI spec declares. - tests/test_ci_collection_contract.py guards the thing that was broken: under --doctest-modules an unimportable module is not one red test, it aborts the whole session. api_pkg_maker stays, excluded from collection - It imports `setuptools.sandbox`, removed from modern setuptools. Rewriting it or deleting it is still an open question, so it is left in place and excluded from collection instead - the reversible option. Excluded in two places that a test keeps in agreement: [tool.wads.ci.testing].exclude_paths and the repo-root conftest. Two traps found while verifying, both of which would have kept CI red - http2py/tests/conftest.py imported py2http at module level. pytest eagerly imports the conftest of any test* subdirectory *before* --ignore is applied, so excluding the directory was not enough: a machine without py2http (CI included) aborted with "ImportError while loading conftest" before any test ran. The import is now inside the fixture that needs it. - A cold `pip install http2py` fails on `import ju`: ju/oas.py imports dill at module level, never uses it, and does not declare it. Filed upstream as i2mint/ju#6. Carrying `dill` here as a temporary dependency, with a test that goes red as soon as upstream is fixed so the workaround does not become permanent. Also added the module docstrings that were missing (they are extracted for generated docs, and D100 is enabled). https://claude.ai/code/session_01GPpn5ixPgqGqk7uJH7cC6o
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.
Advances #14. Not closing it — two questions it raises are still open (see "What is left" at the bottom).
The headline, which #14 understates
The
hodeprecation pointer has never reached PyPI. The README onmastertells people to usehoinstead. PyPI does not. Verified:http2py 0.1.31uploaded to PyPI2026-03-06T11:46:58Zhonote to README2026-03-06 11:47:05Z(7 seconds later)hopointer in the PyPI 0.1.31 long_descriptionEvery publish since has failed, so anyone running
pip install http2pytoday still gets a package that never mentions its successor. Getting a green CI that can publish is therefore the real deliverable here — the deprecation notice ships as a side effect of the next successful merge.Why Publish was red
Both
masterruns on 2026-03-06 showValidation (3.10) => success,Publish (3.10) => failure, withPush ChangesandTag Repositoryskipped. The failing step is namedPublish, and it ran two commands as one step:The upload succeeded (that is why 0.1.31 exists on PyPI).
epythet make . githubthen failed, because commit43aef24had deleteddocs/(22 files) anddocsrc/. Because the step failed, the version push-back and tag were skipped — which is why the tree still said0.1.30while PyPI said0.1.31.What changed
Packaging
pyproject.toml(hatchling) carrying the metadata ported fromsetup.cfg: description, keywords, theapi-pkg-makerconsole script, and the dependency set (glom,i2,ju,requests,argh,PyYAML,importlib_resources).license = "Apache-2.0", noLicense ::classifier (PEP 639 conflict). Confirmed in the built wheel:License-Expression: Apache-2.0.setup.pyandsetup.cfgdeleted.0.1.32—0.1.31is burned. Verifiedisee gen-semverresolves to0.1.33from this state (it reads pyproject0.1.32+ PyPI0.1.31), safely past the burned number.CI
.github/workflows/ci.ymlreplaced wholesale with the current uv-based standard:actions/checkout@v6,astral-sh/setup-uv@v7, and the wads compositessetup-python-uv/install-deps-uv/run-tests-uv/build-dist-uv/pypi-publish-uv.actions/checkout@v2,actions/setup-python@v2, and the deadSCRIPTS_REPOSITORY_URLenv pointing at a host that no longer resolves.[tool.wads.ci.docs].enabled = false; the pages job is gated on it.docs/anddocsrc/no longer exist, and re-enabling a docs build re-creates the exact failure above. Verifiedread-ci-configemitsdocs-enabled=false(see evidence below) — if it had emitted an empty string the gate!= 'false'would have let the job run, which is the trap this note exists for.["3.10", "3.12"]; Windows tests off; metrics off. No system dependencies (pure python), so no[tool.wads.ops.*].Tests — there were none that ran
tests/is new. Previously the only test module importedapi_pkg_makerand could not be collected at all.tests/test_smoke.py— the package imports; its public names are exported;mk_request_functionfills aurl_templatefrom path args (asserted through the injectabledispatchseam, so nothing leaves the machine);HttpClientbinds the routes an OpenAPI spec declares.tests/test_ci_collection_contract.py— guards the failure mode that was actually biting: under--doctest-modulesan unimportable module is not one red test, it aborts the whole session.tests/test_dependency_workarounds.py— see thejunote below.api_pkg_maker— left in place, excluded from collectionIt imports
setuptools.sandbox, which modern setuptools no longer ships, so importing it raisesImportError. Per the open question on #14 this PR takes the reversible option: the module is neither rewritten nor deleted, just excluded from collection, in two places that a test keeps in agreement —[tool.wads.ci.testing].exclude_pathsand the repo-rootconftest.py. A tripwire test fails the moment the module becomes importable again, so a stale exclusion cannot quietly outlive the decision.Two traps found while verifying, both of which would have kept CI red anyway
1.
http2py/tests/conftest.pyimportedpy2httpat module level. pytest eagerly imports theconftest.pyof anytest*sub-directory of a collection root before--ignoreis applied — so excluding the directory was not enough. On any machine withoutpy2http(CI included) the session aborted withImportError while loading conftestbefore a single test ran. The import now lives inside the fixture that needs it. This one is why "exclude the directory" was not a complete fix, and it is the mutation I care most about.2. A cold
pip install http2pyis broken today, upstream of this repo.ju/oas.pyimportsdillat module level, never uses it, and does not declare it;ju/__init__.pyimportsju.oaseagerly. Soimport http2py->import ju->ModuleNotFoundError: No module named 'dill'. Reproduced on a clean Python 3.10 venv againstju==0.1.31(current PyPI). Filed as i2mint/ju#6; not fixed in passing, sincejuis a foundation package with its own release cadence.This PR carries
dillas an explicitly-marked temporary dependency so http2py can be installed and published now, plus a test that goes red as soon asjuis fixed — a workaround with no expiry becomes permanent, and this one has one.Gates actually run
ruff format/ruff check(D100 enabled — the missing module docstrings were added rather than the rule disabled):The exact command CI runs, on Python 3.12:
The same, on a fresh Python 3.10 venv built the way CI builds it (
uv venv --python 3.10,uv pip install -e ".[dev]", no local-source constraints), which is what proves the declared dependency set is complete:(the 3 skips are
tomllib-gated config assertions;tomllibis 3.11+, and the 3.12 leg covers them.)Build + cold install of the artifact:
Wheel metadata confirms the deliverable: the long_description begins with the
hopointer, and'github.com/i2mint/ho' in descriptionisTrue.read-ci-configagainst the new pyproject:Mutation testing
Every guard was re-checked by reintroducing the bug it claims to catch and confirming it goes red; each file was restored from a byte-for-byte backup afterwards. 10/10 mutations turned their guard red.
py2httpat module level againtest_excluded_test_dir_conftest_imports_without_undeclared_depsapi_pkg_makerdropped from the known-broken settest_every_package_module_imports_except_the_known_broken_oneapi_pkg_makermade importabletest_known_broken_module_is_still_brokencollect_ignoredrifts fromexclude_pathstest_pyproject_and_conftest_exclusions_agreetest_docs_builder_is_disableddilldropped while ju#6 is opentest_dill_is_declared_while_the_workaround_standsjufixed upstream (simulated)test_dill_workaround_is_still_neededurl_templateno longer formatted with path argstest_mk_request_function_formats_the_url_template_from_path_argsmk_clidropped from the public APItest_public_names_are_exportedHttpClientstops binding spec-declared routestest_http_client_from_openapi_spec_binds_declared_methodsThe first run of the harness reported the conftest guard as green, which was a flaw in my mutation (it deleted the names instead of restoring the module-level import) rather than a vacuous test; corrected, it goes red.
Publish credentials
This repo has no repository-level secrets. It inherits the organization-level
PYPI_PASSWORDandSSH_PRIVATE_KEY(visibility: all). That pair is confirmed working withuv publish—dol, in the same org and with no repo-level secrets either, published0.3.63through this identical workflow on 2026-08-10. No secrets were created or modified.What is left on #14 (why this does not close it)
api_pkg_makerquestion is unanswered. (a) rewrite it for modern setuptools, or (b) delete it. This PR takes (c) — leave it, exclude it — precisely because neither answer has arrived. The console-script entry point is preserved for the same reason.hopointer is not on PyPI until this merges and the Publish job goes green. The claim in this PR is that CI can now publish; the proof is the first green Publish run.import dillthat is never used and is not a declared dependency ju#6 must be fixed and released before thedilldependency here can be dropped.https://claude.ai/code/session_01GPpn5ixPgqGqk7uJH7cC6o