diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 6ecbe56..22e5db8 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,47 +1,74 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries +# Build and publish the disdat package. +# +# - On a GitHub *release*: build (version derived from the release tag by +# setuptools_scm) and upload to PyPI. +# - On *manual dispatch*: build a dev version and upload to TestPyPI as a +# dry run (no real PyPI publish). Requires a TEST_PYPI_API_TOKEN secret. +# +# Builds use `uv build` (sdist + wheel); uploads use twine via `uvx`. name: Upload Python Package on: workflow_dispatch: - inputs: - name: - description: 'Manual publish event' - required: true - default: 'default default string' - release: types: [created] jobs: - deploy: - + publish-pypi: + # Real publish only happens for an actual release (clean tag version). + if: github.event_name == 'release' runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # setuptools_scm derives the version from tags + + - name: Install uv + uses: astral-sh/setup-uv@v5 with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine - - name: Build and publish + python-version: '3.13' + + - name: Build sdist and wheel + run: uv build + + - name: Check distributions + run: uvx twine check dist/* + + - name: Publish to PyPI env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - echo "Cleaning up repo for sdist build ..." - rm -rf disdat/infrastructure/dockerizer/context.template/disdat-*.tar.gz - rm -rf dist/disdat-*.tar.gz - echo "Creating sdist without a copy of itself for dockerizer ..." - python setup.py sdist - echo "Copying sdist to dockerizer template..." - cp dist/disdat-*.tar.gz disdat/infrastructure/dockerizer/context.template/. - echo "Creating sdist with a copy of itself ..." - python setup.py sdist - echo "Uploading to PyPi ... (not yet)" - twine upload dist/disdat-*.tar.gz + run: uvx twine upload dist/* + + testpypi-dry-run: + # Manual runs validate the full build+upload path against TestPyPI without + # touching real PyPI. Needs a TEST_PYPI_API_TOKEN repository secret. + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + python-version: '3.13' + + - name: Build sdist and wheel + # No tag on HEAD -> a dev version. Strip setuptools_scm's local segment + # (+gHASH), which PyPI/TestPyPI reject, so the dev build is uploadable. + env: + SETUPTOOLS_SCM_OVERRIDES_FOR_DISDAT: '{ local_scheme = "no-local-version" }' + run: uv build + + - name: Check distributions + run: uvx twine check dist/* + + - name: Upload to TestPyPI (dry run) + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} + TWINE_REPOSITORY_URL: https://test.pypi.org/legacy/ + run: uvx twine upload --skip-existing dist/* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ff42eeb --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,41 @@ +name: Tests + +on: + push: + branches: [master] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] + + env: + # disdat uses forked multiprocessing; pin the start method (Python 3.14 + # defaults to forkserver on Linux). + MP_CONTEXT_TYPE: fork + # moto mocks AWS; provide dummy credentials so boto3 never reaches out. + AWS_DEFAULT_REGION: us-east-1 + AWS_ACCESS_KEY_ID: testing + AWS_SECRET_ACCESS_KEY: testing + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # setuptools_scm needs full history/tags + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install project and dev dependencies + run: | + uv venv --clear --python ${{ matrix.python-version }} + uv pip install -e ".[dev]" + + - name: Run tests + run: .venv/bin/python -m pytest tests --disable-warnings -q diff --git a/design_docs/plans/PLAN_LOG.md b/design_docs/plans/PLAN_LOG.md index 669743d..761a9ea 100644 --- a/design_docs/plans/PLAN_LOG.md +++ b/design_docs/plans/PLAN_LOG.md @@ -12,4 +12,4 @@ discipline. Status advances forward only: `created` → `implemented` → | 3 | py314 / Phase 2: modernize dependency pins | `design_docs/plans/py-3.10-3.14-migration.plan.md` | 2026-06-11 | 2026-06-23 | `909253e` | committed | | 4 | py314 / Phase 3: code changes for new deps | `design_docs/plans/py-3.10-3.14-migration.plan.md` | 2026-06-11 | 2026-06-23 | `22a1100` | committed | | 5 | py314 / Phase 4: test & verify across versions | `design_docs/plans/py-3.10-3.14-migration.plan.md` | 2026-06-11 | 2026-06-23 | `22a1100` | committed | -| 6 | py314 / Phase 5: CI matrix + publish update | `design_docs/plans/py-3.10-3.14-migration.plan.md` | 2026-06-11 | - | - | created | +| 6 | py314 / Phase 5: CI matrix + publish update | `design_docs/plans/py-3.10-3.14-migration.plan.md` | 2026-06-11 | 2026-06-23 | `ba826ba` | committed | diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..8a87346 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,35 @@ +# +# Copyright 2024 Disdat +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +import os + +import pytest + +from disdat.common import SYSTEM_CONFIG_DIR, DisdatConfig + + +@pytest.fixture(scope="session", autouse=True) +def ensure_disdat_initialized(): + """Ensure a Disdat configuration exists before the suite runs. + + The tests assume an initialized Disdat (normally created once via + ``dsdt init``). A fresh checkout or CI runner has none, so create it when + absent. Guarded on existence because ``DisdatConfig.init()`` exits the + process if the config directory already exists, so an existing developer + configuration is left untouched. + """ + if not os.path.exists(os.path.expanduser(SYSTEM_CONFIG_DIR)): + DisdatConfig.init() + yield