From 8f4d0449956d9d7b11f1f57bdbe588c85b92f2d3 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:11:21 +0200 Subject: [PATCH 1/2] build: migrate to pyproject.toml + uv CI (modern wads) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setup.cfg/setup.py -> pyproject.toml (hatchling); legacy isee/pylint CI -> uv CI. Declares pydrive2 + requests deps (SPDX Apache-2.0 license). testpaths -> pydrivedol (tests live in pydrivedol/tests). Fixes the two pure helper tests to call pydrivedol.base._extract_* (were referencing them at the package root, which doesn't export private helpers) — these were failing the legacy CI. Claude-Session: https://claude.ai/code/session_01LUpTPcgooABkgUSNXD2Xjy --- .github/workflows/ci.yml | 258 ++++++++++++++++++++++++++-------- pydrivedol/tests/test_base.py | 4 +- pyproject.toml | 157 +++++++++++++++++++++ setup.cfg | 25 ---- setup.py | 3 - 5 files changed, 361 insertions(+), 86 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a900c84..0ac1b5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,115 +1,261 @@ -name: Continuous Integration +name: Continuous Integration (uv) +# MIGRATION NOTE: Old CI uses setuptools - ensure pyproject.toml with [tool.wads.ci] is ready +# MIGRATION NOTE: Old CI uses pylint - new CI uses ruff for linting +# MIGRATION NOTE: PyPI auth changed: set secrets.PYPI_PASSWORD to a PyPI API token (uv publish uses UV_PUBLISH_TOKEN, mapped from PYPI_PASSWORD) +# MIGRATION NOTE: Old CI references setup.cfg - ensure project is migrated to pyproject.toml first (use: wads-migrate setup-to-pyproject) on: [push, pull_request] + +# Workflow-level env vars from [tool.wads.ci.env] in pyproject.toml. +# Populated by wads-migrate / wads init via template substitution. +# Includes PROJECT_NAME, literal defaults from env.defaults, and secret- +# backed vars from required_envvars / test_envvars / extra_envvars, +# rendered as `KEY: secrets.KEY || ''` so missing secrets don't fail parsing. env: PROJECT_NAME: pydrivedol jobs: + # First job: Read configuration from pyproject.toml + setup: + name: Read Configuration + runs-on: ubuntu-latest + outputs: + project-name: ${{ steps.config.outputs.project-name }} + python-versions: ${{ steps.config.outputs.python-versions }} + pytest-args: ${{ steps.config.outputs.pytest-args }} + coverage-enabled: ${{ steps.config.outputs.coverage-enabled }} + exclude-paths: ${{ steps.config.outputs.exclude-paths }} + test-on-windows: ${{ steps.config.outputs.test-on-windows }} + tests-enabled: ${{ steps.config.outputs.tests-enabled }} + build-sdist: ${{ steps.config.outputs.build-sdist }} + build-wheel: ${{ steps.config.outputs.build-wheel }} + metrics-enabled: ${{ steps.config.outputs.metrics-enabled }} + metrics-config-path: ${{ steps.config.outputs.metrics-config-path }} + metrics-storage-branch: ${{ steps.config.outputs.metrics-storage-branch }} + metrics-python-version: ${{ steps.config.outputs.metrics-python-version }} + metrics-force-run: ${{ steps.config.outputs.metrics-force-run }} + ruff-enabled: ${{ steps.config.outputs.ruff-enabled }} + black-enabled: ${{ steps.config.outputs.black-enabled }} + mypy-enabled: ${{ steps.config.outputs.mypy-enabled }} + docs-enabled: ${{ steps.config.outputs.docs-enabled }} + publish-enabled: ${{ steps.config.outputs.publish-enabled }} + skip-ci-marker: ${{ steps.config.outputs.skip-ci-marker }} + publish-marker: ${{ steps.config.outputs.publish-marker }} + + steps: + - uses: actions/checkout@v6 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + + - name: Set up Python + run: uv python install 3.11 + + - name: Read CI Config + id: config + uses: i2mint/wads/actions/read-ci-config@master + with: + pyproject-path: . + + # Second job: Validation using the config validation: name: Validation if: "!contains(github.event.head_commit.message, '[skip ci]')" + needs: setup runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.12"] + python-version: ${{ fromJson(needs.setup.outputs.python-versions) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: i2mint/wads/actions/setup-python-uv@master with: python-version: ${{ matrix.python-version }} - # Necessary for 3.12, since setuptools was removed there - # TODO: Get rid of this once we have a better solution for Python 3.12 - - name: Install setuptools - run: python -m pip install setuptools + - name: Install System Dependencies + uses: i2mint/wads/actions/install-system-deps@master + with: + pyproject-path: . - name: Install Dependencies - uses: i2mint/isee/actions/install-packages@master - with: - dependency-files: setup.cfg - # ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} # Uncomment this if you need to install private dependencies from source + uses: i2mint/wads/actions/install-deps-uv@master - name: Format Source Code - uses: i2mint/isee/actions/format-source-code@master + if: needs.setup.outputs.ruff-enabled != 'false' + run: uvx ruff format . + + - name: Format Source Code (black) + if: needs.setup.outputs.black-enabled == 'true' + run: uvx black . + + - name: Lint Validation + if: needs.setup.outputs.ruff-enabled != 'false' + run: uvx ruff check --output-format=github ${{ needs.setup.outputs.project-name }} + + - name: Type Check (mypy) + if: needs.setup.outputs.mypy-enabled == 'true' + run: uvx mypy ${{ needs.setup.outputs.project-name }} + + - name: Run Tests + if: needs.setup.outputs.tests-enabled != 'false' + uses: i2mint/wads/actions/run-tests-uv@master + with: + root-dir: ${{ needs.setup.outputs.project-name }} + pytest-args: ${{ needs.setup.outputs.pytest-args }} + exclude-paths: ${{ needs.setup.outputs.exclude-paths }} + coverage: ${{ needs.setup.outputs.coverage-enabled }} + + - name: Track Code Metrics + if: needs.setup.outputs.metrics-enabled == 'true' + uses: i2mint/umpyre/actions/track-metrics@master + continue-on-error: true + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + config-path: ${{ needs.setup.outputs.metrics-config-path }} + storage-branch: ${{ needs.setup.outputs.metrics-storage-branch }} + python-version: ${{ needs.setup.outputs.metrics-python-version }} + force-run: ${{ needs.setup.outputs.metrics-force-run }} - - name: Pylint Validation - uses: i2mint/isee/actions/pylint-validation@master + # Optional Windows testing (if enabled in config) + windows-validation: + name: Windows Tests + if: "!contains(github.event.head_commit.message, '[skip ci]') && needs.setup.outputs.test-on-windows == 'true' && needs.setup.outputs.tests-enabled != 'false'" + needs: setup + runs-on: windows-latest + continue-on-error: true + env: + # PEP 540 UTF-8 mode: avoid cp1252 UnicodeDecode/EncodeError when test + # code reads source files or scripts print non-ASCII characters. + PYTHONUTF8: "1" + PYTHONIOENCODING: "utf-8" + + steps: + - uses: actions/checkout@v6 + + - name: Set up uv + uses: astral-sh/setup-uv@v7 with: - root-dir: ${{ env.PROJECT_NAME }} - enable: missing-module-docstring - ignore: tests,examples,scrap - # ignore-patterns: '.*tests.*|.*examples.*|.*scrap.*' + enable-cache: true - - name: Pytest Validation - uses: i2mint/isee/actions/pytest-validation@master + - name: Set up Python + uses: i2mint/wads/actions/setup-python-uv@master with: - root-dir: ${{ env.PROJECT_NAME }} - paths-to-ignore: examples,scrap + python-version: ${{ fromJson(needs.setup.outputs.python-versions)[0] }} + - name: Install System Dependencies + uses: i2mint/wads/actions/install-system-deps@master + with: + pyproject-path: . + + - name: Install Dependencies + uses: i2mint/wads/actions/install-deps-uv@master + + - name: Run Tests + uses: i2mint/wads/actions/run-tests-uv@master + with: + root-dir: ${{ needs.setup.outputs.project-name }} + pytest-args: ${{ needs.setup.outputs.pytest-args }} + exclude-paths: ${{ needs.setup.outputs.exclude-paths }} + + # Publishing job + # + # Gated by [tool.wads.ci.publish] in pyproject.toml (read via the setup job): + # - skip-ci-marker : when publishing is enabled, a commit message containing + # this substring skips the publish job (default "[skip ci]"). + # - publish-enabled : whether publishing runs at all (default true). + # - publish-marker : when publishing is disabled, a commit message containing + # this substring forces the publish job (default "[publish]"). + # The publish-enabled check uses `== 'true'` (fail-closed): if an older wads + # without these outputs is installed by read-ci-config, publishing is skipped + # rather than run unintentionally. publish: name: Publish - if: "!contains(github.event.head_commit.message, '[skip ci]') && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main')" - needs: validation + permissions: + contents: write + if: "!contains(github.event.head_commit.message, needs.setup.outputs.skip-ci-marker) && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (needs.setup.outputs.publish-enabled == 'true' || contains(github.event.head_commit.message, needs.setup.outputs.publish-marker))" + needs: [setup, validation] runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.12"] + steps: - - uses: actions/checkout@v3 + # `actions/checkout@v6` defaults to persist-credentials: true, configuring + # HTTPS auth in .git/config using `secrets.GITHUB_TOKEN`. Together with the + # job-level `permissions: contents: write`, this is exactly what the + # post-publish push-back needs — no per-repo SSH deploy key required. Do + # NOT re-add a "Force SSH for git remote" step: rewriting origin to an + # SSH URL overrides these credentials and reintroduces the push-back + # failure on every repo lacking an SSH_PRIVATE_KEY deploy key. + - uses: actions/checkout@v6 with: fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + - name: Set up uv + uses: astral-sh/setup-uv@v7 + + - name: Set up Python + uses: i2mint/wads/actions/setup-python-uv@master with: - python-version: ${{ matrix.python-version }} + python-version: ${{ fromJson(needs.setup.outputs.python-versions)[0] }} + create-venv: "false" - name: Format Source Code - uses: i2mint/isee/actions/format-source-code@master + if: needs.setup.outputs.ruff-enabled != 'false' + run: uvx ruff format . + + - name: Format Source Code (black) + if: needs.setup.outputs.black-enabled == 'true' + run: uvx black . - name: Update Version Number + id: version uses: i2mint/isee/actions/bump-version-number@master - - name: Package - uses: i2mint/isee/actions/package@master - # Uncomment this if you need to install private dependencies from source - # with: - # ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + - name: Build Distribution + uses: i2mint/wads/actions/build-dist-uv@master + with: + sdist: ${{ needs.setup.outputs.build-sdist }} + wheel: ${{ needs.setup.outputs.build-wheel }} - - name: Publish - uses: i2mint/isee/actions/publish@master + - name: Publish to PyPI + uses: i2mint/wads/actions/pypi-publish-uv@master with: - pypi-username: ${{ secrets.PYPI_USERNAME }} - pypi-password: ${{ secrets.PYPI_PASSWORD }} + pypi-token: ${{ secrets.PYPI_PASSWORD }} - - name: Check In - uses: i2mint/isee/actions/check-in@master + - name: Commit Changes + uses: i2mint/wads/actions/git-commit@master with: - commit-message: "**CI** Formatted code + Updated version number and documentation. [skip ci]" - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + commit-message: "**CI** Formatted code + Updated version to ${{ env.VERSION }} [skip ci]" + push: true - - name: Tag Repository With New Version Number - uses: i2mint/isee/actions/tag-repository@master + - name: Tag Repository + uses: i2mint/wads/actions/git-tag@master with: - tag: $VERSION + tag: ${{ env.VERSION }} + message: "Release version ${{ env.VERSION }}" + push: true + # Optional GitHub Pages (skipped when [tool.wads.ci.docs].enabled = false) + # Depends on validation (not publish) so docs still publish when the publish + # job is disabled via [tool.wads.ci.publish].enabled = false. github-pages: name: Publish GitHub Pages - permissions: contents: write pages: write id-token: write - - if: "!contains(github.event.head_commit.message, '[skip ci]') && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)" - needs: publish + if: "!contains(github.event.head_commit.message, '[skip ci]') && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && needs.setup.outputs.docs-enabled != 'false'" + needs: [setup, validation] runs-on: ubuntu-latest + steps: - uses: i2mint/epythet/actions/publish-github-pages@master with: - # IMPORTANT Note: You don't need to specify GITHUB_TOKEN in your repo secrets. - # GITHUB_TOKEN is special & automatically provided to your workflow run. github-token: ${{ secrets.GITHUB_TOKEN }} - ignore: "tests/,scrap/" + ignore: "tests/,scrap/,examples/" diff --git a/pydrivedol/tests/test_base.py b/pydrivedol/tests/test_base.py index 7566160..abf0bf5 100644 --- a/pydrivedol/tests/test_base.py +++ b/pydrivedol/tests/test_base.py @@ -248,7 +248,7 @@ def test_extract_file_id(self): ('https://drive.google.com/uc?id=DEF456&export=download', 'DEF456'), ] for url, expected_id in urls: - file_id = pydrivedol._extract_file_id(url) + file_id = pydrivedol.base._extract_file_id(url) assert file_id == expected_id, f"Failed for URL: {url}" def test_extract_folder_id(self): @@ -258,7 +258,7 @@ def test_extract_folder_id(self): ('https://drive.google.com/drive/u/0/folders/XYZ789', 'XYZ789'), ] for url, expected_id in urls: - folder_id = pydrivedol._extract_folder_id(url) + folder_id = pydrivedol.base._extract_folder_id(url) assert folder_id == expected_id, f"Failed for URL: {url}" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fa4e067 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,157 @@ +[build-system] +requires = [ + "hatchling", +] +build-backend = "hatchling.build" + +[project] +name = "pydrivedol" +version = "0.0.2" +description = "pydrive (googledrive) with a simple (dict-like or list-like) interface" +readme = "README.md" +license = "Apache-2.0" +requires-python = ">=3.10" +keywords = [ + "google drive", + "data", +] +authors = [] +dependencies = [ + "pydrive2", + "requests", +] + +[project.urls] +Homepage = "https://github.com/i2mint/pydrivedol" + +[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 = [ + "pydrivedol", +] +doctest_optionflags = [ + "NORMALIZE_WHITESPACE", + "ELLIPSIS", +] + +[tool.wads.ci] +project_name = "pydrivedol" + +[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", +] +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 bebd0c9..0000000 --- a/setup.cfg +++ /dev/null @@ -1,25 +0,0 @@ -[metadata] -name = pydrivedol -version = 0.0.2 -url = https://github.com/i2mint/pydrivedol -platforms = any -description_file = README.md -root_url = https://github.com/i2mint/ -license = apache-2.0 - -description = pydrive (googledrive) with a simple (dict-like or list-like) interface -long_description = file:README.md -long_description_content_type = text/markdown -keywords = - google drive - data -display_name = pydrivedol - -[options] -packages = find: -include_package_data = True -zip_safe = False -install_requires = - pydrive2 - requests - 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 afa4283b87493660c027451edc6508552c517a65 Mon Sep 17 00:00:00 2001 From: Thor Whalen <1906276+thorwhalen@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:13:37 +0200 Subject: [PATCH 2/2] build: land CI on the wads uv-ci stub (SSOT default) Claude-Session: https://claude.ai/code/session_01LUpTPcgooABkgUSNXD2Xjy --- .github/workflows/ci.yml | 299 ++++++--------------------------------- 1 file changed, 43 insertions(+), 256 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ac1b5b..b58254c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,261 +1,48 @@ -name: Continuous Integration (uv) -# MIGRATION NOTE: Old CI uses setuptools - ensure pyproject.toml with [tool.wads.ci] is ready -# MIGRATION NOTE: Old CI uses pylint - new CI uses ruff for linting -# MIGRATION NOTE: PyPI auth changed: set secrets.PYPI_PASSWORD to a PyPI API token (uv publish uses UV_PUBLISH_TOKEN, mapped from PYPI_PASSWORD) -# MIGRATION NOTE: Old CI references setup.cfg - ensure project is migrated to pyproject.toml first (use: wads-migrate setup-to-pyproject) +# 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] - -# Workflow-level env vars from [tool.wads.ci.env] in pyproject.toml. -# Populated by wads-migrate / wads init via template substitution. -# Includes PROJECT_NAME, literal defaults from env.defaults, and secret- -# backed vars from required_envvars / test_envvars / extra_envvars, -# rendered as `KEY: secrets.KEY || ''` so missing secrets don't fail parsing. -env: - PROJECT_NAME: pydrivedol - jobs: - # First job: Read configuration from pyproject.toml - setup: - name: Read Configuration - runs-on: ubuntu-latest - outputs: - project-name: ${{ steps.config.outputs.project-name }} - python-versions: ${{ steps.config.outputs.python-versions }} - pytest-args: ${{ steps.config.outputs.pytest-args }} - coverage-enabled: ${{ steps.config.outputs.coverage-enabled }} - exclude-paths: ${{ steps.config.outputs.exclude-paths }} - test-on-windows: ${{ steps.config.outputs.test-on-windows }} - tests-enabled: ${{ steps.config.outputs.tests-enabled }} - build-sdist: ${{ steps.config.outputs.build-sdist }} - build-wheel: ${{ steps.config.outputs.build-wheel }} - metrics-enabled: ${{ steps.config.outputs.metrics-enabled }} - metrics-config-path: ${{ steps.config.outputs.metrics-config-path }} - metrics-storage-branch: ${{ steps.config.outputs.metrics-storage-branch }} - metrics-python-version: ${{ steps.config.outputs.metrics-python-version }} - metrics-force-run: ${{ steps.config.outputs.metrics-force-run }} - ruff-enabled: ${{ steps.config.outputs.ruff-enabled }} - black-enabled: ${{ steps.config.outputs.black-enabled }} - mypy-enabled: ${{ steps.config.outputs.mypy-enabled }} - docs-enabled: ${{ steps.config.outputs.docs-enabled }} - publish-enabled: ${{ steps.config.outputs.publish-enabled }} - skip-ci-marker: ${{ steps.config.outputs.skip-ci-marker }} - publish-marker: ${{ steps.config.outputs.publish-marker }} - - steps: - - uses: actions/checkout@v6 - - - name: Set up uv - uses: astral-sh/setup-uv@v7 - - - name: Set up Python - run: uv python install 3.11 - - - name: Read CI Config - id: config - uses: i2mint/wads/actions/read-ci-config@master - with: - pyproject-path: . - - # Second job: Validation using the config - validation: - name: Validation - if: "!contains(github.event.head_commit.message, '[skip ci]')" - needs: setup - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ${{ fromJson(needs.setup.outputs.python-versions) }} - - steps: - - uses: actions/checkout@v6 - - - name: Set up uv - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - - - name: Set up Python ${{ matrix.python-version }} - uses: i2mint/wads/actions/setup-python-uv@master - with: - python-version: ${{ matrix.python-version }} - - - name: Install System Dependencies - uses: i2mint/wads/actions/install-system-deps@master - with: - pyproject-path: . - - - name: Install Dependencies - uses: i2mint/wads/actions/install-deps-uv@master - - - name: Format Source Code - if: needs.setup.outputs.ruff-enabled != 'false' - run: uvx ruff format . - - - name: Format Source Code (black) - if: needs.setup.outputs.black-enabled == 'true' - run: uvx black . - - - name: Lint Validation - if: needs.setup.outputs.ruff-enabled != 'false' - run: uvx ruff check --output-format=github ${{ needs.setup.outputs.project-name }} - - - name: Type Check (mypy) - if: needs.setup.outputs.mypy-enabled == 'true' - run: uvx mypy ${{ needs.setup.outputs.project-name }} - - - name: Run Tests - if: needs.setup.outputs.tests-enabled != 'false' - uses: i2mint/wads/actions/run-tests-uv@master - with: - root-dir: ${{ needs.setup.outputs.project-name }} - pytest-args: ${{ needs.setup.outputs.pytest-args }} - exclude-paths: ${{ needs.setup.outputs.exclude-paths }} - coverage: ${{ needs.setup.outputs.coverage-enabled }} - - - name: Track Code Metrics - if: needs.setup.outputs.metrics-enabled == 'true' - uses: i2mint/umpyre/actions/track-metrics@master - continue-on-error: true - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - config-path: ${{ needs.setup.outputs.metrics-config-path }} - storage-branch: ${{ needs.setup.outputs.metrics-storage-branch }} - python-version: ${{ needs.setup.outputs.metrics-python-version }} - force-run: ${{ needs.setup.outputs.metrics-force-run }} - - # Optional Windows testing (if enabled in config) - windows-validation: - name: Windows Tests - if: "!contains(github.event.head_commit.message, '[skip ci]') && needs.setup.outputs.test-on-windows == 'true' && needs.setup.outputs.tests-enabled != 'false'" - needs: setup - runs-on: windows-latest - continue-on-error: true - env: - # PEP 540 UTF-8 mode: avoid cp1252 UnicodeDecode/EncodeError when test - # code reads source files or scripts print non-ASCII characters. - PYTHONUTF8: "1" - PYTHONIOENCODING: "utf-8" - - steps: - - uses: actions/checkout@v6 - - - name: Set up uv - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - - - name: Set up Python - uses: i2mint/wads/actions/setup-python-uv@master - with: - python-version: ${{ fromJson(needs.setup.outputs.python-versions)[0] }} - - - name: Install System Dependencies - uses: i2mint/wads/actions/install-system-deps@master - with: - pyproject-path: . - - - name: Install Dependencies - uses: i2mint/wads/actions/install-deps-uv@master - - - name: Run Tests - uses: i2mint/wads/actions/run-tests-uv@master - with: - root-dir: ${{ needs.setup.outputs.project-name }} - pytest-args: ${{ needs.setup.outputs.pytest-args }} - exclude-paths: ${{ needs.setup.outputs.exclude-paths }} - - # Publishing job - # - # Gated by [tool.wads.ci.publish] in pyproject.toml (read via the setup job): - # - skip-ci-marker : when publishing is enabled, a commit message containing - # this substring skips the publish job (default "[skip ci]"). - # - publish-enabled : whether publishing runs at all (default true). - # - publish-marker : when publishing is disabled, a commit message containing - # this substring forces the publish job (default "[publish]"). - # The publish-enabled check uses `== 'true'` (fail-closed): if an older wads - # without these outputs is installed by read-ci-config, publishing is skipped - # rather than run unintentionally. - publish: - name: Publish - permissions: - contents: write - if: "!contains(github.event.head_commit.message, needs.setup.outputs.skip-ci-marker) && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && (needs.setup.outputs.publish-enabled == 'true' || contains(github.event.head_commit.message, needs.setup.outputs.publish-marker))" - needs: [setup, validation] - runs-on: ubuntu-latest - - steps: - # `actions/checkout@v6` defaults to persist-credentials: true, configuring - # HTTPS auth in .git/config using `secrets.GITHUB_TOKEN`. Together with the - # job-level `permissions: contents: write`, this is exactly what the - # post-publish push-back needs — no per-repo SSH deploy key required. Do - # NOT re-add a "Force SSH for git remote" step: rewriting origin to an - # SSH URL overrides these credentials and reintroduces the push-back - # failure on every repo lacking an SSH_PRIVATE_KEY deploy key. - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up uv - uses: astral-sh/setup-uv@v7 - - - name: Set up Python - uses: i2mint/wads/actions/setup-python-uv@master - with: - python-version: ${{ fromJson(needs.setup.outputs.python-versions)[0] }} - create-venv: "false" - - - name: Format Source Code - if: needs.setup.outputs.ruff-enabled != 'false' - run: uvx ruff format . - - - name: Format Source Code (black) - if: needs.setup.outputs.black-enabled == 'true' - run: uvx black . - - - name: Update Version Number - id: version - uses: i2mint/isee/actions/bump-version-number@master - - - name: Build Distribution - uses: i2mint/wads/actions/build-dist-uv@master - with: - sdist: ${{ needs.setup.outputs.build-sdist }} - wheel: ${{ needs.setup.outputs.build-wheel }} - - - name: Publish to PyPI - uses: i2mint/wads/actions/pypi-publish-uv@master - with: - pypi-token: ${{ secrets.PYPI_PASSWORD }} - - - name: Commit Changes - uses: i2mint/wads/actions/git-commit@master - with: - commit-message: "**CI** Formatted code + Updated version to ${{ env.VERSION }} [skip ci]" - push: true - - - name: Tag Repository - uses: i2mint/wads/actions/git-tag@master - with: - tag: ${{ env.VERSION }} - message: "Release version ${{ env.VERSION }}" - push: true - - # Optional GitHub Pages (skipped when [tool.wads.ci.docs].enabled = false) - # Depends on validation (not publish) so docs still publish when the publish - # job is disabled via [tool.wads.ci.publish].enabled = false. - github-pages: - name: Publish GitHub Pages + ci: + uses: i2mint/wads/.github/workflows/uv-ci.yml@master permissions: contents: write pages: write - id-token: write - if: "!contains(github.event.head_commit.message, '[skip ci]') && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && needs.setup.outputs.docs-enabled != 'false'" - needs: [setup, validation] - runs-on: ubuntu-latest - - steps: - - uses: i2mint/epythet/actions/publish-github-pages@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - ignore: "tests/,scrap/,examples/" + # 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 }}