From 9b8e1c427fcfd2b79af6ab1cdad83d3c81202fcc Mon Sep 17 00:00:00 2001 From: Oliver Bristow Date: Sun, 18 Oct 2026 16:00:25 +0100 Subject: [PATCH] Modernize packaging and gate publishing on installed artifacts --- .coveragerc | 4 + .github/dependabot.yml | 10 +++ .github/workflows/ci.yml | 159 ++++++++++++++++++++++++++++------ .github/workflows/publish.yml | 26 +++--- README.rst | 3 +- pyproject.toml | 18 ++-- 6 files changed, 169 insertions(+), 51 deletions(-) create mode 100644 .coveragerc create mode 100644 .github/dependabot.yml diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..4dc0764 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,4 @@ +[paths] +source = + . + */site-packages diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6c5049e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 317f281..0ec4cfa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,12 +6,46 @@ on: - main - master pull_request: + workflow_call: + +permissions: + contents: read + +defaults: + run: + shell: bash jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out repository and version tags + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.15" + + - name: Build and check distributions + run: | + python -m pip install --upgrade pip build twine + python -m build --sdist --wheel + python -m twine check --strict dist/* + + - name: Upload distributions + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: distributions + path: dist/* + if-no-files-found: error + tests: + needs: build runs-on: ${{ matrix.os }} - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} strategy: fail-fast: false matrix: @@ -19,53 +53,128 @@ jobs: - ubuntu-latest - windows-latest python-version: - - "3.8" - "3.9" - "3.10" - "3.11" - "3.12" - "3.13" - "3.14" - - "pypy-3.9" + - "3.15" + - "pypy3.11" exclude: - os: windows-latest - python-version: "pypy-3.9" + python-version: "pypy3.11" steps: - - name: Check out repository - uses: actions/checkout@v4 + - name: Check out tests + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: ${{ matrix.python-version }} - - name: Install system dependencies - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y zip p7zip-full + - name: Download distributions + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: distributions + path: dist - - name: Install Python dependencies + - name: Install wheel and test dependencies run: | python -m pip install --upgrade pip - python -m pip install . pytest pytest-cov + python -m pip install --no-cache-dir dist/*.whl pytest pytest-cov + mkdir -p "$RUNNER_TEMP/installed-tests" + cp test_jsonstore.py "$RUNNER_TEMP/installed-tests/" + + - name: Test installed wheel outside the checkout + working-directory: ${{ runner.temp }}/installed-tests + run: | + python -I -c 'import jsonstore; print(jsonstore.__file__)' + python -I -m pytest -vv --cov=jsonstore --cov-report= --junitxml=test-results.xml + + - name: Map installed coverage to repository paths + run: | + python -m coverage combine "$RUNNER_TEMP/installed-tests/.coverage" + python -m coverage xml -o "$RUNNER_TEMP/installed-tests/coverage.xml" + + - name: Build and install wheel from source distribution + run: | + python -m pip uninstall --yes python-jsonstore + python -m pip install --no-cache-dir dist/*.tar.gz + + - name: Test installed source distribution outside the checkout + working-directory: ${{ runner.temp }}/installed-tests + run: | + python -I -c 'import jsonstore; print(jsonstore.__file__)' + python -I -m pytest -vv + + - name: Upload test reports + if: always() && matrix.python-version == '3.15' + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: test-reports-${{ matrix.os }} + path: | + ${{ runner.temp }}/installed-tests/coverage.xml + ${{ runner.temp }}/installed-tests/test-results.xml + if-no-files-found: warn + + reports: + if: ${{ !cancelled() && github.event_name != 'release' }} + needs: tests + runs-on: ubuntu-latest + steps: + - name: Check whether Codecov is configured + id: codecov + env: + ENABLED: ${{ secrets.CODECOV_TOKEN != '' }} + run: echo "enabled=$ENABLED" >> "$GITHUB_OUTPUT" + + - name: Check out repository for report paths + if: steps.codecov.outputs.enabled == 'true' + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Download test reports + id: reports + continue-on-error: ${{ needs.tests.result != 'success' }} + if: steps.codecov.outputs.enabled == 'true' + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + pattern: test-reports-* + path: reports + + - name: Find available test reports + id: files + if: steps.codecov.outputs.enabled == 'true' + run: | + python3 - <<'PYTHON' + import glob + import os - - name: Run tests - run: pytest -vv --cov=jsonstore --cov-report=xml --junitxml=test-results.xml + with open(os.environ["GITHUB_OUTPUT"], "a") as output: + for name, pattern in (("coverage", "reports/*/coverage.xml"), + ("tests", "reports/*/test-results.xml")): + output.write(f"{name}={','.join(sorted(glob.glob(pattern)))}\n") + PYTHON - name: Upload coverage to Codecov - if: always() && env.CODECOV_TOKEN != '' - uses: codecov/codecov-action@v5 + if: steps.files.outputs.coverage != '' + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: - token: ${{ env.CODECOV_TOKEN }} - files: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ steps.files.outputs.coverage }} + disable_search: true fail_ci_if_error: true - name: Upload test results to Codecov - if: always() && env.CODECOV_TOKEN != '' - uses: codecov/codecov-action@v5 + if: steps.files.outputs.tests != '' + uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: - token: ${{ env.CODECOV_TOKEN }} - files: ./test-results.xml + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ steps.files.outputs.tests }} report_type: test_results + disable_search: true fail_ci_if_error: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e789857..bdd88e2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,8 +4,15 @@ on: release: types: [published] +permissions: + contents: read + jobs: - build-and-publish: + validate: + uses: ./.github/workflows/ci.yml + + publish: + needs: validate runs-on: ubuntu-latest environment: name: pypi @@ -13,15 +20,12 @@ jobs: permissions: id-token: write steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Download validated distributions + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 with: - python-version: '3.x' - - name: Build package - run: | - python -m pip install --upgrade pip build - python -m build - # the publishing uses PyPI's trusted publishing, so configured on pypi instead of using secrets + name: distributions + path: dist + + # Keep publishing in this workflow for the existing PyPI Trusted Publisher. - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 diff --git a/README.rst b/README.rst index c35366d..19cf0dd 100644 --- a/README.rst +++ b/README.rst @@ -12,8 +12,7 @@ flexible than the `configparser `__ module which is included with Python. -This works is tested and working on Python 2.7 and Python 3.6+. It will -not work on 2.6 or lower. The tests are also executed on PyPy. +Requires Python 3.9 or newer. Tests run on CPython 3.9–3.15 and PyPy 3.11. Examples -------- diff --git a/pyproject.toml b/pyproject.toml index f00b633..0d0c05a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,14 @@ [build-system] -requires = ["setuptools>=75", "setuptools_scm>=7", "wheel"] +requires = ["setuptools>=77.0.3", "setuptools_scm>=7"] build-backend = "setuptools.build_meta" [project] name = "python-jsonstore" description = "Map JSON files onto Python attributes for simple configuration storage" readme = "README.rst" -requires-python = ">=2.7" -license = {text = "MIT"} +requires-python = ">=3.9" +license = "MIT" +license-files = ["LICENCE.md"] authors = [ {name = "Oliver Bristow", email = "github+pypi@oliverbristow.co.uk"}, ] @@ -15,23 +16,15 @@ keywords = ["json", "key", "value", "store"] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Database", @@ -45,6 +38,5 @@ Homepage = "https://github.com/Code0x58/python-jsonstore/" [tool.setuptools] py-modules = ["jsonstore"] -license-files = ["LICENCE.md"] [tool.setuptools_scm]