Compare commits

..

3 Commits

Author SHA1 Message Date
Adam Hopkins
dcea7da2e1 Allow for simple signals 2023-08-30 12:48:04 +03:00
Adam Hopkins
38ff9069f3 Suppress task cancel traceback (#2812) 2023-08-30 09:05:37 +03:00
Néstor Pérez
4dde4572ec Refactor GitHub Actions (#2808) 2023-08-29 21:47:40 +03:00
26 changed files with 294 additions and 652 deletions

View File

@@ -12,29 +12,23 @@ on:
- main
- current-release
- "*LTS"
jobs:
test:
runs-on: ${{ matrix.os }}
coverage:
name: Check coverage
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.9]
os: [ubuntu-latest]
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v1
with:
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
uses: sanic-org/simple-tox-action@v1
with:
python-version: "3.11"
tox-env: coverage
ignore-errors: true
- name: Run Codecov
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
fail_ci_if_error: false

View File

@@ -1,39 +0,0 @@
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"

View File

@@ -1,37 +0,0 @@
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 }}"

View File

@@ -1,33 +0,0 @@
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 }}"

View File

@@ -1,34 +0,0 @@
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 }}"

View File

@@ -1,41 +0,0 @@
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"

View File

@@ -1,48 +0,0 @@
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"

View File

@@ -1,48 +0,0 @@
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"

View File

@@ -1,36 +0,0 @@
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"

View File

@@ -1,48 +0,0 @@
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"

View File

@@ -1,37 +0,0 @@
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 }}"

View File

@@ -1,38 +0,0 @@
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"

View File

@@ -1,48 +0,0 @@
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'

View File

@@ -1,39 +0,0 @@
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 }}

174
.github/workflows/publish-release.yml vendored Normal file
View File

@@ -0,0 +1,174 @@
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 }}

49
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: Tests
on:
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
- | |Py310Test| |Py39Test| |Py38Test|
- | |Tests|
* - Docs
- | |UserGuide| |Documentation|
* - Package
@@ -27,12 +27,8 @@ 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
.. |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
.. |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
.. |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,9 +1,13 @@
ARG BASE_IMAGE_ORG
ARG BASE_IMAGE_NAME
ARG BASE_IMAGE_TAG
FROM sanicframework/sanic-build:${BASE_IMAGE_TAG}
FROM ${BASE_IMAGE_ORG}/${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG}
RUN apk update
RUN update-ca-certificates
RUN pip install sanic
ARG SANIC_PYPI_VERSION
RUN pip install -U pip && pip install sanic==${SANIC_PYPI_VERSION}
RUN apk del build-base

View File

@@ -5,6 +5,7 @@ import logging
import logging.config
import re
import sys
from asyncio import (
AbstractEventLoop,
CancelledError,
@@ -93,6 +94,7 @@ 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
@@ -1741,20 +1743,6 @@ 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,6 +3,7 @@ from __future__ import annotations
import os
import platform
import sys
from asyncio import (
AbstractEventLoop,
CancelledError,
@@ -64,13 +65,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
@@ -892,6 +893,7 @@ class StartupMixin(metaclass=SanicMeta):
app.router.reset()
app.signal_router.reset()
sync_manager.shutdown()
for sock in socks:
try:
sock.shutdown(SHUT_RDWR)
@@ -903,33 +905,12 @@ 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,15 +122,17 @@ def _setup_system_signals(
register_sys_signals: bool,
loop: asyncio.AbstractEventLoop,
) -> None: # no cov
signal_func(SIGINT, SIG_IGN)
signal_func(SIGTERM, SIG_IGN)
os.environ["SANIC_WORKER_PROCESS"] = "true"
# Ignore SIGINT when run_multiple
if run_multiple:
signal_func(SIGINT, 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 [SIGINT, SIGTERM]:
for _signal in [SIGTERM] if run_multiple else [SIGINT, SIGTERM]:
loop.add_signal_handler(
_signal, partial(app.stop, terminate=False)
)
@@ -141,6 +143,8 @@ 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)
@@ -152,7 +156,6 @@ 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(
@@ -256,11 +259,8 @@ 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

@@ -532,12 +532,11 @@ 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()
tasks = (
self.recv_cancel,
asyncio.ensure_future(self.assembler.get(timeout)),
)
assembler_get = asyncio.create_task(self.assembler.get(timeout))
tasks = (self.recv_cancel, assembler_get)
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_COMPLETED,
@@ -551,6 +550,11 @@ 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()
@@ -584,16 +588,15 @@ 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:
tasks = (
self.recv_cancel,
asyncio.ensure_future(self.assembler.get(timeout=0)),
)
assembler_get = asyncio.create_task(self.assembler.get(0))
tasks = (self.recv_cancel, assembler_get)
done, pending = await asyncio.wait(
tasks,
return_when=asyncio.FIRST_COMPLETED,
@@ -616,6 +619,11 @@ 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

@@ -39,6 +39,7 @@ class Event(Enum):
RESERVED_NAMESPACES = {
"sanic": ("sanic.__signal__.__init__",),
"server": (
Event.SERVER_EXCEPTION_REPORT.value,
Event.SERVER_INIT_AFTER.value,
@@ -65,6 +66,8 @@ RESERVED_NAMESPACES = {
),
}
GENERIC_SIGNAL_FORMAT = "__generic__.__signal__.%s"
def _blank():
...
@@ -89,11 +92,26 @@ class SignalRouter(BaseRouter):
self.allow_fail_builtin = True
self.ctx.loop = None
@staticmethod
def format_event(event: str) -> str:
"""Ensure event strings in proper format
Args:
event (str): event string
Returns:
str: formatted event string
"""
if "." not in event:
event = GENERIC_SIGNAL_FORMAT % event
return event
def get( # type: ignore
self,
event: str,
condition: Optional[Dict[str, str]] = None,
):
event = self.format_event(event)
extra = condition or {}
try:
group, param_basket = self.find_route(
@@ -133,6 +151,7 @@ class SignalRouter(BaseRouter):
fail_not_found: bool = True,
reverse: bool = False,
) -> Any:
event = self.format_event(event)
try:
group, handlers, params = self.get(event, condition=condition)
except NotFound as e:
@@ -195,6 +214,7 @@ class SignalRouter(BaseRouter):
inline: bool = False,
reverse: bool = False,
) -> Union[asyncio.Task, Any]:
event = self.format_event(event)
dispatch = self._dispatch(
event,
context=context,
@@ -218,6 +238,7 @@ class SignalRouter(BaseRouter):
condition: Optional[Dict[str, Any]] = None,
exclusive: bool = True,
) -> Signal:
event = self.format_event(event)
event_definition = event
parts = self._build_event_parts(event)
if parts[2].startswith("<"):

View File

@@ -122,7 +122,6 @@ class WorkerManager:
self.monitor()
self.join()
self.terminate()
self.cleanup()
def start(self):
for process in self.processes:
@@ -148,11 +147,6 @@ 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,24 +28,6 @@ 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,20 +65,6 @@ 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(
@@ -91,6 +77,7 @@ 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):
...
@@ -131,16 +118,6 @@ 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.")