Compare commits

..

1 Commits

Author SHA1 Message Date
Adam Hopkins
e0839fe130 Cleaner process management 2023-08-29 19:17:50 +03:00
26 changed files with 657 additions and 305 deletions

View File

@@ -12,23 +12,29 @@ on:
- main
- current-release
- "*LTS"
jobs:
coverage:
name: Check coverage
runs-on: ubuntu-latest
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: [3.9]
os: [ubuntu-latest]
fail-fast: false
steps:
- name: Run coverage
uses: sanic-org/simple-tox-action@v1
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
python-version: "3.11"
tox-env: coverage
ignore-errors: true
- name: Run Codecov
uses: codecov/codecov-action@v3
python-version: ${{ matrix.python-version }}
- name: Install dependencies 🔨
run: |
python -m pip install --upgrade pip
pip install tox
- name: Run coverage
run: tox -e coverage
continue-on-error: true
- uses: codecov/codecov-action@v2
with:
files: ./coverage.xml
fail_ci_if_error: false

39
.github/workflows/on-demand.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: On Demand Task
on:
workflow_dispatch:
inputs:
python-version:
description: 'Version of Python to use for running Test'
required: false
default: "3.8"
tox-env:
description: 'Test Environment to Run'
required: true
default: ''
os:
description: 'Operating System to Run Test on'
required: false
default: ubuntu-latest
jobs:
onDemand:
name: tox-${{ matrix.config.tox-env }}-on-${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["${{ github.event.inputs.os}}"]
config:
- { tox-env: "${{ github.event.inputs.tox-env }}", py-version: "${{ github.event.inputs.python-version }}"}
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Run tests
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.py-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }}"
experimental-ignore-error: "yes"

37
.github/workflows/pr-bandit.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Security Analysis
on:
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
bandit:
if: github.event.pull_request.draft == false
name: type-check-${{ matrix.config.python-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
config:
- { python-version: 3.8, tox-env: security}
- { python-version: 3.9, tox-env: security}
- { python-version: "3.10", tox-env: security}
- { python-version: "3.11", tox-env: security}
steps:
- name: Checkout the repository
uses: actions/checkout@v2
id: checkout-branch
- name: Run Linter Checks
id: linter-check
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }}"

33
.github/workflows/pr-docs.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Document Linter
on:
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
docsLinter:
if: github.event.pull_request.draft == false
name: Lint Documentation
runs-on: ubuntu-latest
strategy:
matrix:
config:
- {python-version: "3.10", tox-env: "docs"}
fail-fast: false
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Run Document Linter
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }}"

34
.github/workflows/pr-linter.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Linter Checks
on:
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
linter:
if: github.event.pull_request.draft == false
name: lint
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
config:
- { python-version: "3.10", tox-env: lint}
steps:
- name: Checkout the repository
uses: actions/checkout@v2
id: checkout-branch
- name: Run Linter Checks
id: linter-check
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }}"

41
.github/workflows/pr-python-pypy.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Python PyPy Tests
on:
workflow_dispatch:
inputs:
tox-env:
description: "Tox Env to run on the PyPy Infra"
required: false
default: "pypy310"
pypy-version:
description: "Version of PyPy to use"
required: false
default: "pypy-3.10"
jobs:
testPyPy:
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
config:
- {
python-version: "${{ github.event.inputs.pypy-version }}",
tox-env: "${{ github.event.inputs.tox-env }}",
}
steps:
- name: Checkout the Repository
uses: actions/checkout@v2
id: checkout-branch
- name: Run Unit Tests
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }}"
experimental-ignore-error: "true"
command-timeout: "600000"

48
.github/workflows/pr-python310.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Python 3.10 Tests
on:
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
testPy310:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
config:
- {
python-version: "3.10",
tox-env: py310,
ignore-error-flake: "false",
command-timeout: "0",
}
- {
python-version: "3.10",
tox-env: py310-no-ext,
ignore-error-flake: "true",
command-timeout: "600000",
}
steps:
- name: Checkout the Repository
uses: actions/checkout@v2
id: checkout-branch
- name: Run Unit Tests
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }},-vv=''"
experimental-ignore-error: "${{ matrix.config.ignore-error-flake }}"
command-timeout: "${{ matrix.config.command-timeout }}"
test-failure-retry: "3"

48
.github/workflows/pr-python311.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Python 3.11 Tests
on:
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
testPy311:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
config:
- {
python-version: "3.11",
tox-env: py311,
ignore-error-flake: "false",
command-timeout: "0",
}
- {
python-version: "3.11",
tox-env: py311-no-ext,
ignore-error-flake: "true",
command-timeout: "600000",
}
steps:
- name: Checkout the Repository
uses: actions/checkout@v2
id: checkout-branch
- name: Run Unit Tests
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }},-vv=''"
experimental-ignore-error: "${{ matrix.config.ignore-error-flake }}"
command-timeout: "${{ matrix.config.command-timeout }}"
test-failure-retry: "3"

36
.github/workflows/pr-python38.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Python 3.8 Tests
on:
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
testPy38:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
# os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
config:
- { python-version: 3.8, tox-env: py38 }
- { python-version: 3.8, tox-env: py38-no-ext }
steps:
- name: Checkout the Repository
uses: actions/checkout@v2
id: checkout-branch
- name: Run Unit Tests
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }}"
test-failure-retry: "3"

48
.github/workflows/pr-python39.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Python 3.9 Tests
on:
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
testPy39:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
# os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
config:
- {
python-version: 3.9,
tox-env: py39,
ignore-error-flake: "false",
command-timeout: "0",
}
- {
python-version: 3.9,
tox-env: py39-no-ext,
ignore-error-flake: "true",
command-timeout: "600000",
}
steps:
- name: Checkout the Repository
uses: actions/checkout@v2
id: checkout-branch
- name: Run Unit Tests
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }},-vv=''"
experimental-ignore-error: "${{ matrix.config.ignore-error-flake }}"
command-timeout: "${{ matrix.config.command-timeout }}"
test-failure-retry: "3"

37
.github/workflows/pr-type-check.yml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Typing Checks
on:
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
typeChecking:
if: github.event.pull_request.draft == false
name: type-check-${{ matrix.config.python-version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
config:
- { python-version: 3.8, tox-env: type-checking}
- { python-version: 3.9, tox-env: type-checking}
- { python-version: "3.10", tox-env: type-checking}
- { python-version: "3.11", tox-env: type-checking}
steps:
- name: Checkout the repository
uses: actions/checkout@v2
id: checkout-branch
- name: Run Linter Checks
id: linter-check
uses: harshanarayana/custom-actions@main
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }}"

38
.github/workflows/pr-windows.yml vendored Normal file
View File

@@ -0,0 +1,38 @@
name: Run Unit Tests on Windows
on:
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
testsOnWindows:
if: github.event.pull_request.draft == false
name: ut-${{ matrix.config.tox-env }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
config:
- { python-version: 3.8, tox-env: py38-no-ext }
- { python-version: 3.9, tox-env: py39-no-ext }
- { python-version: "3.10", tox-env: py310-no-ext }
- { python-version: "3.11", tox-env: py310-no-ext }
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Run Unit Tests
uses: ahopkins/custom-actions@pip-extra-args
with:
python-version: ${{ matrix.config.python-version }}
test-infra-tool: tox
test-infra-version: latest
action: tests
test-additional-args: "-e=${{ matrix.config.tox-env }}"
experimental-ignore-error: "true"
command-timeout: "600000"
pip-extra-args: "--user"

48
.github/workflows/publish-images.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Publish Docker Images
on:
workflow_run:
workflows:
- 'Publish Artifacts'
types:
- completed
jobs:
publishDockerImages:
name: Docker Image Build [${{ matrix.python-version }}]
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Build Latest Base images for ${{ matrix.python-version }}
uses: harshanarayana/custom-actions@main
with:
docker-image-base-name: sanicframework/sanic-build
ignore-python-setup: 'true'
dockerfile-base-dir: './docker'
action: 'image-publish'
docker-image-tag: "${{ matrix.python-version }}"
docker-file-suffix: "base"
docker-build-args: "PYTHON_VERSION=${{ matrix.python-version }}"
registry-auth-user: ${{ secrets.DOCKER_ACCESS_USER }}
registry-auth-password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
push-images: 'true'
- name: Publish Sanic Docker Image for ${{ matrix.python-version }}
uses: harshanarayana/custom-actions@main
with:
docker-image-base-name: sanicframework/sanic
ignore-python-setup: 'true'
dockerfile-base-dir: './docker'
action: 'image-publish'
docker-build-args: "BASE_IMAGE_TAG=${{ matrix.python-version }}"
docker-image-prefix: "${{ matrix.python-version }}"
registry-auth-user: ${{ secrets.DOCKER_ACCESS_USER }}
registry-auth-password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
push-images: 'true'

39
.github/workflows/publish-package.yml vendored Normal file
View File

@@ -0,0 +1,39 @@
name: Upload Python Package
on:
release:
types: [created]
workflow_dispatch:
jobs:
build-n-publish:
name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.x"
- name: Install pypa/build
run: >-
python3 -m
pip install
build
--user
- name: Build a binary wheel and a source tarball
run: >-
python3 -m
build
--sdist
--wheel
--outdir dist/
.
# - name: Publish distribution 📦 to Test PyPI
# uses: pypa/gh-action-pypi-publish@release/v1
# with:
# password: ${{ secrets.SANIC_TEST_PYPI_API_TOKEN }}
# repository-url: https://test.pypi.org/legacy/
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.SANIC_PYPI_API_TOKEN }}

View File

@@ -1,174 +0,0 @@
name: Publish release
on:
release:
types: [created]
env:
IS_TEST: false
DOCKER_ORG_NAME: sanicframework
DOCKER_IMAGE_NAME: sanic
DOCKER_BASE_IMAGE_NAME: sanic-build
DOCKER_IMAGE_DOCKERFILE: ./docker/Dockerfile
DOCKER_BASE_IMAGE_DOCKERFILE: ./docker/Dockerfile-base
jobs:
generate_info:
name: Generate info
runs-on: ubuntu-latest
outputs:
docker-tags: ${{ steps.generate_docker_info.outputs.tags }}
pypi-version: ${{ steps.parse_version_tag.outputs.pypi-version }}
steps:
- name: Parse version tag
id: parse_version_tag
env:
TAG_NAME: ${{ github.event.release.tag_name }}
run: |
tag_name="${{ env.TAG_NAME }}"
if [[ ! "${tag_name}" =~ ^v([0-9]{2})\.([0-9]{1,2})\.([0-9]+)$ ]]; then
echo "::error::Tag name must be in the format vYY.MM.MICRO"
exit 1
fi
year_output="year=${BASH_REMATCH[1]}"
month_output="month=${BASH_REMATCH[2]}"
pypi_output="pypi-version=${tag_name#v}"
echo "${year_output}"
echo "${month_output}"
echo "${pypi_output}"
echo "${year_output}" >> $GITHUB_OUTPUT
echo "${month_output}" >> $GITHUB_OUTPUT
echo "${pypi_output}" >> $GITHUB_OUTPUT
- name: Get latest release
id: get_latest_release
run: |
latest_tag=$(
curl -L \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ github.token }}" \
-H "X-GitHub-Api-Version: 2022-11-28" \
https://api.github.com/repos/${{ github.repository }}/releases/latest \
| jq -r '.tag_name'
)
echo "latest_tag=$latest_tag" >> $GITHUB_OUTPUT
- name: Generate Docker info
id: generate_docker_info
run: |
tag_year="${{ steps.parse_version_tag.outputs.year }}"
tag_month="${{ steps.parse_version_tag.outputs.month }}"
latest_tag="${{ steps.get_latest_release.outputs.latest_tag }}"
tag="${{ github.event.release.tag_name }}"
tags="${tag_year}.${tag_month}"
if [[ "${tag_month}" == "12" ]]; then
tags+=",LTS"
echo "::notice::Tag ${tag} is LTS version"
else
echo "::notice::Tag ${tag} is not LTS version"
fi
if [[ "${latest_tag}" == "${{ github.event.release.tag_name }}" ]]; then
tags+=",latest"
echo "::notice::Tag ${tag} is marked as latest"
else
echo "::notice::Tag ${tag} is not marked as latest"
fi
tags_output="tags=${tags}"
echo "${tags_output}"
echo "${tags_output}" >> $GITHUB_OUTPUT
publish_package:
name: Build and publish package
runs-on: ubuntu-latest
needs: generate_info
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: "3.11"
- name: Install dependencies
run: pip install build twine
- name: Update package version
run: |
echo "__version__ = \"${{ needs.generate_info.outputs.pypi-version }}\"" > sanic/__version__.py
- name: Build a binary wheel and a source tarball
run: python -m build --sdist --wheel --outdir dist/ .
- name: Publish to PyPi 🚀
run: twine upload --non-interactive --disable-progress-bar dist/*
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ env.IS_TEST == 'true' && secrets.SANIC_TEST_PYPI_API_TOKEN || secrets.SANIC_PYPI_API_TOKEN }}
TWINE_REPOSITORY: ${{ env.IS_TEST == 'true' && 'testpypi' || 'pypi' }}
publish_docker:
name: Publish Docker / Python ${{ matrix.python-version }}
needs: [generate_info, publish_package]
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
python-version: ["3.10", "3.11"]
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_ACCESS_USER }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Build and push base image
uses: docker/build-push-action@v4
with:
push: ${{ env.IS_TEST == 'false' }}
file: ${{ env.DOCKER_BASE_IMAGE_DOCKERFILE }}
tags: ${{ env.DOCKER_ORG_NAME }}/${{ env.DOCKER_BASE_IMAGE_NAME }}:${{ matrix.python-version }}
build-args: |
PYTHON_VERSION=${{ matrix.python-version }}
- name: Parse tags for this Python version
id: parse_tags
run: |
IFS=',' read -ra tags <<< "${{ needs.generate_info.outputs.docker-tags }}"
tag_args=""
for tag in "${tags[@]}"; do
tag_args+=",${{ env.DOCKER_ORG_NAME }}/${{ env.DOCKER_IMAGE_NAME }}:${tag}-py${{ matrix.python-version }}"
done
tag_args_output="tag_args=${tag_args:1}"
echo "${tag_args_output}"
echo "${tag_args_output}" >> $GITHUB_OUTPUT
- name: Build and push Sanic image
uses: docker/build-push-action@v4
with:
push: ${{ env.IS_TEST == 'false' }}
file: ${{ env.DOCKER_IMAGE_DOCKERFILE }}
tags: ${{ steps.parse_tags.outputs.tag_args }}
build-args: |
BASE_IMAGE_ORG=${{ env.DOCKER_ORG_NAME }}
BASE_IMAGE_NAME=${{ env.DOCKER_BASE_IMAGE_NAME }}
BASE_IMAGE_TAG=${{ matrix.python-version }}
SANIC_PYPI_VERSION=${{ needs.generate_info.outputs.pypi-version }}

View File

@@ -1,56 +0,0 @@
name: Tests
on:
push:
branches:
- main
- current-release
- "*LTS"
tags:
- "!*"
pull_request:
branches:
- main
- current-release
- "*LTS"
types: [opened, synchronize, reopened, ready_for_review]
jobs:
run_tests:
name: "${{ matrix.config.platform == 'windows-latest' && 'Windows' || 'Linux' }} / Python ${{ matrix.config.python-version }} / tox -e ${{ matrix.config.tox-env }}"
if: github.event.pull_request.draft == false
runs-on: ${{ matrix.config.platform || 'ubuntu-latest' }}
strategy:
fail-fast: true
matrix:
config:
- { python-version: "3.8", tox-env: security }
- { python-version: "3.9", tox-env: security }
- { python-version: "3.10", tox-env: security }
- { python-version: "3.11", tox-env: security }
- { python-version: "3.10", tox-env: lint }
- { python-version: "3.10", tox-env: docs }
- { python-version: "3.8", tox-env: type-checking }
- { python-version: "3.9", tox-env: type-checking }
- { python-version: "3.10", tox-env: type-checking }
- { python-version: "3.11", tox-env: type-checking }
- { python-version: "3.8", tox-env: py38, max-attempts: 3 }
- { python-version: "3.8", tox-env: py38-no-ext, max-attempts: 3 }
- { python-version: "3.9", tox-env: py39, max-attempts: 3 }
- { python-version: "3.9", tox-env: py39-no-ext, max-attempts: 3 }
- { python-version: "3.10", tox-env: py310, max-attempts: 3 }
- { python-version: "3.10", tox-env: py310-no-ext, max-attempts: 3 }
- { python-version: "3.11", tox-env: py311, max-attempts: 3 }
- { python-version: "3.11", tox-env: py311-no-ext, max-attempts: 3 }
- { python-version: "3.8", tox-env: py38-no-ext, platform: windows-latest, ignore-errors: true }
- { python-version: "3.9", tox-env: py39-no-ext, platform: windows-latest, ignore-errors: true }
- { python-version: "3.10", tox-env: py310-no-ext, platform: windows-latest, ignore-errors: true }
- { python-version: "3.11", tox-env: py310-no-ext, platform: windows-latest, ignore-errors: true }
steps:
- name: Run tests
uses: sanic-org/simple-tox-action@v1
with:
python-version: ${{ matrix.config.python-version }}
tox-env: ${{ matrix.config.tox-env }}
max-attempts: ${{ matrix.config.max-attempts || 1 }}
ignore-errors: ${{ matrix.config.ignore-errors || false }}

View File

@@ -11,7 +11,7 @@ Sanic | Build fast. Run fast.
:stub-columns: 1
* - Build
- | |Tests|
- | |Py310Test| |Py39Test| |Py38Test|
* - Docs
- | |UserGuide| |Documentation|
* - Package
@@ -27,8 +27,12 @@ Sanic | Build fast. Run fast.
:target: https://community.sanicframework.org/
.. |Discord| image:: https://img.shields.io/discord/812221182594121728?logo=discord
:target: https://discord.gg/FARQzAEMAA
.. |Tests| image:: https://github.com/sanic-org/sanic/actions/workflows/tests.yml/badge.svg?branch=main
:target: https://github.com/sanic-org/sanic/actions/workflows/tests.yml
.. |Py310Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python310.yml/badge.svg?branch=main
:target: https://github.com/sanic-org/sanic/actions/workflows/pr-python310.yml
.. |Py39Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python39.yml/badge.svg?branch=main
:target: https://github.com/sanic-org/sanic/actions/workflows/pr-python39.yml
.. |Py38Test| image:: https://github.com/sanic-org/sanic/actions/workflows/pr-python38.yml/badge.svg?branch=main
:target: https://github.com/sanic-org/sanic/actions/workflows/pr-python38.yml
.. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest
:target: http://sanic.readthedocs.io/en/latest/?badge=latest
.. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg

View File

@@ -1,13 +1,9 @@
ARG BASE_IMAGE_ORG
ARG BASE_IMAGE_NAME
ARG BASE_IMAGE_TAG
FROM ${BASE_IMAGE_ORG}/${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG}
FROM sanicframework/sanic-build:${BASE_IMAGE_TAG}
RUN apk update
RUN update-ca-certificates
ARG SANIC_PYPI_VERSION
RUN pip install -U pip && pip install sanic==${SANIC_PYPI_VERSION}
RUN pip install sanic
RUN apk del build-base

View File

@@ -5,7 +5,6 @@ import logging
import logging.config
import re
import sys
from asyncio import (
AbstractEventLoop,
CancelledError,
@@ -94,7 +93,6 @@ from sanic.worker.inspector import Inspector
from sanic.worker.loader import CertLoader
from sanic.worker.manager import WorkerManager
if TYPE_CHECKING:
try:
from sanic_ext import Extend # type: ignore
@@ -1743,6 +1741,20 @@ class Sanic(
if hasattr(self, "multiplexer"):
self.multiplexer.ack()
def set_serving(self, serving: bool) -> None:
"""Set the serving state of the application.
This method is used to set the serving state of the application.
It is used internally by Sanic and should not typically be called
manually.
Args:
serving (bool): Whether the application is serving.
"""
self.state.is_running = serving
if hasattr(self, "multiplexer"):
self.multiplexer.set_serving(serving)
async def _server_event(
self,
concern: str,

View File

@@ -3,7 +3,6 @@ from __future__ import annotations
import os
import platform
import sys
from asyncio import (
AbstractEventLoop,
CancelledError,
@@ -49,7 +48,7 @@ from sanic.application.motd import MOTD
from sanic.application.state import ApplicationServerInfo, Mode, ServerStage
from sanic.base.meta import SanicMeta
from sanic.compat import OS_IS_WINDOWS, StartMethod
from sanic.exceptions import SanicException, ServerKilled
from sanic.exceptions import ServerKilled
from sanic.helpers import Default, _default, is_atty
from sanic.http.constants import HTTP
from sanic.http.tls import get_ssl_context, process_to_context
@@ -65,13 +64,13 @@ from sanic.server.protocols.http_protocol import HttpProtocol
from sanic.server.protocols.websocket_protocol import WebSocketProtocol
from sanic.server.runners import serve
from sanic.server.socket import configure_socket, remove_unix_socket
from sanic.worker.constants import ProcessState
from sanic.worker.loader import AppLoader
from sanic.worker.manager import WorkerManager
from sanic.worker.multiplexer import WorkerMultiplexer
from sanic.worker.reloader import Reloader
from sanic.worker.serve import worker_serve
if TYPE_CHECKING:
from sanic import Sanic
from sanic.application.state import ApplicationState
@@ -880,18 +879,12 @@ class StartupMixin(metaclass=SanicMeta):
manager.run()
except ServerKilled:
exit_code = 1
except SanicException as e:
exit_code = 1
kwargs = primary_server_info.settings
if e.quiet:
error_logger.error(str(e))
else:
raise
except BaseException:
kwargs = primary_server_info.settings
error_logger.exception(
"Experienced exception while trying to serve"
)
raise
finally:
logger.info("Server Stopped")
for app in apps:
@@ -899,7 +892,6 @@ class StartupMixin(metaclass=SanicMeta):
app.router.reset()
app.signal_router.reset()
sync_manager.shutdown()
for sock in socks:
try:
sock.shutdown(SHUT_RDWR)
@@ -911,12 +903,33 @@ class StartupMixin(metaclass=SanicMeta):
loop.close()
cls._cleanup_env_vars()
cls._cleanup_apps()
from time import sleep
limit = 100
while cls._get_process_states(worker_state):
sleep(0.1)
limit -= 1
if limit <= 0:
error_logger.warning(
"Worker shutdown timed out. "
"Some processes may still be running."
)
break
sync_manager.shutdown()
unix = kwargs.get("unix")
if unix:
remove_unix_socket(unix)
logger.info("Goodbye.")
if exit_code:
os._exit(exit_code)
@staticmethod
def _get_process_states(worker_state) -> List[str]:
return [
state for s in worker_state.values() if (state := s.get("state"))
]
@classmethod
def serve_single(cls, primary: Optional[Sanic] = None) -> None:
os.environ["SANIC_MOTD_OUTPUT"] = "true"

View File

@@ -122,17 +122,15 @@ def _setup_system_signals(
register_sys_signals: bool,
loop: asyncio.AbstractEventLoop,
) -> None: # no cov
# Ignore SIGINT when run_multiple
if run_multiple:
signal_func(SIGINT, SIG_IGN)
signal_func(SIGTERM, SIG_IGN)
os.environ["SANIC_WORKER_PROCESS"] = "true"
# Register signals for graceful termination
if register_sys_signals:
if OS_IS_WINDOWS:
ctrlc_workaround_for_windows(app)
else:
for _signal in [SIGTERM] if run_multiple else [SIGINT, SIGTERM]:
for _signal in [SIGINT, SIGTERM]:
loop.add_signal_handler(
_signal, partial(app.stop, terminate=False)
)
@@ -143,8 +141,6 @@ def _run_server_forever(loop, before_stop, after_stop, cleanup, unix):
try:
server_logger.info("Starting worker [%s]", pid)
loop.run_forever()
except KeyboardInterrupt:
pass
finally:
server_logger.info("Stopping worker [%s]", pid)
@@ -156,6 +152,7 @@ def _run_server_forever(loop, before_stop, after_stop, cleanup, unix):
loop.run_until_complete(after_stop())
remove_unix_socket(unix)
loop.close()
server_logger.info("Worker complete [%s]", pid)
def _serve_http_1(
@@ -259,8 +256,11 @@ def _serve_http_1(
else:
conn.abort()
app.set_serving(False)
_setup_system_signals(app, run_multiple, register_sys_signals, loop)
loop.run_until_complete(app._server_event("init", "after"))
app.set_serving(True)
_run_server_forever(
loop,
partial(app._server_event, "shutdown", "before"),

View File

@@ -47,10 +47,10 @@ def bind_unix_socket(path: str, *, mode=0o666, backlog=100) -> socket.socket:
path = os.path.abspath(path)
folder = os.path.dirname(path)
if not os.path.isdir(folder):
raise FileNotFoundError("Socket folder does not exist")
raise FileNotFoundError(f"Socket folder does not exist: {folder}")
try:
if not stat.S_ISSOCK(os.stat(path, follow_symlinks=False).st_mode):
raise FileExistsError("Existing file is not a socket")
raise FileExistsError(f"Existing file is not a socket: {path}")
except FileNotFoundError:
pass
# Create new socket with a random temporary name
@@ -103,10 +103,7 @@ def configure_socket(
unix = server_settings["unix"]
backlog = server_settings["backlog"]
if unix:
try:
sock = bind_unix_socket(unix, backlog=backlog)
except OSError as e:
raise ServerError(f"Error binding {unix}: {e}", quiet=True)
server_settings["unix"] = unix
if sock is None:
try:
@@ -115,17 +112,6 @@ def configure_socket(
server_settings["port"],
backlog=backlog,
)
except PermissionError:
p = server_settings["port"]
if not p or p >= 1024:
raise
addr = f"{server_settings['host']}:{p}"
error = ServerError(
f"Permission denied binding to {addr}.\n\n"
"Use `sudo sanic` to run on a privileged port.\n"
)
error.quiet = True
raise error
except OSError as e: # no cov
error = ServerError(
f"Sanic server could not start: {e}.\n\n"

View File

@@ -532,11 +532,12 @@ class WebsocketImplProtocol:
raise WebsocketClosed(
"Cannot receive from websocket interface after it is closed."
)
assembler_get: Optional[asyncio.Task] = None
try:
self.recv_cancel = asyncio.Future()
assembler_get = asyncio.create_task(self.assembler.get(timeout))
tasks = (self.recv_cancel, assembler_get)
tasks = (
self.recv_cancel,
asyncio.ensure_future(self.assembler.get(timeout)),
)
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_COMPLETED,
@@ -550,11 +551,6 @@ class WebsocketImplProtocol:
else:
self.recv_cancel.cancel()
return done_task.result()
except asyncio.CancelledError:
# recv was cancelled
if assembler_get:
assembler_get.cancel()
raise
finally:
self.recv_cancel = None
self.recv_lock.release()
@@ -588,15 +584,16 @@ class WebsocketImplProtocol:
"Cannot receive from websocket interface after it is closed."
)
messages = []
assembler_get: Optional[asyncio.Task] = None
try:
# Prevent pausing the transport when we're
# receiving a burst of messages
self.can_pause = False
self.recv_cancel = asyncio.Future()
while True:
assembler_get = asyncio.create_task(self.assembler.get(0))
tasks = (self.recv_cancel, assembler_get)
tasks = (
self.recv_cancel,
asyncio.ensure_future(self.assembler.get(timeout=0)),
)
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_COMPLETED,
@@ -619,11 +616,6 @@ class WebsocketImplProtocol:
# next message to pass into the Assembler
await asyncio.sleep(0)
self.recv_cancel.cancel()
except asyncio.CancelledError:
# recv_burst was cancelled
if assembler_get:
assembler_get.cancel()
raise
finally:
self.recv_cancel = None
self.can_pause = True

View File

@@ -122,6 +122,7 @@ class WorkerManager:
self.monitor()
self.join()
self.terminate()
self.cleanup()
def start(self):
for process in self.processes:
@@ -147,6 +148,11 @@ class WorkerManager:
for process in self.processes:
process.terminate()
def cleanup(self):
"""Cleanup the worker processes."""
for process in self.processes:
process.exit()
def restart(
self,
process_names: Optional[List[str]] = None,

View File

@@ -28,6 +28,24 @@ class WorkerMultiplexer:
"state": ProcessState.ACKED.name,
}
def set_serving(self, serving: bool) -> None:
"""Set the worker to serving.
Args:
serving (bool): Whether the worker is serving.
"""
self._state._state[self.name] = {
**self._state._state[self.name],
"serving": serving,
}
def exit(self):
"""Run cleanup at worker exit."""
try:
del self._state._state[self.name]
except ConnectionRefusedError:
logger.debug("Monitor process has already exited.")
def restart(
self,
name: str = "",

View File

@@ -65,6 +65,20 @@ class WorkerProcess:
self.set_state(ProcessState.JOINED)
self._current_process.join()
def exit(self):
limit = 100
while self.is_alive() and limit > 0:
sleep(0.1)
limit -= 1
if not self.is_alive():
try:
del self.worker_state[self.name]
except ConnectionRefusedError:
logger.debug("Monitor process has already exited.")
except KeyError:
logger.debug("Could not find worker state to delete.")
def terminate(self):
if self.state is not ProcessState.TERMINATED:
logger.debug(
@@ -77,7 +91,6 @@ class WorkerProcess:
self.set_state(ProcessState.TERMINATED, force=True)
try:
os.kill(self.pid, SIGINT)
del self.worker_state[self.name]
except (KeyError, AttributeError, ProcessLookupError):
...
@@ -118,6 +131,16 @@ class WorkerProcess:
except AssertionError:
return False
# def _run(self, **kwargs):
# atexit.register(self._exit)
# self.target(**kwargs)
# def _exit(self):
# try:
# del self.worker_state[self.name]
# except ConnectionRefusedError:
# logger.debug("Monitor process has already exited.")
def spawn(self):
if self.state not in (ProcessState.IDLE, ProcessState.RESTARTING):
raise Exception("Cannot spawn a worker process until it is idle.")