chore: migrate to modern Python tooling (pyproject.toml + uv) - #267
chore: migrate to modern Python tooling (pyproject.toml + uv)#267natestemen wants to merge 10 commits into
Conversation
2dbfe9c to
8d5a8f4
Compare
antalszava
left a comment
There was a problem hiding this comment.
Looks good to me! 👏 This is a great change, amazing one 💯
Some comments are targetting slight changes to be made before merging, but none are too major to block the PR.
|
|
||
| ```bash | ||
| pytest [pytest-args] | ||
| uv run pylint -rn qiskit_ionq test |
There was a problem hiding this comment.
This command is giving some formatting issues atm. for the repo and it's not the one that's being used in the CI (see line 46 of ci.yml):
# Tests lint with the same config, minus the 50-char cap on function names:
uv run --no-sync pylint -rn --function-rgx='[a-z_][a-z0-9_]+$' test
| version_file = "qiskit_ionq/_version.py" | ||
|
|
||
| [tool.ruff] | ||
| extend-exclude = ["docs/conf.py", "tools/verify_headers.py"] |
There was a problem hiding this comment.
Note from Claude:
ruff format silently lost coverage of two files. On main, the exclude for docs/conf.py and tools/verify_headers.py applied only to the ruff lint hook; moving it to top-level [tool.ruff] extend-exclude makes ruff format skip them as well (the hook passes --force-exclude). Nothing breaks today (both are format-clean under the pinned ruff 0.8.2), but future edits to those files go unformatted. If lint-only exclusion was the intent, use [tool.ruff.lint] exclude instead.
| # qiskit 2.5.0 deadlocks when transpiling to the IonQ native target at | ||
| # optimization_level=3 (e.g. test_forte_rzz_transpiles_to_zz); keep the | ||
| # locked dev/CI environment on 2.4.x until that is fixed upstream. | ||
| constraint-dependencies = ["qiskit<2.5"] |
There was a problem hiding this comment.
this may not be relevant anymore with #268 merged and [project] dependencies having the same pin, right?
There was a problem hiding this comment.
Also: was this resolved in 2.5.1 (like in Qiskit/qiskit#16592)?
| "Programming Language :: Python :: 3.10", | ||
| "Programming Language :: Python :: 3.11", | ||
| "Programming Language :: Python :: 3.12", | ||
| "Programming Language :: Python :: 3.13", |
There was a problem hiding this comment.
minor and may be out of scope: what about 3.14? qiskit supports that (at least on their GH main)
| qiskit_ionq = ["py.typed"] | ||
|
|
||
| [tool.setuptools_scm] | ||
| version_file = "qiskit_ionq/_version.py" |
There was a problem hiding this comment.
What I'm gathering is that this file is not being used anywhere in this repo atm. - is that true?
As far as my findings go, from importlib.metadata import version … version("qiskit-ionq") reads installed-package metadata. If that's true then generating "qiskit_ionq/_version.py" is not necessary and the [tool.setuptools_scm] section should be empty for simplicity.
| pytest [pytest-args] | ||
| uv run pylint -rn qiskit_ionq test | ||
| uv run ruff check | ||
| uv run ruff format |
There was a problem hiding this comment.
Running this on the repo atm. is producing changes.
|
Before addressing the changes here, I think it's worth strategizing. From what I recall, @splch wanted to move the repo to use ionq-core internally before this. I don't remember the reason for that, however. Spencer can you provide your thoughts? |
Move all package metadata from setup.py/setup.cfg into [project], replace the three requirements*.txt files with PEP 735 [dependency-groups] (test, docs, dev), and merge the pytest config from setup.cfg and tox.ini into [tool.pytest.ini_options]. The importlib-metadata backport is dropped from dependencies in favor of the stdlib importlib.metadata (available since Python 3.8), which requires a one-line import change in helpers.py. The unused [tool.isort] block is removed (nothing invokes isort; ruff handles import sorting via pre-commit). MANIFEST.in is no longer needed: README and LICENSE are picked up automatically and py.typed ships via [tool.setuptools.package-data]. Versioning becomes dynamic via setuptools-scm so the package remains buildable without setup.py; the legacy version.py is removed in a follow-up commit. A uv.lock is committed for reproducible CI installs. Part of #265. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Delete the ~100-line version.py that shelled out to git to compute a dev version. setuptools-scm now derives the version from git tags at build time (writing qiskit_ionq/_version.py, which is gitignored), and runtime consumers read it from the installed distribution metadata via importlib.metadata: __init__.py exposes __version__ from it and docs/conf.py uses it for the Sphinx release string. Dev installs keep the same flavor of version string as before (e.g. 1.1.2.dev1+g2d2a1ae99) without any subprocess calls at import time, and tagged releases (v1.1.1-style tags) resolve to the plain version automatically -- no manual version bump commit needed. Part of #265. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Translate .pylintrc into [tool.pylint] tables, carrying only values that differ from pylint's defaults (the [DESIGN] limits were dropped since every corresponding too-many-* message is disabled anyway) and additionally ignoring the setuptools-scm-generated _version.py. Verified for parity: pylint reports zero diagnostics before and after the translation. Delete tox.ini -- its pytest config already moved to pyproject.toml and CI will invoke pytest/pylint through uv directly, with the GitHub Actions matrix covering multiple Python versions. Ruff's file exclusions move from the pre-commit hook definition into [tool.ruff] so they hold anywhere ruff is run. The importlib.metadata import in helpers.py moves up into the stdlib import block to satisfy pylint's wrong-import-order now that it no longer comes from the third-party importlib_metadata backport. Part of #265. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replace the setup-python + pip install tox/tox-gh-actions steps with astral-sh/setup-uv and locked installs from uv.lock: - tests: uv sync with the test dependency group, then pytest with the coverage floor (--cov-fail-under=80) that previously lived in tox.ini - lint: pylint (config now in pyproject.toml) plus the license-header check, from the same test group environment - docs (PR build and publish): uv sync with the docs group, then make html - pre-commit: run via uvx instead of a pip-installed pre-commit - release: build the sdist/wheel with uv build; checkout now uses fetch-depth: 0 so setuptools-scm can resolve the version from the pushed tag Part of #265. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a development environment section to CONTRIBUTING.md covering uv sync, pre-commit setup, running tests, linting/formatting, and building the docs. Remove the deprecated `python setup.py test` instructions from CONTRIBUTING.md and README.md and replace the `pip install -r requirements-*.txt` steps with their uv equivalents. ruff and pre-commit are added to the dev dependency group so the documented `uv run ruff ...` / `uv run pre-commit ...` commands work out of the box after `uv sync`. Closes #265. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The setuptools-scm-generated qiskit_ionq/_version.py carries no license header and exists in any synced checkout, so verify_headers.py now skips generated files during discovery. Part of #265. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`from importlib.metadata import version` left a function bound to the module-level name `version`, which Sphinx reads as its reserved version config string and crashes on. Import the module instead. Part of #265. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
qiskit 2.5.0 (released 2026-07-02) deadlocks when transpiling to the IonQ native target at optimization_level=3, hanging the test suite at test_forte_rzz_transpiles_to_zz. This reproduces on main with the old pip-based setup as well, so it is an upstream regression rather than anything in this migration -- but any freshly resolved environment (including CI on main, whose last green run predates the 2.5.0 release) will hit it. Constrain uv's resolution to qiskit<2.5 so the locked dev/CI environment stays on 2.4.x. This does not restrict the published package metadata, which still allows qiskit>=2.0.0. Remove the constraint once the upstream hang is fixed. Part of #265. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ugh rebase Main (PR #268) pinned qiskit<2.5.0 in the requirements files this branch consolidated into pyproject.toml, and relaxed pylint's function-name length cap for the test suite via a second tox invocation. Reflect both here: refresh uv.lock for the new metadata pin and split the CI pylint run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Main (PR #272) relaxed the qiskit pin from <2.5.0 to !=2.5.0, treating the 2.5.0 transpiler hang as fixed in any later release. The rebase carried that into the consolidated pyproject metadata, so remove the matching uv constraint-dependencies workaround (added with a note to delete it once upstream was fixed) and re-lock. The lock still resolves to qiskit 2.4.2 because no release newer than 2.5.0 exists yet. Assisted-By: Claude Fable 5 <noreply@anthropic.com>
8d5a8f4 to
5a65d97
Compare
|
Just rebased so this is up to date, but would still like @splch's comments before we push on this. |
Closes #265.
This migrates the project to modern Python packaging and tooling, consolidating six config files (
setup.py,setup.cfg,tox.ini,.pylintrc,MANIFEST.in, threerequirements*.txt) intopyproject.tomland adopting uv across CI and the contributor workflow.What changed
Packaging (2d2a1ae)
setup.py/setup.cfginto[project], with an SPDX license expressionrequirements*.txtfiles became PEP 735[dependency-groups](test,docs, and adevgroup that pulls in both)setup.cfg+tox.inimerged into[tool.pytest.ini_options]importlib-metadatabackport in favor of stdlibimportlib.metadatauv.lockcommitted for reproducible CI installsVersioning (6a6df1f, 6b79b6b)
version.py(~100 lines shelling out to git at import time) replaced with setuptools-scm; the version derives from the existingv*tags, so tagged releases need no version-bump commit, and dev installs get the same1.1.2.dev5+g<sha>flavor of version as before__version__and the Sphinxreleasestring now come from installed-distribution metadataLint config (b082b44, 682469b)
.pylintrctranslated into[tool.pylint], carrying only non-default values; verified for parity (zero diagnostics before and after)tox.inideleted; the CI matrix already covers multi-Python, so tox was pure indirection[tool.ruff]_version.pyCI (2ba7fa6)
astral-sh/setup-uvwith locked installs of only the dependency group each job needs; release builds withuv build(withfetch-depth: 0so setuptools-scm sees the tag)Docs (0e4b173)
uv sync,uv run pytest, lint/format commands, pre-commit setup); the deprecatedpython setup.py testinstructions are gone from CONTRIBUTING.md and README.mdHeads-up: qiskit 2.5.0 regression (pre-existing, not caused by this PR)
While verifying, the test suite hung at
test_forte_rzz_transpiles_to_zz: qiskit 2.5.0 (released 2026-07-02) deadlocks when transpiling to the IonQ native target atoptimization_level=3(the main thread parks forever in a rayon lock inside_accelerate). This reproduces identically on unmodifiedmainwith the old pip-based setup — the last green CI run onmain(June 5) simply predates the 2.5.0 release, somain's next CI run would hang too. qiskit 2.4.2 is fine.This PR constrains the locked dev/CI environment to
qiskit<2.5via[tool.uv] constraint-dependencies(published metadata still allowsqiskit>=2.0.0). This deserves its own issue — both to remove the constraint once fixed and probably to report upstream to Qiskit.Deviation from the issue
The issue proposed replacing pylint with ruff. Pylint is intentionally kept (alongside ruff, which already runs via pre-commit): its
docparams/docstyleextensions validate docstring parameter lists against signatures, which ruff's stable rules don't cover yet. Its config now lives inpyproject.tomllike everything else.Verification
uv run pytest: 417 passed, 89.81% branch coverage (above the 80% floor) on py3.13, plus a py3.10 smoke run against the built wheeluv run pylint -rn qiskit_ionq test: 10.00/10, zero diagnostics (parity with.pylintrc)uv run pre-commit run --all-files: all hooks pass (ruff, ruff-format, mypy, zizmor)uv build: wheel containspy.typed+ generated_version.py, excludestest/; installed into a fresh venv and smoke-tested (IonQProvider, gates,__version__)uv run make html: docs build succeeds and the page title shows the scm-derived version🤖 Generated with Claude Code