Skip to content

fixes #344; fixes #326; consolidate packaging into pyproject.toml and make it the single source of truth for the version - #347

Merged
dave-doty merged 4 commits into
devfrom
344-consolidate-packaging-metadata-into-pyproject-toml
Aug 10, 2026
Merged

fixes #344; fixes #326; consolidate packaging into pyproject.toml and make it the single source of truth for the version#347
dave-doty merged 4 commits into
devfrom
344-consolidate-packaging-metadata-into-pyproject-toml

Conversation

@dave-doty

Copy link
Copy Markdown
Member

Fixes #344. Also fixes #326 (deleting setup.py forces that change, so it is included here).

Three commits, each reviewable on its own.


1. 2c49119 — consolidate packaging metadata into pyproject.toml

Metadata lived in four files: setup.py (everything real), setup.cfg (one dead description_file key), MANIFEST (a checked-in distutils artifact whose own first line says do NOT edit), and doc/requirements.txt (docs deps, hand-synced with install_requires). All four are deleted in favour of one declarative PEP 621 file.

Docs dependencies become a docs extra; tests_require — which setuptools stopped recognizing and warned about on every build — becomes a tests extra.

Verified against artifacts built from the old setup.py: the wheel contents are byte-identical, and twine check passes on both. Metadata differences are the expected PEP 621/639 spellings (Home-pageProject-URL, Author/Author-email combined, License: MITLicense-Expression: MIT). One field is genuinely lost — Download-URL, an f-string interpolating the version into a GitHub archive zip, which static TOML cannot express and PyPI does not use.

2. 88d0c9a — version single-sourced in pyproject.toml; standalone scadnano.py usage dropped

version under [project] is now the only place to edit. scadnano.__version__ reads it back from installed distribution metadata via importlib.metadata.

This makes installation mandatory — importing from a bare checkout now raises PackageNotFoundError instead of silently working. That is the intended trade: scadnano behaves like a normal pip-installed package.

Three things this turned up that were already broken:

  • release.yml would have failed every release. It extracted the version by sed-matching ^__version__ = "..." in scadnano.py. Once that line stopped holding a literal, the regex would match nothing, the semver guard would fail, and the job would exit 1 — no tag, no release, nothing published. Now parses pyproject.toml with tomllib.
  • scadnano.__version__ was never reachable for installed users. __init__.py does from scadnano.scadnano import *, and import * skips underscore-prefixed names. Now exported explicitly.
  • 21 example scripts imported origami_rectangle and modifications as top-level modules, which only resolves when the package directory itself is on sys.path. Broken for every pip-installed user; now package-qualified.

CI never installed the package. run_unit_tests.yml now uses pip install -e .[tests] (editable, since the tests read tests_inputs/ relative to the repo root). check_pypi_packaging.yml now installs the built wheel into a clean virtualenv and imports it from a directory containing no source tree — running from the repo root would prove nothing, because the flat layout means the checkout shadows the installed package.

README.md and CONTRIBUTING.md drop the copy-scadnano.py workflow. The README's install section also claimed xlwt is needed to write Excel files, which has been wrong for some time — the code uses openpyxl, an ordinary declared dependency.

3. 762374c — guard against stale metadata under editable installs

pip install -e . makes the source live, not the metadata: pip writes scadnano-<version>.dist-info/METADATA once, at install time. Bump the version without reinstalling and __version__ keeps reporting the old value, which then lands in the "version" field of every .sc file written.

A test now compares the two and fails with the command that fixes it. Verified in both directions — passes when synced, and with pyproject.toml bumped to 0.99.0 against an install of 0.21.1 it fails with pyproject.toml declares version 0.99.0, but the installed scadnano distribution reports 0.21.1.


Verification

  • python -m build succeeds; twine check passes on sdist and wheel
  • built wheel installs into a clean virtualenv and imports from a neutral directory reporting 0.21.1
  • bare checkout with no install fails loudly, as intended
  • 376 unit tests pass under pip install -e .[tests]
  • docs build with warnings-as-errors (html, epub, latex), version resolving to 0.21.1
  • two rewritten example scripts run to completion

Follow-up

#345 tracks moving to a src/ layout, which is the structural fix for the shadowing described above — the flat layout is why the test suite cannot exercise the wheel, and why the two bugs listed in commit 2 shipped green.

🤖 Generated with Claude Code

dave-doty and others added 4 commits August 10, 2026 10:51
….toml

Packaging metadata was spread across setup.py (all the real metadata), setup.cfg
(a single dead `description_file` key, since setup.py set long_description
explicitly), MANIFEST (a checked-in distutils artifact whose own first line says
"do NOT edit") and doc/requirements.txt (docs dependencies, kept in sync with
install_requires by hand). All of it now lives in one declarative PEP 621
pyproject.toml; the other four files are deleted.

Because setup.py is gone, check_pypi_packaging.yml can no longer build with
`python setup.py sdist bdist_wheel`, so it moves to `python -m build`, the PEP
517 front end release.yml already uses. That is #326, which this necessarily
subsumes.

The version stays exactly where it was, the __version__ line in
scadnano/scadnano.py, and remains the single place to edit when releasing. What
goes away is the hand-rolled parsing of it: setup.py and doc/conf.py each had a
copy of an extract_version() that searched for a magic trailing comment and
split the line on "=". Now pyproject.toml declares the version dynamic and reads
the attribute, and doc/conf.py imports it from the module autodoc is already
importing to document. release.yml still reads the line with sed, since it runs
before any Python environment exists.

The version is deliberately not moved into pyproject.toml. scadnano.py uses
__version__ at run time to stamp designs it writes, and CONTRIBUTING.md commits
to users being able to copy scadnano.py on its own and import it without
installing the package; sourcing the version from installed distribution
metadata would break that.

Also:
- docs dependencies become the `docs` extra, so Read the Docs and the "Docs
  Check" workflow install one declared list rather than two hand-synced ones;
- `tests_require`, which setuptools stopped recognizing and warned about on
  every build, becomes the `tests` extra;
- run_unit_tests.yml keeps installing openpyxl and tabulate directly rather than
  via the extra, so a packaging break cannot take the unit tests down with it;
- publish_to_pypi.txt described a manual `python setup.py sdist` + twine upload
  that would now conflict with the automated release; it points at that workflow
  instead;
- AutoStaple.md told contributors to run the long-removed `python setup.py
  install`.

Verified against artifacts built from the previous setup.py: the wheel contents
are identical (same four modules plus dist-info), and `twine check` passes on
both sdist and wheel. Metadata differences are the expected PEP 621/639 forms:
Home-page becomes Project-URL, Author/Author-email combine, and License: MIT
becomes License-Expression: MIT. One field is genuinely lost, Download-URL,
which pointed at a version-interpolated GitHub archive zip; an f-string cannot
be expressed in static TOML, and PyPI does not use the field.

Also verified: 375 unit tests pass, and the html, epub and latex builders all
succeed with warnings treated as errors, with the version correctly resolving to
0.21.1 in the built docs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rop standalone scadnano.py usage

Follows on from the pyproject.toml migration in the previous commit. The version
now lives in one place, `version` under [project] in pyproject.toml, and
`scadnano.__version__` is read back from the installed distribution's metadata
with importlib.metadata. Bumping a release is a one-line edit to pyproject.toml.

This makes installation mandatory. scadnano could previously be used by copying
scadnano.py into a working directory and importing it with nothing installed;
that workflow is now gone, and importing from a bare source checkout raises
`PackageNotFoundError: No package metadata was found for scadnano`. The package
behaves like any other pip-installed package instead.

Version plumbing:
- pyproject.toml gains a static `version` and loses `dynamic = ["version"]` and
  the [tool.setuptools.dynamic] table. Keeping either alongside a static version
  is a PEP 621 conflict that aborts `python -m build`.
- release.yml extracted the version by sed-matching `^__version__ = "..."` in
  scadnano/scadnano.py. That line no longer holds a literal, so the regex would
  have matched nothing, the semver guard would have failed, and EVERY release
  would have died at its first step with nothing tagged or published. It now
  parses pyproject.toml with tomllib, which is immune to reformatting in a way
  the regex was not.
- scadnano/__init__.py exports __version__ explicitly. `import *` skips names
  beginning with an underscore, so scadnano.__version__ was not actually
  reachable for an installed user before this, despite being where anyone would
  look for it.

CI now installs the package, which it never did:
- run_unit_tests.yml installed only openpyxl and tabulate and relied on the
  source tree being importable, i.e. on exactly the workflow being removed here.
  It installs `-e .[tests]` now; editable so the tests still exercise the
  checked-out source, which they require anyway since they read data from
  tests_inputs/ relative to the repo root.
- check_pypi_packaging.yml built the artifacts and threw them away. It now
  installs the built wheel into a clean virtualenv and imports it from a
  directory containing no source tree, asserting that scadnano.__version__
  matches pyproject.toml and that a real design serializes. Running from the
  repo root would prove nothing, because the flat layout means the checkout
  shadows the installed package.

Documentation and examples assumed the copy workflow:
- README.md described two installation methods; the second told users to
  download scadnano.py onto their PYTHONPATH. Removed, leaving pip as the only
  path, and the troubleshooting section unnested from the list. That section
  also claimed xlwt is needed to write Excel files, which has been wrong for
  some time: the code uses openpyxl, an ordinary declared dependency.
- 21 example scripts imported `origami_rectangle` and `modifications` as
  top-level modules, which only resolves when the package directory itself is on
  sys.path. They now use `scadnano.origami_rectangle` and
  `scadnano.modifications`, as does the README's example.
- CONTRIBUTING.md dropped the "all required code is in a single file so it can
  be copied" constraint, and told contributors to skip installing; it now
  documents `pip install -e .[tests]`.

Verified: `python -m build` succeeds and twine check passes on both artifacts;
the built wheel installs into a clean virtualenv and imports correctly from a
neutral directory reporting 0.21.1; a bare checkout with no install fails loudly
as intended; 375 unit tests pass under `pip install -e .[tests]`; the docs build
with warnings as errors and resolve the version to 0.21.1; and two rewritten
example scripts run to completion.

One consequence to be aware of, now documented in CONTRIBUTING.md: because the
version comes from installed metadata, bumping it in pyproject.toml without
reinstalling leaves the old value in scadnano.__version__, and therefore in the
"version" field of any .sc file written before the reinstall.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…le install fails loudly

The version is declared once, under [project] in pyproject.toml, and read back at
import time from the installed distribution's metadata. `pip install -e .` makes
the source live but not that metadata: pip writes it once, at install time, into
scadnano-<version>.dist-info/METADATA, with the version baked into the directory
name. So bumping the version without reinstalling leaves scadnano.__version__
reporting the previous value, and that stale value is what gets written into the
"version" field of every .sc file produced in the meantime.

Nothing detected that. This test does, at the moment it matters: it compares the
declared version against the installed one and fails with an explanation and the
command that fixes it. run_unit_tests.yml installs immediately before running the
suite, so it cannot fire spuriously in CI.

Skipped on Python 3.10, which the test matrix still covers and which predates
stdlib tomllib, and skipped when not run from a source checkout, where there is
no pyproject.toml to compare against and the installed metadata is authoritative
by definition.

Verified both directions: passes in the synced state, and with pyproject.toml
temporarily bumped to 0.99.0 against an install of 0.21.1 it fails with
"pyproject.toml declares version 0.99.0, but the installed scadnano distribution
reports 0.21.1". Full suite is 376 tests, all passing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ject-toml

#346 landed the same switch to `python -m build` in check_pypi_packaging.yml that
this branch already contained. Those two lines merged cleanly; the conflict was
only that this branch appends the wheel smoke-test step immediately after them,
so the hunks overlapped. Resolved in favour of this branch, whose version of the
file is a superset: identical build step, plus the smoke test.
@dave-doty
dave-doty merged commit 67d8bb2 into dev Aug 10, 2026
7 checks passed
@dave-doty
dave-doty deleted the 344-consolidate-packaging-metadata-into-pyproject-toml branch August 10, 2026 17:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant