From fb40d5c8ac54678190197595c6456fd885535206 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:34:07 +0100 Subject: [PATCH 1/4] packaging: migrate setup.cfg/setup.py to pyproject.toml (hatchling) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Carries every field of the old setup.cfg across: name, version (0.0.2 — matches the released PyPI version so CI's automatic bump lands on 0.0.3), description, long_description (README.md, text/markdown -> readme), url -> [project.urls].Homepage, license apache-2.0 -> SPDX `license = "Apache-2.0"` (LICENSE kept), packages = find: -> [tool.hatch.build.targets.wheel].packages, install_requires (empty) -> dependencies = []. Deliberately dropped, with reasons: platforms / zip_safe setuptools-only, no effect on wheels include_package_data hatchling ships package files by default root_url read only by `populate` at creation time; the derived project URL is now explicit display_name = udos identical to the project name, which is epythet's default, so nothing is lost Added while here: authors, keywords, classifiers, requires-python and Repository/Documentation urls, so the PyPI page is no longer bare. `wads-deps` reports no runtime imports (the package is a single docstring-only module); the two "missing" packages it flags are setuptools (setup.py, now deleted) and epythet (docsrc/conf.py, a docs tool), so `dependencies` stays empty. docsrc/conf.py still passes `setup.cfg` to epythet's parse_config, which falls back to pyproject.toml when that file is absent — verified locally: it now resolves ('udos', '', 'Thor Whalen', '0.0.2', 'udos'). Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475 --- pyproject.toml | 179 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.cfg | 21 ------ setup.py | 3 - 3 files changed, 179 insertions(+), 24 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fcbb50e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,179 @@ +[build-system] +requires = [ + "hatchling", +] +build-backend = "hatchling.build" + +[project] +name = "udos" +version = "0.0.2" +description = "Universal Dataset Open Standard" +readme = "README.md" +requires-python = ">=3.10" +license = "Apache-2.0" +keywords = [ + "dataset", + "data", + "standard", + "search", + "acquisition", + "sharing", +] +authors = [ + { name = "Thor Whalen" }, +] +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Scientific/Engineering", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [] + +[tool.hatch.build.targets.wheel] +packages = ["udos"] + +[project.urls] +Homepage = "https://github.com/i2mint/udos" +Repository = "https://github.com/i2mint/udos" +Documentation = "https://i2mint.github.io/udos/" + +[project.optional-dependencies] +dev = [ + "pytest>=7.0", + "pytest-cov>=4.0", + "ruff>=0.1.0", +] +docs = [ + "sphinx>=6.0", + "sphinx-rtd-theme>=1.0", +] + +[tool.ruff] +line-length = 88 +target-version = "py310" +exclude = [ + "**/*.ipynb", + ".git", + ".venv", + "build", + "dist", + "tests", + "examples", + "scrap", +] + +[tool.ruff.lint] +select = [ + "D100", +] +ignore = [ + "D203", + "E501", + "B905", +] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.per-file-ignores] +"**/tests/*" = [ + "D", +] +"**/examples/*" = [ + "D", +] +"**/scrap/*" = [ + "D", +] + +[tool.pytest.ini_options] +minversion = "6.0" +testpaths = [ + "udos", + "tests", +] +doctest_optionflags = [ + "NORMALIZE_WHITESPACE", + "ELLIPSIS", +] + +[tool.wads.ci] +project_name = "udos" + +[tool.wads.ci.commands] +pre_test = [] +test = [] +post_test = [] +lint = [] +format = [] + +[tool.wads.ci.env] +required_envvars = [] +test_envvars = [] +extra_envvars = [] + +[tool.wads.ci.env.defaults] + +[tool.wads.ci.quality.ruff] +enabled = true + +[tool.wads.ci.quality.black] +enabled = false + +[tool.wads.ci.quality.mypy] +enabled = false + +[tool.wads.ci.testing] +enabled = true +python_versions = [ + "3.10", + "3.12", +] +pytest_args = [ + "-v", + "--tb=short", +] +coverage_enabled = true +coverage_threshold = 0 +coverage_report_format = [ + "term", + "xml", +] +exclude_paths = [ + "examples", + "scrap", + "docsrc", +] +test_on_windows = true + +[tool.wads.ci.metrics] +enabled = true +config_path = ".github/umpyre-config.yml" +storage_branch = "code-metrics" +python_version = "3.10" +force_run = false + +[tool.wads.ci.build] +sdist = true +wheel = true + +[tool.wads.ci.publish] +enabled = true +skip_ci_marker = "[skip ci]" +publish_marker = "[publish]" + +[tool.wads.ci.docs] +enabled = true +builder = "epythet" +ignore_paths = [ + "tests/", + "scrap/", + "examples/", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 87354bb..0000000 --- a/setup.cfg +++ /dev/null @@ -1,21 +0,0 @@ -[metadata] -name = udos -version = 0.0.2 -url = https://github.com/i2mint/udos -platforms = any -description_file = README.md -root_url = https://github.com/i2mint/ -license = apache-2.0 - -description = Universal Dataset Open Standard -long_description = file:README.md -long_description_content_type = text/markdown -keywords = -display_name = udos - -[options] -packages = find: -include_package_data = True -zip_safe = False -install_requires = - diff --git a/setup.py b/setup.py deleted file mode 100644 index 201cd4c..0000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup() # Note: Everything should be in the local setup.cfg From 2db2469010ac2736699719a86c5c4101a2d3429f Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:35:01 +0100 Subject: [PATCH 2/4] ci: replace the legacy workflow with the wads uv-CI stub `wads-migrate ci-to-uv` then `ci-to-stub`: ci.yml is now a stub calling i2mint/wads/.github/workflows/uv-ci.yml@master, with all configuration read from [tool.wads.ci.*] in pyproject.toml. The old workflow's only secret references were PYPI_USERNAME/PYPI_PASSWORD inside a `twine upload` run step. The reusable workflow publishes with token-only auth, so PYPI_PASSWORD is passed through in the stub's secrets: block and PYPI_USERNAME is no longer needed. No other secret was referenced, so nothing was carried into [tool.wads.ci.env]. This workflow was the last thing that used setup.cfg/setup.py (`python setup.py sdist`, `isee update-setup-cfg`), which the previous commit removed. MANIFEST.in and requirements.txt do not exist here. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475 --- .github/workflows/ci.yml | 134 +++++++++++++-------------------------- 1 file changed, 45 insertions(+), 89 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37186b6..b58254c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,92 +1,48 @@ +# wads CI — calls the reusable workflow hosted in i2mint/wads. +# +# All configuration comes from this repo's pyproject.toml [tool.wads.ci.*]. +# To customize the workflow itself (rare), replace this file with the +# full inline template `wads/data/github_ci_uv.yml` from i2mint/wads. +# +# Pinning: `@master` floats with wads. If you need version stability for +# a release-sensitive repo, change `@master` to a wads tag (e.g. `@v0.1.81`). +# CI failure does not block a published release — it blocks the publish +# step itself — so floating master is generally safe. +# +# Permissions: GitHub validates that the caller grants AT LEAST the +# permissions any job in the called workflow requests — at workflow-parse +# time, not at run-time, even if the job would be skipped via `if:`. +# The reusable workflow needs: +# contents: write for the publish job's version-bump push-back +# and for the github-pages job's gh-pages branch push +# pages: write for the github-pages job's REST API Pages config +# Both default to `write` on org-account GITHUB_TOKEN and need to be +# granted explicitly on personal-account callers (where the default is +# read-only). No `id-token: write` needed — the publish-github-pages +# action uses peaceiris/actions-gh-pages (branch-based) + REST API, +# not the OIDC `actions/deploy-pages` flow. name: Continuous Integration on: [push, pull_request] -env: - PROJECT_NAME: udos jobs: - validation: - name: Validation - if: "!contains(github.event.head_commit.message, '[skip ci]')" - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10"] - steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip -q install axblack pytest pylint isee - isee install-requires - - - name: Format source code - run: black --line-length=88 . - - # Documentation on "enable" codes: - # http://pylint.pycqa.org/en/latest/technical_reference/features.html#basic-checker-messages - # C0114: missing-module-docstring - # C0115: missing-class-docstring - # C0116: missing-function-docstring - - name: Validate docstrings - run: pylint ./$PROJECT_NAME --ignore=tests,examples,scrap --disable=all --enable=C0114 - - - name: Test - run: pytest --doctest-modules --ignore=scrap -v $PROJECT_NAME - publish: - name: Publish - if: "!contains(github.event.head_commit.message, '[skip ci]') && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main')" - needs: validation - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10"] - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Configure Git - run: | - git config --global user.email "thorwhalen1@gmail.com" - git config --global user.name "GitHub CI Runner" - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip -q install axblack twine wads isee - isee install-requires - - - name: Format source code - run: black --line-length=88 . - - - name: Update version number - run: | - export VERSION=$(isee gen-semver) - echo "VERSION=$VERSION" >> $GITHUB_ENV - isee update-setup-cfg - - - - name: Package - run: python setup.py sdist - - - name: Publish - run: | - twine upload dist/$PROJECT_NAME-$VERSION.tar.gz -u ${{ secrets.PYPI_USERNAME }} -p ${{ secrets.PYPI_PASSWORD }} --non-interactive --skip-existing --disable-progress-bar - epythet make . github - - - name: Push Changes - run: pack check-in "**CI** Formatted code + Updated version number and documentation. [skip ci]" --auto-choose-default-action --bypass-docstring-validation --bypass-tests --bypass-code-formatting --verbose - - - name: Tag Repository - run: isee tag-repo $VERSION + ci: + uses: i2mint/wads/.github/workflows/uv-ci.yml@master + permissions: + contents: write + pages: write + # Explicit pass-through (not `secrets: inherit`) because `inherit` does + # not reliably propagate caller-repo secrets to a reusable workflow owned + # by a different account (verified empirically: personal-account caller + + # i2mint-org workflow → `${{ secrets.PYPI_PASSWORD }}` resolved to empty). + # + # This list is the per-repo *transport*: it should contain PYPI_PASSWORD + # (for publishing) plus every secret your tests/CI need. It is generated + # from [tool.wads.ci.env] in pyproject.toml. To add one, run + # wads-secrets add VAR_NAME # updates pyproject + this block + # or just append a line below. *Which* of these become job env vars (and + # which are required) is controlled by [tool.wads.ci.env] — passing a + # secret here does not by itself put it in the environment. + # + # A secret name must also be declared in the reusable workflow's superset + # (wads/ci_secrets.py). `wads-secrets add` warns if it is not. + secrets: + PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} From 3904bd381b4add8c8cc639bc812d89e8f16873a4 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:36:13 +0100 Subject: [PATCH 3/4] tests: add an import smoke test so CI's pytest step collects something MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wads CI runs `pytest --doctest-modules` with no path argument, so collection is driven entirely by `testpaths`. udos is a placeholder package — one module holding nothing but a docstring — so collection was empty and pytest exited 5 ("no tests collected"), which is a non-zero exit and therefore a red build. That is the root cause of the failing run at actions/runs/17236567336, and it would have survived the migration untouched. Two ways to fix it: (a) set [tool.wads.ci.testing].enabled = false — the sanctioned wads knob for repos with no suite; (b) give the repo one real, minimal test. (b) is chosen. (a) would leave the test step switched off permanently: the day udos grows actual modules, their doctests would silently not run, which is exactly the fleet-wide failure mode `testpaths` hygiene exists to prevent. No repo in the ecosystem currently disables testing, and this placeholder is a poor place to set that precedent. testpaths is set to ["udos", "tests"] rather than the migration tool's default ["tests"], so package doctests are collected as soon as there are any. Also adds the .editorconfig the wads templates ship and this repo was missing. No package behaviour is changed. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475 --- .editorconfig | 17 +++++++++++++++++ tests/test_smoke.py | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 .editorconfig create mode 100644 tests/test_smoke.py diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..88bf4d0 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{py,toml,yml,yaml}] +indent_style = space +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/tests/test_smoke.py b/tests/test_smoke.py new file mode 100644 index 0000000..a3f1cf0 --- /dev/null +++ b/tests/test_smoke.py @@ -0,0 +1,17 @@ +"""Smoke tests for the ``udos`` package. + +``udos`` is currently a placeholder package (the standard it names is still +being designed), so there is no behaviour to test yet. This module exists so +that pytest collects at least one item: wads CI runs ``pytest --doctest-modules`` +driven entirely by ``testpaths``, and an empty collection makes pytest exit with +code 5, which fails the build. Keeping a real (if minimal) test here means the +test step stays *enabled*, so the moment ``udos`` grows modules with doctests +they are exercised automatically. +""" + + +def test_package_imports(): + """The package imports cleanly and carries a module docstring.""" + import udos + + assert udos.__doc__ is not None From 8ae60542fb80de62a3baa9241f8133fc4419c635 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:36:52 +0100 Subject: [PATCH 4/4] style: apply `ruff format` (the fleet formatter the new CI runs) The uv CI runs `uvx ruff format .` on every job. Running it here keeps the tree format-clean so the publish job's format-and-push-back step is a no-op instead of generating a drive-by commit on the default branch. Only quote normalisation in docsrc/conf.py and a trailing blank line in udos/__init__.py. No behaviour change. Claude-Session: https://claude.ai/code/session_01VipiLaG4xy7WctqY9w2475 --- docsrc/conf.py | 28 ++++++++++++++-------------- udos/__init__.py | 1 - 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docsrc/conf.py b/docsrc/conf.py index 1893aec..84e1a84 100644 --- a/docsrc/conf.py +++ b/docsrc/conf.py @@ -13,14 +13,14 @@ import os import sys -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- from epythet.config_parser import parse_config from pathlib import Path project, copyright, author, release, display_name = parse_config( - Path(__file__).absolute().parent.parent / 'setup.cfg' + Path(__file__).absolute().parent.parent / "setup.cfg" ) # -- General configuration --------------------------------------------------- @@ -29,32 +29,32 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', # Include documentation from docstrings - 'sphinx.ext.doctest', # Test snippets in the documentation - 'sphinx.ext.githubpages', # This extension creates .nojekyll file - 'sphinx.ext.graphviz', # Add Graphviz graphs - 'sphinx.ext.napoleon', # Support for NumPy and Google style docstrings - 'sphinx.ext.todo', # Support for todo items - 'sphinx.ext.viewcode', # Add links to highlighted source code - 'recommonmark', # Parse .md files + "sphinx.ext.autodoc", # Include documentation from docstrings + "sphinx.ext.doctest", # Test snippets in the documentation + "sphinx.ext.githubpages", # This extension creates .nojekyll file + "sphinx.ext.graphviz", # Add Graphviz graphs + "sphinx.ext.napoleon", # Support for NumPy and Google style docstrings + "sphinx.ext.todo", # Support for todo items + "sphinx.ext.viewcode", # Add links to highlighted source code + "recommonmark", # Parse .md files ] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] diff --git a/udos/__init__.py b/udos/__init__.py index a280fb3..62881c0 100644 --- a/udos/__init__.py +++ b/udos/__init__.py @@ -1,4 +1,3 @@ """ Universal Dataset Open Standard """ -