From b2a8da6efaae5270529f777c46a63bb4a30ea47a Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Wed, 18 Sep 2024 18:09:47 -0700 Subject: [PATCH] Combines action workflows to lint, test, build and create releases to both Github and PyPI --- .editorconfig | 32 +++ .github/workflows/ci.yml | 256 +++++++++++++++++++++ .github/workflows/publish-develop-docs.yml | 27 --- .github/workflows/publish-py.yml | 32 --- .github/workflows/publish-release-docs.yml | 27 --- .github/workflows/test-docs.yml | 29 --- .github/workflows/test-src.yml | 93 -------- CHANGELOG.md | 77 +++---- 8 files changed, 316 insertions(+), 257 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/publish-develop-docs.yml delete mode 100644 .github/workflows/publish-py.yml delete mode 100644 .github/workflows/publish-release-docs.yml delete mode 100644 .github/workflows/test-docs.yml delete mode 100644 .github/workflows/test-src.yml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5b65dd3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,32 @@ +# EditorConfig: http://EditorConfig.org + +root = true + +[*] +indent_style = tab +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf +charset = utf-8 +max_line_length = 99 + +[{*.html,*.css,*.js}] +max_line_length = off + +[*.py] +indent_size = 4 +indent_style = space + +[*.{yml,yaml}] +indent_style = space +indent_size = 4 + +[*.{rst, md}] +indent_style = space + +# Tests don't get a line width restriction. It's still a good idea to follow +# the 99 character rule, but in the interests of clarity, tests often need to +# violate it. +[**/test_*.py] +max_line_length = off diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ffc7340 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,256 @@ +name: Continuous Integration + +on: + push: + pull_request: + branches: + - main + +concurrency: + group: ci-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + code-lint: + name: Code Linting + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.x + uses: actions/setup-python@v5 + with: + python-version: '3.x' + cache: 'pip' + - name: Install Hatch + run: | + pip3 --quiet install --upgrade hatch uv + hatch --version + uv --version + - name: Lint project + # TODO: This cannot run as the project doesn't pass yet + if: false + run: | + hatch fmt --check + - name: Check files with pre-commit + # TODO: This cannot run as the project doesn't pass yet + if: false + uses: pre-commit/action@v3.0.1 + + docs-lint: + name: Documentation Linting + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + cache: pip + - name: Install dependecies + run: | + pip install --upgrade hatch uv + - name: Check documentation links + run: | + hatch run docs:linkcheck + - name: Check docs build + run: | + hatch run docs:build + + tests: + name: Python ${{ matrix.python-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + needs: + - code-lint + + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + cache: pip + + - name: Install dependencies + run: | + python -m pip install --upgrade pip hatch uv + + - name: Show environment + run: | + hatch test --show --python ${{ matrix.python-version }} + + - name: Run tests + run: | + hatch test --cover --python ${{ matrix.python-version }} + mv .coverage ".coverage.py${{ matrix.python-version }}" + + - name: Upload coverage data + if: matrix.os != 'windows-latest' + uses: actions/upload-artifact@v4 + with: + name: "coverage-data-py${{ matrix.python-version }}" + path: ".coverage.py${{ matrix.python-version }}" + if-no-files-found: error + include-hidden-files: true + + build: + name: Build Distributions + runs-on: ubuntu-latest + permissions: + contents: read + needs: + - code-lint + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.x + uses: actions/setup-python@v5 + with: + python-version: '3.x' + cache: 'pip' + - name: Install Hatch + run: | + pip3 --quiet install --upgrade hatch uv + hatch --version + uv --version + - name: Build release files + run: | + hatch build --clean + - uses: actions/upload-artifact@v4 + with: + name: artifacts + path: dist/* + if-no-files-found: error + retention-days: 7 + + coverage: + name: Coverage + runs-on: ubuntu-latest + needs: + - tests + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: pip + + - name: Install dependencies + run: python -m pip install --upgrade coverage[toml] + + - name: Download data + uses: actions/download-artifact@v4 + with: + merge-multiple: true + + - name: Combine coverage and fail if it's <95% + run: | + python -m coverage combine + python -m coverage html --skip-covered --skip-empty + python -m coverage report --fail-under=95 + + - name: Upload HTML report + if: ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: html-report + path: htmlcov + + docs-deploy: + name: Deploy Documentation + runs-on: ubuntu-latest + needs: + - docs-lint + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + cache: pip + - name: Install dependecies + run: | + pip install --upgrade hatch uv + - name: Configure Git + run: | + git config user.name github-actions + git config user.email github-actions@github.com + - name: Publish Develop Docs + if: github.ref_name == 'main' + run: | + hatch run docs:deploy_develop + - name: Publish Develop Docs + if: startsWith(github.ref, 'refs/tags/') + run: | + hatch run docs:deploy_latest ${{ github.ref_name }} + + create-release: + name: Create Release + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + permissions: + contents: write + needs: + - build + - tests + - docs-deploy + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: actions/download-artifact@v4 + with: + name: artifacts + path: dist + - name: Get latest release info + id: query-release-info + uses: release-flow/keep-a-changelog-action@v3 + with: + command: query + version: ${{ github.ref_name }} + - name: Display release info + run: | + echo "Version: ${{ steps.query-release-info.outputs.version }}" + echo "Date: ${{ steps.query-release-info.outputs.release-date }}" + echo "${{ steps.query-release-info.outputs.release-notes }}" + - uses: ncipollo/release-action@v1 + with: + artifacts: "dist/*.tar.gz,dist/*.whl" + body: ${{ steps.query-release-info.outputs.release-notes }} + + pypi-publish: + name: Publish to PyPi + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + needs: + - build + - tests + - docs-deploy + steps: + - uses: actions/download-artifact@v4 + with: + name: artifacts + path: dist + - name: Publish build to PyPI + uses: pypa/gh-action-pypi-publish@v1.10.1 diff --git a/.github/workflows/publish-develop-docs.yml b/.github/workflows/publish-develop-docs.yml deleted file mode 100644 index 1b3afba..0000000 --- a/.github/workflows/publish-develop-docs.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Publish Develop Docs - -on: - push: - branches: - - main -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - cache: pip - - name: Install dependecies - run: | - pip install --upgrade hatch uv - - name: Publish Develop Docs - run: | - git config user.name github-actions - git config user.email github-actions@github.com - hatch run docs:deploy_develop - concurrency: - group: publish-docs diff --git a/.github/workflows/publish-py.yml b/.github/workflows/publish-py.yml deleted file mode 100644 index afa591f..0000000 --- a/.github/workflows/publish-py.yml +++ /dev/null @@ -1,32 +0,0 @@ -# This workflows 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 - -name: Publish Python - -on: - release: - types: [published] - -jobs: - release-package: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - cache: pip - - name: Install dependecies - run: | - pip install --upgrade hatch uv - - name: Build - run: | - hatch build --clean - # TODO: Update trusted publishing with pypa/gh-action-pypi-publish - - name: Publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - twine upload dist/* diff --git a/.github/workflows/publish-release-docs.yml b/.github/workflows/publish-release-docs.yml deleted file mode 100644 index d4bd103..0000000 --- a/.github/workflows/publish-release-docs.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Publish Release Docs - -on: - release: - types: [published] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - cache: pip - - name: Install dependecies - run: | - pip install --upgrade hatch uv - - name: Publish ${{ github.event.release.name }} Docs - run: | - git config user.name github-actions - git config user.email github-actions@github.com - hatch run docs:deploy_latest ${{ github.event.release.name }} - concurrency: - group: publish-docs diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml deleted file mode 100644 index 8c4f9cd..0000000 --- a/.github/workflows/test-docs.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Test Docs - -on: - pull_request: - branches: - - main - schedule: - - cron: "0 0 * * *" - -jobs: - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: actions/setup-python@v5 - with: - python-version: 3.x - cache: pip - - name: Install dependecies - run: | - pip install --upgrade hatch uv - - name: Check documentation links - run: | - hatch run docs:linkcheck - - name: Check docs build - run: | - hatch run docs:build diff --git a/.github/workflows/test-src.yml b/.github/workflows/test-src.yml deleted file mode 100644 index 4769549..0000000 --- a/.github/workflows/test-src.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Test Code - -on: - push: - branches: - - main - pull_request: - branches: - - main - -concurrency: - group: ${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - tests: - name: Python ${{ matrix.python-version }} on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: - - ubuntu-latest - - windows-latest - python-version: - - "3.9" - - "3.10" - - "3.11" - - "3.12" - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - cache: pip - - - name: Install dependencies - run: | - python -m pip install --upgrade pip hatch uv - - - name: Show environment - run: | - hatch test --show --python ${{ matrix.python-version }} - - - name: Run tests - run: | - hatch test --cover --python ${{ matrix.python-version }} - mv .coverage ".coverage.py${{ matrix.python-version }}" - - - name: Upload coverage data - if: matrix.os != 'windows-latest' - uses: actions/upload-artifact@v4 - with: - name: "coverage-data-py${{ matrix.python-version }}" - path: ".coverage.py${{ matrix.python-version }}" - if-no-files-found: error - include-hidden-files: true - - coverage: - name: Coverage - runs-on: ubuntu-latest - needs: tests - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: pip - - - name: Install dependencies - run: python -m pip install --upgrade coverage[toml] - - - name: Download data - uses: actions/download-artifact@v4 - with: - merge-multiple: true - - - name: Combine coverage and fail if it's <95% - run: | - python -m coverage combine - python -m coverage html --skip-covered --skip-empty - python -m coverage report --fail-under=95 - - - name: Upload HTML report - if: ${{ failure() }} - uses: actions/upload-artifact@v4 - with: - name: html-report - path: htmlcov diff --git a/CHANGELOG.md b/CHANGELOG.md index f298783..812def5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,87 +2,66 @@ All notable changes to this project will be documented in this file. - +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - - - - - - - -## [Unreleased](https://github.com/Archmonger/ServeStatic/compare/2.0.1...HEAD) +### Changed -- Nothing (yet)! +- Neat stuff -## [2.0.1](https://github.com/Archmonger/ServeStatic/compare/2.0.0...2.0.1) - 2024-09-13 +## [2.0.1] - 2024-09-13 ### Fixed -- Fix crash when running `manage.py collectstatic` when Django's `settings.py:STATIC_ROOT` is a `Path` object. +- Fix crash when running `manage.py collectstatic` when Django's `settings.py:STATIC_ROOT` is a `Path` object. -## [2.0.0](https://github.com/Archmonger/ServeStatic/compare/1.2.0...2.0.0) - 2024-09-12 +## [2.0.0] - 2024-09-12 ### Added -- Django `settings.py:SERVESTATIC_USE_MANIFEST` will allow ServeStatic to use the Django manifest rather than scanning the filesystem. - - When also using ServeStatic's `CompressedManifestStaticFilesStorage` backend, ServeStatic will no longer need to call `os.stat`. +- Django `settings.py:SERVESTATIC_USE_MANIFEST` will allow ServeStatic to use the Django manifest rather than scanning the filesystem. + - When also using ServeStatic's `CompressedManifestStaticFilesStorage` backend, ServeStatic will no longer need to call `os.stat`. ### Changed -- Minimum python version is now 3.9. -- Django `setings.py:SERVESTATIC_USE_FINDERS` will now discover files strictly using the [finders API](https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/#finders-module). Previously, ServeStatic would also scan `settings.py:STATIC_ROOT` for files not found by the finders API. -- Async file reading is now done via threads rather than [`aiofiles`](https://github.com/Tinche/aiofiles) due [recent performance tests](https://github.com/mosquito/aiofile/issues/88#issuecomment-2314380621). -- `BaseServeStatic` has been renamed to `ServeStaticBase`. -- `AsgiFileServer` has been renamed to `FileServerASGI`. -- Lots of internal refactoring to improve performance, code quality, and maintainability. +- Minimum Python version is now 3.9. +- Django `settings.py:SERVESTATIC_USE_FINDERS` will now discover files strictly using the [finders API](https://docs.djangoproject.com/en/stable/ref/contrib/staticfiles/#finders-module). Previously, ServeStatic would also scan `settings.py:STATIC_ROOT` for files not found by the finders API. +- Async file reading is now done via threads rather than [`aiofiles`](https://github.com/Tinche/aiofiles) due to [recent performance tests](https://github.com/mosquito/aiofile/issues/88#issuecomment-2314380621). +- `BaseServeStatic` has been renamed to `ServeStaticBase`. +- `AsgiFileServer` has been renamed to `FileServerASGI`. +- Lots of internal refactoring to improve performance, code quality, and maintainability. -## [1.2.0](https://github.com/Archmonger/ServeStatic/compare/1.1.0...1.2.0) - 2024-08-30 +## [1.2.0] - 2024-08-30 ### Added -- Verbose Django `404` error page when `settings.py:DEBUG` is `True` +- Verbose Django `404` error page when `settings.py:DEBUG` is `True` ### Fixed -- Fix Django compatibility with third-party sync middleware. - - ServeStatic Django middleware now only runs in async mode to avoid clashing with Django's internal usage of `asgiref.AsyncToSync`. -- Respect Django `settings.py:FORCE_SCRIPT_NAME` configuration value. +- Fix Django compatibility with third-party sync middleware. + - ServeStatic Django middleware now only runs in async mode to avoid clashing with Django's internal usage of `asgiref.AsyncToSync`. +- Respect Django `settings.py:FORCE_SCRIPT_NAME` configuration value. -## [1.1.0](https://github.com/Archmonger/ServeStatic/compare/1.0.0...1.1.0) - 2024-08-27 +## [1.1.0] - 2024-08-27 ### Added -- Files are now compressed within a thread pool to increase performance. +- Files are now compressed within a thread pool to increase performance. ### Fixed -- Fix Django `StreamingHttpResponse must consume synchronous iterators` warning. -- Fix Django bug where file paths could fail to be followed on Windows. +- Fix Django `StreamingHttpResponse must consume synchronous iterators` warning. +- Fix Django bug where file paths could fail to be followed on Windows. -## [1.0.0](https://github.com/Archmonger/ServeStatic/releases/tag/1.0.0) - 2024-05-08 +## [1.0.0] - 2024-05-08 ### Changed -- Forked from [`whitenoise`](https://github.com/evansd/whitenoise) to add ASGI support. +- Forked from [`whitenoise`](https://github.com/evansd/whitenoise) to add ASGI support.