Compare commits
218 Commits
v21.6.2
...
monitor-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f83553be9e | ||
|
|
0c3527b8b2 | ||
|
|
232bbce1e0 | ||
|
|
f034a31d29 | ||
|
|
3536c0af27 | ||
|
|
0e7ee94574 | ||
|
|
4e4e2b036b | ||
|
|
92e7463721 | ||
|
|
7f682cea02 | ||
|
|
ae1669cd8f | ||
|
|
8e720365c2 | ||
|
|
3c4c136090 | ||
|
|
d4041161c7 | ||
|
|
f32437bf13 | ||
|
|
0909e94527 | ||
|
|
aef2673c38 | ||
|
|
4c14910d5b | ||
|
|
beae35f921 | ||
|
|
ad4e526c77 | ||
|
|
4422d0c34d | ||
|
|
ad9183d21d | ||
|
|
d70636ba2e | ||
|
|
da23f85675 | ||
|
|
3f4663b9f8 | ||
|
|
65d7447cf6 | ||
|
|
5369291c27 | ||
|
|
1c4925edf7 | ||
|
|
6b9edfd05c | ||
|
|
97f33f42df | ||
|
|
15a588a90c | ||
|
|
82421e7efc | ||
|
|
f891995b48 | ||
|
|
5052321801 | ||
|
|
23ce4eaaa4 | ||
|
|
23a430c4ad | ||
|
|
ec158ffa69 | ||
|
|
6e32270036 | ||
|
|
43ba381e7b | ||
|
|
16503319e5 | ||
|
|
389363ab71 | ||
|
|
7f894c45b3 | ||
|
|
4726cf1910 | ||
|
|
d352a4155e | ||
|
|
e5010286b4 | ||
|
|
358498db96 | ||
|
|
e4999401ab | ||
|
|
c8df0aa2cb | ||
|
|
5fb207176b | ||
|
|
a12b560478 | ||
|
|
753ee992a6 | ||
|
|
09089b1bd3 | ||
|
|
7ddbe5e844 | ||
|
|
ab5a7038af | ||
|
|
4f3c780dc3 | ||
|
|
71f7765a4c | ||
|
|
0392d1dcfc | ||
|
|
7827b1b41d | ||
|
|
8e9342e188 | ||
|
|
2f6f2bfa76 | ||
|
|
dee09d7fff | ||
|
|
9cf38a0a83 | ||
|
|
3def3d3569 | ||
|
|
e100a14fd4 | ||
|
|
2fa28f1711 | ||
|
|
9d415e4ec6 | ||
|
|
312ab298fd | ||
|
|
2fc21ad576 | ||
|
|
8f6c87c3d6 | ||
|
|
4429e76532 | ||
|
|
e4be70bae8 | ||
|
|
13d5a44278 | ||
|
|
aba333bfb6 | ||
|
|
b59da498cc | ||
|
|
70382f21ba | ||
|
|
0e1bf89fad | ||
|
|
6c48c8b3ba | ||
|
|
d1c5e8003b | ||
|
|
ce926a34f2 | ||
|
|
a744041e38 | ||
|
|
2f90a85df1 | ||
|
|
a411bc06e3 | ||
|
|
1668e1532f | ||
|
|
b87982769f | ||
|
|
65b53a5f3f | ||
|
|
49789b7841 | ||
|
|
c249004c30 | ||
|
|
4ee2e57ec8 | ||
|
|
86ae5f981c | ||
|
|
2bfa65e0de | ||
|
|
293278bb08 | ||
|
|
5d683c6ea4 | ||
|
|
78b6723149 | ||
|
|
3a6cc7389c | ||
|
|
cc97287f8e | ||
|
|
00218aa9f2 | ||
|
|
874718db94 | ||
|
|
bb4474897f | ||
|
|
0cb342aef4 | ||
|
|
030987480c | ||
|
|
f6fdc80b40 | ||
|
|
361c242473 | ||
|
|
32962d1e1c | ||
|
|
6e0a6871b5 | ||
|
|
0030425c8c | ||
|
|
c9dbc8ed26 | ||
|
|
44b108b564 | ||
|
|
2a8e91052f | ||
|
|
0c9df02e66 | ||
|
|
7523e87937 | ||
|
|
d4fb44e986 | ||
|
|
68b654d981 | ||
|
|
88bc6d8966 | ||
|
|
ac388d644b | ||
|
|
bb517ddcca | ||
|
|
b8d991420b | ||
|
|
4a416e177a | ||
|
|
8dfa49b648 | ||
|
|
8b0eaa097c | ||
|
|
101151b419 | ||
|
|
4669036f45 | ||
|
|
9bf9067c99 | ||
|
|
a7bc8b56ba | ||
|
|
371985d129 | ||
|
|
3eae00898d | ||
|
|
dc3ccba527 | ||
|
|
b91ffed010 | ||
|
|
8c07e388cd | ||
|
|
98ce4bdeb2 | ||
|
|
4659069350 | ||
|
|
080d41627a | ||
|
|
d799c5f03c | ||
|
|
abe062b371 | ||
|
|
b5a00ac1ca | ||
|
|
f282865362 | ||
|
|
377c2ada38 | ||
|
|
264453459e | ||
|
|
3d383d7b97 | ||
|
|
c0cc26021b | ||
|
|
96c027bad5 | ||
|
|
b2a1bc69f5 | ||
|
|
426742b3e2 | ||
|
|
ab35121864 | ||
|
|
cf3c205fa5 | ||
|
|
19f6544923 | ||
|
|
f641830d26 | ||
|
|
a8d55e180c | ||
|
|
55c36e0240 | ||
|
|
2c03eee329 | ||
|
|
65e28b8c22 | ||
|
|
dfd33dd63d | ||
|
|
722a6db8d9 | ||
|
|
9c576c74db | ||
|
|
523db190a7 | ||
|
|
95631b9686 | ||
|
|
0860bfe1f1 | ||
|
|
85e7b712b9 | ||
|
|
b731a6b48c | ||
|
|
cde02b5936 | ||
|
|
abeb8d0bc0 | ||
|
|
9a9f72ad64 | ||
|
|
392a497366 | ||
|
|
36e6a6c506 | ||
|
|
a361b345ad | ||
|
|
f5bd6e3b2f | ||
|
|
6c7df68c7c | ||
|
|
5b82884f8b | ||
|
|
f0f81ec458 | ||
|
|
71cc30e5cd | ||
|
|
645310cff6 | ||
|
|
2f30b5748a | ||
|
|
5e1ef96934 | ||
|
|
57e98b62b3 | ||
|
|
3262878ebd | ||
|
|
5e12edbc38 | ||
|
|
50a606adee | ||
|
|
f995612073 | ||
|
|
bc08383acd | ||
|
|
b83a1a184c | ||
|
|
59dd6814f8 | ||
|
|
f7abf3db1b | ||
|
|
cf1d2148ac | ||
|
|
b5f2bd9b0e | ||
|
|
ba2670e99c | ||
|
|
6ffc4d9756 | ||
|
|
595d2c76ac | ||
|
|
d9796e9b1e | ||
|
|
404c5f9f9e | ||
|
|
a937e08ef0 | ||
|
|
ef4f058a6c | ||
|
|
69c5dde9bf | ||
|
|
945885d501 | ||
|
|
9d0b54c90d | ||
|
|
2e5c288fea | ||
|
|
f32ef20b74 | ||
|
|
e2eefaac55 | ||
|
|
e1cfbf0fd9 | ||
|
|
08c5689441 | ||
|
|
8dbda247d6 | ||
|
|
71a631237d | ||
|
|
e22ff3828b | ||
|
|
b1b12e004e | ||
|
|
0ba57d4701 | ||
|
|
54ca6a6178 | ||
|
|
7dd4a78cf2 | ||
|
|
52ff49512a | ||
|
|
4732b6bdfa | ||
|
|
c3b6fa1bba | ||
|
|
94d496afe1 | ||
|
|
7b7a572f9b | ||
|
|
1b8cb742f9 | ||
|
|
3492d180a8 | ||
|
|
021da38373 | ||
|
|
ac784759d5 | ||
|
|
36eda2cd62 | ||
|
|
08a4b3013f | ||
|
|
1dd0332e8b | ||
|
|
a90877ac31 | ||
|
|
8b7ea27a48 |
@@ -1,2 +0,0 @@
|
||||
[tool.black]
|
||||
line-length = 79
|
||||
@@ -1,12 +0,0 @@
|
||||
exclude_patterns:
|
||||
- "sanic/__main__.py"
|
||||
- "sanic/reloader_helpers.py"
|
||||
- "sanic/simple.py"
|
||||
- "sanic/utils.py"
|
||||
- ".github/"
|
||||
- "changelogs/"
|
||||
- "docker/"
|
||||
- "docs/"
|
||||
- "examples/"
|
||||
- "hack/"
|
||||
- "scripts/"
|
||||
@@ -4,9 +4,11 @@ source = sanic
|
||||
omit =
|
||||
site-packages
|
||||
sanic/__main__.py
|
||||
sanic/reloader_helpers.py
|
||||
sanic/server/legacy.py
|
||||
sanic/compat.py
|
||||
sanic/simple.py
|
||||
sanic/utils.py
|
||||
sanic/cli
|
||||
|
||||
[html]
|
||||
directory = coverage
|
||||
@@ -18,3 +20,5 @@ exclude_lines =
|
||||
noqa
|
||||
NOQA
|
||||
pragma: no cover
|
||||
TYPE_CHECKING
|
||||
skip_empty = True
|
||||
|
||||
66
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
66
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: 🐞 Bug report
|
||||
description: Create a report to help us improve
|
||||
labels: ["bug", "triage"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: existing
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is, make sure to paste any exceptions and tracebacks using markdown code-block syntax to make it easier to read.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: code
|
||||
attributes:
|
||||
label: Code snippet
|
||||
description: Relevant source code, make sure to remove what is not necessary.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A concise description of what you expected to happen.
|
||||
validations:
|
||||
required: false
|
||||
- type: dropdown
|
||||
id: running
|
||||
attributes:
|
||||
label: How do you run Sanic?
|
||||
options:
|
||||
- Sanic CLI
|
||||
- As a module
|
||||
- As a script (`app.run` or `Sanic.serve`)
|
||||
- ASGI
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: What OS?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Sanic Version
|
||||
description: Check startup logs or try `sanic --version`
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
25
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,25 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is, make sure to paste any exceptions and tracebacks.
|
||||
|
||||
|
||||
**Code snippet**
|
||||
Relevant source code, make sure to remove what is not necessary.
|
||||
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Version [e.g. 0.8.3]
|
||||
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Questions and Help
|
||||
url: https://community.sanicframework.org/c/questions-and-help
|
||||
about: Do you need help with Sanic? Ask your questions here.
|
||||
- name: Discussion and Support
|
||||
url: https://discord.gg/FARQzAEMAA
|
||||
about: For live discussion and support, checkout the Sanic Discord server.
|
||||
|
||||
34
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
34
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: 🌟 Feature request
|
||||
description: Suggest an enhancement for Sanic
|
||||
labels: ["feature request"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: existing
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the enhancement you are proposing.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: code
|
||||
attributes:
|
||||
label: Describe the solution you'd like
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
16
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,16 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for Sanic
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
|
||||
**Additional context**
|
||||
Add any other context or sample code about the feature request here.
|
||||
10
.github/workflows/codeql-analysis.yml
vendored
10
.github/workflows/codeql-analysis.yml
vendored
@@ -2,14 +2,20 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches:
|
||||
- main
|
||||
- "*LTS"
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches:
|
||||
- main
|
||||
- "*LTS"
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
schedule:
|
||||
- cron: '25 16 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
36
.github/workflows/coverage.yml
vendored
36
.github/workflows/coverage.yml
vendored
@@ -1,17 +1,15 @@
|
||||
name: Coverage check
|
||||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# tags:
|
||||
# - "!*" # Do not execute on tags
|
||||
# paths:
|
||||
# - sanic/*
|
||||
# - tests/*
|
||||
# pull_request:
|
||||
# paths:
|
||||
# - "!*.MD"
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- "*LTS"
|
||||
tags:
|
||||
- "!*" # Do not execute on tags
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "*LTS"
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -23,7 +21,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
@@ -32,9 +29,10 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install tox
|
||||
- uses: paambaati/codeclimate-action@v2.5.3
|
||||
if: always()
|
||||
env:
|
||||
CC_TEST_REPORTER_ID: ${{ secrets.CODECLIMATE }}
|
||||
- name: Run coverage
|
||||
run: tox -e coverage
|
||||
continue-on-error: true
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
coverageCommand: tox -e coverage
|
||||
files: ./coverage.xml
|
||||
fail_ci_if_error: false
|
||||
|
||||
4
.github/workflows/pr-bandit.yml
vendored
4
.github/workflows/pr-bandit.yml
vendored
@@ -3,9 +3,12 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "*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:
|
||||
@@ -16,6 +19,7 @@ jobs:
|
||||
- { python-version: 3.7, tox-env: security}
|
||||
- { python-version: 3.8, tox-env: security}
|
||||
- { python-version: 3.9, tox-env: security}
|
||||
- { python-version: "3.10", tox-env: security}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
3
.github/workflows/pr-docs.yml
vendored
3
.github/workflows/pr-docs.yml
vendored
@@ -3,9 +3,12 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "*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:
|
||||
|
||||
3
.github/workflows/pr-linter.yml
vendored
3
.github/workflows/pr-linter.yml
vendored
@@ -3,9 +3,12 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "*LTS"
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
jobs:
|
||||
linter:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: lint
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
|
||||
47
.github/workflows/pr-python310.yml
vendored
Normal file
47
.github/workflows/pr-python310.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
name: Python 3.10 Tests
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "*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"
|
||||
11
.github/workflows/pr-python37.yml
vendored
11
.github/workflows/pr-python37.yml
vendored
@@ -3,19 +3,16 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- sanic/*
|
||||
- tests/*
|
||||
- "*LTS"
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
jobs:
|
||||
testPy37:
|
||||
if: github.event.pull_request.draft == false
|
||||
name: ut-${{ matrix.config.tox-env }}-${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
# os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
11
.github/workflows/pr-python38.yml
vendored
11
.github/workflows/pr-python38.yml
vendored
@@ -3,19 +3,16 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- sanic/*
|
||||
- tests/*
|
||||
- "*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: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
# os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
11
.github/workflows/pr-python39.yml
vendored
11
.github/workflows/pr-python39.yml
vendored
@@ -3,19 +3,16 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- sanic/*
|
||||
- tests/*
|
||||
- "*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: false
|
||||
fail-fast: true
|
||||
matrix:
|
||||
# os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
6
.github/workflows/pr-type-check.yml
vendored
6
.github/workflows/pr-type-check.yml
vendored
@@ -3,9 +3,12 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "*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:
|
||||
@@ -13,9 +16,10 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
config:
|
||||
- { python-version: 3.7, tox-env: type-checking}
|
||||
# - { python-version: 3.7, tox-env: type-checking}
|
||||
- { python-version: 3.8, tox-env: type-checking}
|
||||
- { python-version: 3.9, tox-env: type-checking}
|
||||
- { python-version: "3.10", tox-env: type-checking}
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
66
.github/workflows/pr-windows.yml
vendored
66
.github/workflows/pr-windows.yml
vendored
@@ -1,34 +1,38 @@
|
||||
# name: Run Unit Tests on Windows
|
||||
# on:
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - main
|
||||
name: Run Unit Tests on Windows
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- "*LTS"
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
# jobs:
|
||||
# testsOnWindows:
|
||||
# name: ut-${{ matrix.config.tox-env }}
|
||||
# runs-on: windows-latest
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# config:
|
||||
# - { python-version: 3.7, tox-env: py37-no-ext }
|
||||
# - { python-version: 3.8, tox-env: py38-no-ext }
|
||||
# - { python-version: 3.9, tox-env: py39-no-ext }
|
||||
# - { python-version: pypy-3.7, tox-env: pypy37-no-ext }
|
||||
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.7, tox-env: py37-no-ext }
|
||||
- { 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: pypy-3.7, tox-env: pypy37-no-ext }
|
||||
|
||||
# steps:
|
||||
# - name: Checkout Repository
|
||||
# uses: actions/checkout@v2
|
||||
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"
|
||||
- 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"
|
||||
|
||||
2
.github/workflows/publish-images.yml
vendored
2
.github/workflows/publish-images.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
python-version: ["3.7", "3.8", "3.9"]
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
|
||||
224
CHANGELOG.rst
224
CHANGELOG.rst
@@ -1,8 +1,26 @@
|
||||
.. note::
|
||||
|
||||
CHANGELOG files are maintained in ``./docs/sanic/releases``. To view the full CHANGELOG, please visit https://sanic.readthedocs.io/en/stable/sanic/changelog.html.
|
||||
|
||||
|
||||
Version 21.6.1
|
||||
--------------
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* `#2178 <https://github.com/sanic-org/sanic/pull/2178>`_
|
||||
Update sanic-routing to allow for better splitting of complex URI templates
|
||||
* `#2183 <https://github.com/sanic-org/sanic/pull/2183>`_
|
||||
Proper handling of chunked request bodies to resolve phantom 503 in logs
|
||||
* `#2181 <https://github.com/sanic-org/sanic/pull/2181>`_
|
||||
Resolve regression in exception logging
|
||||
* `#2201 <https://github.com/sanic-org/sanic/pull/2201>`_
|
||||
Cleanup request info in pipelined requests
|
||||
|
||||
Version 21.6.0
|
||||
--------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
* `#2094 <https://github.com/sanic-org/sanic/pull/2094>`_
|
||||
Add ``response.eof()`` method for closing a stream in a handler
|
||||
@@ -49,8 +67,7 @@ Features
|
||||
* `#2170 <https://github.com/sanic-org/sanic/pull/2170>`_
|
||||
Additional methods for attaching ``HTTPMethodView``
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
* `#2091 <https://github.com/sanic-org/sanic/pull/2091>`_
|
||||
Fix ``UserWarning`` in ASGI mode for missing ``__slots__``
|
||||
@@ -66,8 +83,7 @@ Bugfixes
|
||||
Fix issue where Blueprint exception handlers do not consistently route to proper handler
|
||||
|
||||
|
||||
Deprecations and Removals
|
||||
*************************
|
||||
**Deprecations and Removals**
|
||||
|
||||
* `#2156 <https://github.com/sanic-org/sanic/pull/2156>`_
|
||||
Remove config value ``REQUEST_BUFFER_QUEUE_SIZE``
|
||||
@@ -76,14 +92,12 @@ Deprecations and Removals
|
||||
* `#2172 <https://github.com/sanic-org/sanic/pull/2170>`_
|
||||
Deprecate StreamingHTTPResponse
|
||||
|
||||
Developer infrastructure
|
||||
************************
|
||||
**Developer infrastructure**
|
||||
|
||||
* `#2149 <https://github.com/sanic-org/sanic/pull/2149>`_
|
||||
Remove Travis CI in favor of GitHub Actions
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
* `#2164 <https://github.com/sanic-org/sanic/pull/2164>`_
|
||||
Fix typo in documentation
|
||||
@@ -93,8 +107,7 @@ Improved Documentation
|
||||
Version 21.3.2
|
||||
--------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
* `#2081 <https://github.com/sanic-org/sanic/pull/2081>`_
|
||||
Disable response timeout on websocket connections
|
||||
@@ -105,8 +118,7 @@ Bugfixes
|
||||
Version 21.3.1
|
||||
--------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
* `#2076 <https://github.com/sanic-org/sanic/pull/2076>`_
|
||||
Static files inside subfolders are not accessible (404)
|
||||
@@ -116,8 +128,7 @@ Version 21.3.0
|
||||
|
||||
`Release Notes <https://sanicframework.org/en/guide/release-notes/v21.3.html>`_
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1876 <https://github.com/sanic-org/sanic/pull/1876>`_
|
||||
@@ -170,8 +181,7 @@ Features
|
||||
`#2063 <https://github.com/sanic-org/sanic/pull/2063>`_
|
||||
App and connection level context objects
|
||||
|
||||
Bugfixes and issues resolved
|
||||
****************************
|
||||
**Bugfixes**
|
||||
|
||||
* Resolve `#1420 <https://github.com/sanic-org/sanic/pull/1420>`_
|
||||
``url_for`` where ``strict_slashes`` are on for a path ending in ``/``
|
||||
@@ -201,8 +211,7 @@ Bugfixes and issues resolved
|
||||
`#2001 <https://github.com/sanic-org/sanic/pull/2001>`_
|
||||
Raise ValueError when cookie max-age is not an integer
|
||||
|
||||
Deprecations and Removals
|
||||
*************************
|
||||
**Deprecations and Removals**
|
||||
|
||||
*
|
||||
`#2007 <https://github.com/sanic-org/sanic/pull/2007>`_
|
||||
@@ -221,8 +230,7 @@ Deprecations and Removals
|
||||
* ``Request.endpoint`` deprecated in favor of ``Request.name``
|
||||
* handler type name prefixes removed (static, websocket, etc)
|
||||
|
||||
Developer infrastructure
|
||||
************************
|
||||
**Developer infrastructure**
|
||||
|
||||
*
|
||||
`#1995 <https://github.com/sanic-org/sanic/pull/1995>`_
|
||||
@@ -240,8 +248,7 @@ Developer infrastructure
|
||||
`#2049 <https://github.com/sanic-org/sanic/pull/2049>`_
|
||||
Updated setup.py to use ``find_packages``
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
*
|
||||
`#1218 <https://github.com/sanic-org/sanic/pull/1218>`_
|
||||
@@ -263,8 +270,7 @@ Improved Documentation
|
||||
`#2052 <https://github.com/sanic-org/sanic/pull/2052>`_
|
||||
Fix some examples and docs
|
||||
|
||||
Miscellaneous
|
||||
*************
|
||||
**Miscellaneous**
|
||||
|
||||
* ``Request.route`` property
|
||||
* Better websocket subprotocols support
|
||||
@@ -307,11 +313,12 @@ Miscellaneous
|
||||
`#2074 <https://github.com/sanic-org/sanic/pull/2074>`_
|
||||
Performance adjustments in ``handle_request_``
|
||||
|
||||
Version 20.12.3
|
||||
---------------
|
||||
Version 20.12.3 🔷
|
||||
------------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
`Current LTS version`
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#2021 <https://github.com/sanic-org/sanic/pull/2021>`_
|
||||
@@ -320,8 +327,7 @@ Bugfixes
|
||||
Version 20.12.2
|
||||
---------------
|
||||
|
||||
Dependencies
|
||||
************
|
||||
**Dependencies**
|
||||
|
||||
*
|
||||
`#2026 <https://github.com/sanic-org/sanic/pull/2026>`_
|
||||
@@ -334,8 +340,7 @@ Dependencies
|
||||
Version 19.12.5
|
||||
---------------
|
||||
|
||||
Dependencies
|
||||
************
|
||||
**Dependencies**
|
||||
|
||||
*
|
||||
`#2025 <https://github.com/sanic-org/sanic/pull/2025>`_
|
||||
@@ -345,22 +350,15 @@ Dependencies
|
||||
`#2027 <https://github.com/sanic-org/sanic/pull/2027>`_
|
||||
Remove old chardet requirement, add in hard multidict requirement
|
||||
|
||||
Version 20.12.0
|
||||
---------------
|
||||
Version 20.12.0 🔹
|
||||
-----------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1993 <https://github.com/sanic-org/sanic/pull/1993>`_
|
||||
Add disable app registry
|
||||
|
||||
Version 20.12.0
|
||||
---------------
|
||||
|
||||
Features
|
||||
********
|
||||
|
||||
*
|
||||
`#1945 <https://github.com/sanic-org/sanic/pull/1945>`_
|
||||
Static route more verbose if file not found
|
||||
@@ -397,22 +395,19 @@ Features
|
||||
`#1979 <https://github.com/sanic-org/sanic/pull/1979>`_
|
||||
Add app registry and Sanic class level app retrieval
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1965 <https://github.com/sanic-org/sanic/pull/1965>`_
|
||||
Fix Chunked Transport-Encoding in ASGI streaming response
|
||||
|
||||
Deprecations and Removals
|
||||
*************************
|
||||
**Deprecations and Removals**
|
||||
|
||||
*
|
||||
`#1981 <https://github.com/sanic-org/sanic/pull/1981>`_
|
||||
Cleanup and remove deprecated code
|
||||
|
||||
Developer infrastructure
|
||||
************************
|
||||
**Developer infrastructure**
|
||||
|
||||
*
|
||||
`#1956 <https://github.com/sanic-org/sanic/pull/1956>`_
|
||||
@@ -426,8 +421,7 @@ Developer infrastructure
|
||||
`#1986 <https://github.com/sanic-org/sanic/pull/1986>`_
|
||||
Update tox requirements
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
*
|
||||
`#1951 <https://github.com/sanic-org/sanic/pull/1951>`_
|
||||
@@ -445,8 +439,7 @@ Improved Documentation
|
||||
Version 20.9.1
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1954 <https://github.com/sanic-org/sanic/pull/1954>`_
|
||||
@@ -459,8 +452,7 @@ Bugfixes
|
||||
Version 19.12.3
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1959 <https://github.com/sanic-org/sanic/pull/1959>`_
|
||||
@@ -471,8 +463,7 @@ Version 20.9.0
|
||||
---------------
|
||||
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1887 <https://github.com/sanic-org/sanic/pull/1887>`_
|
||||
@@ -499,22 +490,19 @@ Features
|
||||
`#1937 <https://github.com/sanic-org/sanic/pull/1937>`_
|
||||
Added auto, text, and json fallback error handlers (in v21.3, the default will change form html to auto)
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1897 <https://github.com/sanic-org/sanic/pull/1897>`_
|
||||
Resolves exception from unread bytes in stream
|
||||
|
||||
Deprecations and Removals
|
||||
*************************
|
||||
**Deprecations and Removals**
|
||||
|
||||
*
|
||||
`#1903 <https://github.com/sanic-org/sanic/pull/1903>`_
|
||||
config.from_envar, config.from_pyfile, and config.from_object are deprecated and set to be removed in v21.3
|
||||
|
||||
Developer infrastructure
|
||||
************************
|
||||
**Developer infrastructure**
|
||||
|
||||
*
|
||||
`#1890 <https://github.com/sanic-org/sanic/pull/1890>`_,
|
||||
@@ -529,8 +517,7 @@ Developer infrastructure
|
||||
`#1924 <https://github.com/sanic-org/sanic/pull/1924>`_
|
||||
Adding --strict-markers for pytest
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
*
|
||||
`#1922 <https://github.com/sanic-org/sanic/pull/1922>`_
|
||||
@@ -540,8 +527,7 @@ Improved Documentation
|
||||
Version 20.6.3
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1884 <https://github.com/sanic-org/sanic/pull/1884>`_
|
||||
@@ -551,8 +537,7 @@ Bugfixes
|
||||
Version 20.6.2
|
||||
---------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1641 <https://github.com/sanic-org/sanic/pull/1641>`_
|
||||
@@ -562,8 +547,7 @@ Features
|
||||
Version 20.6.1
|
||||
---------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1760 <https://github.com/sanic-org/sanic/pull/1760>`_
|
||||
@@ -577,8 +561,7 @@ Features
|
||||
`#1880 <https://github.com/sanic-org/sanic/pull/1880>`_
|
||||
Add handler names for websockets for url_for usage
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1776 <https://github.com/sanic-org/sanic/pull/1776>`_
|
||||
@@ -600,15 +583,13 @@ Bugfixes
|
||||
`#1853 <https://github.com/sanic-org/sanic/pull/1853>`_
|
||||
Fix pickle error when attempting to pickle an application which contains websocket routes
|
||||
|
||||
Deprecations and Removals
|
||||
*************************
|
||||
**Deprecations and Removals**
|
||||
|
||||
*
|
||||
`#1739 <https://github.com/sanic-org/sanic/pull/1739>`_
|
||||
Deprecate body_bytes to merge into body
|
||||
|
||||
Developer infrastructure
|
||||
************************
|
||||
**Developer infrastructure**
|
||||
|
||||
*
|
||||
`#1852 <https://github.com/sanic-org/sanic/pull/1852>`_
|
||||
@@ -623,8 +604,7 @@ Developer infrastructure
|
||||
Wrap run()'s "protocol" type annotation in Optional[]
|
||||
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
*
|
||||
`#1846 <https://github.com/sanic-org/sanic/pull/1846>`_
|
||||
@@ -638,14 +618,13 @@ Improved Documentation
|
||||
Version 20.6.0
|
||||
---------------
|
||||
|
||||
*Released, but unintentionally ommitting PR #1880, so was replaced by 20.6.1*
|
||||
*Released, but unintentionally omitting PR #1880, so was replaced by 20.6.1*
|
||||
|
||||
|
||||
Version 20.3.0
|
||||
---------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1762 <https://github.com/sanic-org/sanic/pull/1762>`_
|
||||
@@ -676,8 +655,7 @@ Features
|
||||
`#1820 <https://github.com/sanic-org/sanic/pull/1820>`_
|
||||
Do not set content-type and content-length headers in exceptions
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1748 <https://github.com/sanic-org/sanic/pull/1748>`_
|
||||
@@ -695,8 +673,7 @@ Bugfixes
|
||||
`#1808 <https://github.com/sanic-org/sanic/pull/1808>`_
|
||||
Fix Ctrl+C and tests on Windows
|
||||
|
||||
Deprecations and Removals
|
||||
*************************
|
||||
**Deprecations and Removals**
|
||||
|
||||
*
|
||||
`#1800 <https://github.com/sanic-org/sanic/pull/1800>`_
|
||||
@@ -714,8 +691,7 @@ Deprecations and Removals
|
||||
`#1818 <https://github.com/sanic-org/sanic/pull/1818>`_
|
||||
Complete deprecation of ``app.remove_route`` and ``request.raw_args``
|
||||
|
||||
Dependencies
|
||||
************
|
||||
**Dependencies**
|
||||
|
||||
*
|
||||
`#1794 <https://github.com/sanic-org/sanic/pull/1794>`_
|
||||
@@ -725,15 +701,13 @@ Dependencies
|
||||
`#1806 <https://github.com/sanic-org/sanic/pull/1806>`_
|
||||
Import ``ASGIDispatch`` from top-level ``httpx`` (from third-party deprecation)
|
||||
|
||||
Developer infrastructure
|
||||
************************
|
||||
**Developer infrastructure**
|
||||
|
||||
*
|
||||
`#1833 <https://github.com/sanic-org/sanic/pull/1833>`_
|
||||
Resolve broken documentation builds
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
*
|
||||
`#1755 <https://github.com/sanic-org/sanic/pull/1755>`_
|
||||
@@ -775,8 +749,7 @@ Improved Documentation
|
||||
Version 19.12.0
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
- Fix blueprint middleware application
|
||||
|
||||
@@ -795,8 +768,7 @@ Bugfixes
|
||||
due to an `AttributeError`. This fix makes the availability of `SERVER_NAME` on our `app.config` an optional behavior. (`#1707 <https://github.com/sanic-org/sanic/issues/1707>`__)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
- Move docs from MD to RST
|
||||
|
||||
@@ -810,8 +782,7 @@ Improved Documentation
|
||||
Version 19.6.3
|
||||
--------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
- Enable Towncrier Support
|
||||
|
||||
@@ -819,8 +790,7 @@ Features
|
||||
of generating and managing change logs as part of each of pull requests. (`#1631 <https://github.com/sanic-org/sanic/issues/1631>`__)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
- Documentation infrastructure changes
|
||||
|
||||
@@ -833,8 +803,7 @@ Improved Documentation
|
||||
Version 19.6.2
|
||||
--------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1562 <https://github.com/sanic-org/sanic/pull/1562>`_
|
||||
@@ -850,8 +819,7 @@ Features
|
||||
Add Configure support from object string
|
||||
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1587 <https://github.com/sanic-org/sanic/pull/1587>`_
|
||||
@@ -869,8 +837,7 @@ Bugfixes
|
||||
`#1594 <https://github.com/sanic-org/sanic/pull/1594>`_
|
||||
Strict Slashes behavior fix
|
||||
|
||||
Deprecations and Removals
|
||||
*************************
|
||||
**Deprecations and Removals**
|
||||
|
||||
*
|
||||
`#1544 <https://github.com/sanic-org/sanic/pull/1544>`_
|
||||
@@ -894,8 +861,7 @@ Deprecations and Removals
|
||||
Version 19.3
|
||||
------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1497 <https://github.com/sanic-org/sanic/pull/1497>`_
|
||||
@@ -963,8 +929,7 @@ Features
|
||||
|
||||
This is a breaking change.
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
|
||||
*
|
||||
@@ -1000,8 +965,7 @@ Bugfixes
|
||||
This allows the access log to be disabled for example when running via
|
||||
gunicorn.
|
||||
|
||||
Developer infrastructure
|
||||
************************
|
||||
**Developer infrastructure**
|
||||
|
||||
* `#1529 <https://github.com/sanic-org/sanic/pull/1529>`_ Update project PyPI credentials
|
||||
* `#1515 <https://github.com/sanic-org/sanic/pull/1515>`_ fix linter issue causing travis build failures (fix #1514)
|
||||
@@ -1009,8 +973,7 @@ Developer infrastructure
|
||||
* `#1478 <https://github.com/sanic-org/sanic/pull/1478>`_ Upgrade setuptools version and use native docutils in doc build
|
||||
* `#1464 <https://github.com/sanic-org/sanic/pull/1464>`_ Upgrade pytest, and fix caplog unit tests
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
* `#1516 <https://github.com/sanic-org/sanic/pull/1516>`_ Fix typo at the exception documentation
|
||||
* `#1510 <https://github.com/sanic-org/sanic/pull/1510>`_ fix typo in Asyncio example
|
||||
@@ -1071,21 +1034,19 @@ Version 18.12
|
||||
* Fix Range header handling for static files (#1402)
|
||||
* Fix the logger and make it work (#1397)
|
||||
* Fix type pikcle->pickle in multiprocessing test
|
||||
* Fix pickling blueprints Change the string passed in the "name" section of the namedtuples in Blueprint to match the name of the Blueprint module attribute name. This allows blueprints to be pickled and unpickled, without errors, which is a requirment of running Sanic in multiprocessing mode in Windows. Added a test for pickling and unpickling blueprints Added a test for pickling and unpickling sanic itself Added a test for enabling multiprocessing on an app with a blueprint (only useful to catch this bug if the tests are run on Windows).
|
||||
* Fix pickling blueprints Change the string passed in the "name" section of the namedtuples in Blueprint to match the name of the Blueprint module attribute name. This allows blueprints to be pickled and unpickled, without errors, which is a requirement of running Sanic in multiprocessing mode in Windows. Added a test for pickling and unpickling blueprints Added a test for pickling and unpickling sanic itself Added a test for enabling multiprocessing on an app with a blueprint (only useful to catch this bug if the tests are run on Windows).
|
||||
* Fix document for logging
|
||||
|
||||
Version 0.8
|
||||
-----------
|
||||
|
||||
0.8.3
|
||||
*****
|
||||
**0.8.3**
|
||||
|
||||
* Changes:
|
||||
|
||||
* Ownership changed to org 'sanic-org'
|
||||
|
||||
0.8.0
|
||||
*****
|
||||
**0.8.0**
|
||||
|
||||
* Changes:
|
||||
|
||||
@@ -1110,7 +1071,7 @@ Version 0.8
|
||||
* Content-length header on 204/304 responses (Arnulfo Solís)
|
||||
* Extend WebSocketProtocol arguments and add docs (Bob Olde Hampsink, yunstanford)
|
||||
* Update development status from pre-alpha to beta (Maksim Anisenkov)
|
||||
* KeepAlive Timout log level changed to debug (Arnulfo Solís)
|
||||
* KeepAlive Timeout log level changed to debug (Arnulfo Solís)
|
||||
* Pin pytest to 3.3.2 because of pytest-dev/pytest#3170 (Maksim Aniskenov)
|
||||
* Install Python 3.5 and 3.6 on docker container for tests (Shahin Azad)
|
||||
* Add support for blueprint groups and nesting (Elias Tarhini)
|
||||
@@ -1165,19 +1126,16 @@ Version 0.1
|
||||
-----------
|
||||
|
||||
|
||||
0.1.7
|
||||
*****
|
||||
**0.1.7**
|
||||
|
||||
* Reversed static url and directory arguments to meet spec
|
||||
|
||||
0.1.6
|
||||
*****
|
||||
**0.1.6**
|
||||
|
||||
* Static files
|
||||
* Lazy Cookie Loading
|
||||
|
||||
0.1.5
|
||||
*****
|
||||
**0.1.5**
|
||||
|
||||
* Cookies
|
||||
* Blueprint listeners and ordering
|
||||
@@ -1185,23 +1143,19 @@ Version 0.1
|
||||
* Fix: Incomplete file reads on medium+ sized post requests
|
||||
* Breaking: after_start and before_stop now pass sanic as their first argument
|
||||
|
||||
0.1.4
|
||||
*****
|
||||
**0.1.4**
|
||||
|
||||
* Multiprocessing
|
||||
|
||||
0.1.3
|
||||
*****
|
||||
**0.1.3**
|
||||
|
||||
* Blueprint support
|
||||
* Faster Response processing
|
||||
|
||||
0.1.1 - 0.1.2
|
||||
*************
|
||||
**0.1.1 - 0.1.2**
|
||||
|
||||
* Struggling to update pypi via CI
|
||||
|
||||
0.1.0
|
||||
*****
|
||||
**0.1.0**
|
||||
|
||||
* Released to public
|
||||
|
||||
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at sanic-maintainers@googlegroups.com. All
|
||||
reported by contacting the project team at adam@sanicframework.org. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
@@ -19,7 +19,7 @@ a virtual environment already set up, then run:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip3 install -e . ".[dev]"
|
||||
pip install -e ".[dev]"
|
||||
|
||||
Dependency Changes
|
||||
------------------
|
||||
@@ -71,9 +71,9 @@ To execute only unittests, run ``tox`` with environment like so:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
tox -e py36 -v -- tests/test_config.py
|
||||
# or
|
||||
tox -e py37 -v -- tests/test_config.py
|
||||
# or
|
||||
tox -e py310 -v -- tests/test_config.py
|
||||
|
||||
Run lint checks
|
||||
---------------
|
||||
@@ -140,6 +140,7 @@ To maintain the code consistency, Sanic uses following tools.
|
||||
#. `isort <https://github.com/timothycrosley/isort>`_
|
||||
#. `black <https://github.com/python/black>`_
|
||||
#. `flake8 <https://github.com/PyCQA/flake8>`_
|
||||
#. `slotscheck <https://github.com/ariebovenberg/slotscheck>`_
|
||||
|
||||
isort
|
||||
*****
|
||||
@@ -167,7 +168,13 @@ flake8
|
||||
#. pycodestyle
|
||||
#. Ned Batchelder's McCabe script
|
||||
|
||||
``isort``\ , ``black`` and ``flake8`` checks are performed during ``tox`` lint checks.
|
||||
slotscheck
|
||||
**********
|
||||
|
||||
``slotscheck`` ensures that there are no problems with ``__slots__``
|
||||
(e.g. overlaps, or missing slots in base classes).
|
||||
|
||||
``isort``\ , ``black``\ , ``flake8`` and ``slotscheck`` checks are performed during ``tox`` lint checks.
|
||||
|
||||
The **easiest** way to make your code conform is to run the following before committing.
|
||||
|
||||
|
||||
6
Makefile
6
Makefile
@@ -66,15 +66,15 @@ ifdef include_tests
|
||||
isort -rc sanic tests
|
||||
else
|
||||
$(info Sorting Imports)
|
||||
isort -rc sanic tests --profile=black
|
||||
isort -rc sanic tests
|
||||
endif
|
||||
endif
|
||||
|
||||
black:
|
||||
black --config ./.black.toml sanic tests
|
||||
black sanic tests
|
||||
|
||||
isort:
|
||||
isort sanic tests --profile=black
|
||||
isort sanic tests
|
||||
|
||||
pretty: black isort
|
||||
|
||||
|
||||
30
README.rst
30
README.rst
@@ -11,7 +11,7 @@ Sanic | Build fast. Run fast.
|
||||
:stub-columns: 1
|
||||
|
||||
* - Build
|
||||
- | |Py39Test| |Py38Test| |Py37Test| |Codecov|
|
||||
- | |Py310Test| |Py39Test| |Py38Test| |Py37Test|
|
||||
* - Docs
|
||||
- | |UserGuide| |Documentation|
|
||||
* - Package
|
||||
@@ -27,8 +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
|
||||
.. |Codecov| image:: https://codecov.io/gh/sanic-org/sanic/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/sanic-org/sanic
|
||||
.. |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
|
||||
@@ -66,7 +66,7 @@ Sanic | Build fast. Run fast.
|
||||
|
||||
Sanic is a **Python 3.7+** web server and web framework that's written to go fast. It allows the usage of the ``async/await`` syntax added in Python 3.5, which makes your code non-blocking and speedy.
|
||||
|
||||
Sanic is also ASGI compliant, so you can deploy it with an `alternative ASGI webserver <https://sanic.readthedocs.io/en/latest/sanic/deploying.html#running-via-asgi>`_.
|
||||
Sanic is also ASGI compliant, so you can deploy it with an `alternative ASGI webserver <https://sanicframework.org/en/guide/deployment/running.html#asgi>`_.
|
||||
|
||||
`Source code on GitHub <https://github.com/sanic-org/sanic/>`_ | `Help and discussion board <https://community.sanicframework.org/>`_ | `User Guide <https://sanicframework.org>`_ | `Chat on Discord <https://discord.gg/FARQzAEMAA>`_
|
||||
|
||||
@@ -77,17 +77,11 @@ The goal of the project is to provide a simple way to get up and running a highl
|
||||
Sponsor
|
||||
-------
|
||||
|
||||
|Try CodeStream|
|
||||
Check out `open collective <https://opencollective.com/sanic-org>`_ to learn more about helping to fund Sanic.
|
||||
|
||||
.. |Try CodeStream| image:: https://alt-images.codestream.com/codestream_logo_sanicorg.png
|
||||
:target: https://codestream.com/?utm_source=github&utm_campaign=sanicorg&utm_medium=banner
|
||||
:alt: Try CodeStream
|
||||
Thanks to `Linode <https://www.linode.com>`_ for their contribution towards the development and community of Sanic.
|
||||
|
||||
Manage pull requests and conduct code reviews in your IDE with full source-tree context. Comment on any line, not just the diffs. Use jump-to-definition, your favorite keybindings, and code intelligence with more of your workflow.
|
||||
|
||||
`Learn More <https://codestream.com/?utm_source=github&utm_campaign=sanicorg&utm_medium=banner>`_
|
||||
|
||||
Thank you to our sponsor. Check out `open collective <https://opencollective.com/sanic-org>`_ to learn more about helping to fund Sanic.
|
||||
|Linode|
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -108,9 +102,6 @@ Installation
|
||||
If you are running on a clean install of Fedora 28 or above, please make sure you have the ``redhat-rpm-config`` package installed in case if you want to
|
||||
use ``sanic`` with ``ujson`` dependency.
|
||||
|
||||
.. note::
|
||||
|
||||
Windows support is currently "experimental" and on a best-effort basis. Multiple workers are also not currently supported on Windows (see `Issue #1517 <https://github.com/sanic-org/sanic/issues/1517>`_), but setting ``workers=1`` should launch the server successfully.
|
||||
|
||||
Hello World Example
|
||||
-------------------
|
||||
@@ -120,7 +111,7 @@ Hello World Example
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
app = Sanic("My Hello, world app")
|
||||
app = Sanic("my-hello-world-app")
|
||||
|
||||
@app.route('/')
|
||||
async def test(request):
|
||||
@@ -172,3 +163,8 @@ Contribution
|
||||
------------
|
||||
|
||||
We are always happy to have new contributions. We have `marked issues good for anyone looking to get started <https://github.com/sanic-org/sanic/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner>`_, and welcome `questions on the forums <https://community.sanicframework.org/>`_. Please take a look at our `Contribution guidelines <https://github.com/sanic-org/sanic/blob/master/CONTRIBUTING.rst>`_.
|
||||
|
||||
.. |Linode| image:: https://www.linode.com/wp-content/uploads/2021/01/Linode-Logo-Black.svg
|
||||
:alt: Linode
|
||||
:target: https://www.linode.com
|
||||
:width: 200px
|
||||
|
||||
52
SECURITY.md
52
SECURITY.md
@@ -4,31 +4,41 @@
|
||||
|
||||
Sanic releases long term support release once a year in December. LTS releases receive bug and security updates for **24 months**. Interim releases throughout the year occur every three months, and are supported until the subsequent interim release.
|
||||
|
||||
| Version | LTS | Supported |
|
||||
| ------- | ------------- | ------------------ |
|
||||
| 20.12 | until 2022-12 | :heavy_check_mark: |
|
||||
| 20.9 | | :x: |
|
||||
| 20.6 | | :x: |
|
||||
| 20.3 | | :x: |
|
||||
| 19.12 | until 2021-12 | :white_check_mark: |
|
||||
| 19.9 | | :x: |
|
||||
| 19.6 | | :x: |
|
||||
| 19.3 | | :x: |
|
||||
| 18.12 | | :x: |
|
||||
| 0.8.3 | | :x: |
|
||||
| 0.7.0 | | :x: |
|
||||
| 0.6.0 | | :x: |
|
||||
| 0.5.4 | | :x: |
|
||||
| 0.4.1 | | :x: |
|
||||
| 0.3.1 | | :x: |
|
||||
| 0.2.0 | | :x: |
|
||||
| 0.1.9 | | :x: |
|
||||
|
||||
:white_check_mark: = security/bug fixes
|
||||
:heavy_check_mark: = full support
|
||||
| Version | LTS | Supported |
|
||||
| ------- | ------------- | ----------------------- |
|
||||
| 22.9 | | :white_check_mark: |
|
||||
| 22.6 | | :x: |
|
||||
| 22.3 | | :x: |
|
||||
| 21.12 | until 2023-12 | :white_check_mark: |
|
||||
| 21.9 | | :x: |
|
||||
| 21.6 | | :x: |
|
||||
| 21.3 | | :x: |
|
||||
| 20.12 | until 2022-12 | :ballot_box_with_check: |
|
||||
| 20.9 | | :x: |
|
||||
| 20.6 | | :x: |
|
||||
| 20.3 | | :x: |
|
||||
| 19.12 | | :x: |
|
||||
| 19.9 | | :x: |
|
||||
| 19.6 | | :x: |
|
||||
| 19.3 | | :x: |
|
||||
| 18.12 | | :x: |
|
||||
| 0.8.3 | | :x: |
|
||||
| 0.7.0 | | :x: |
|
||||
| 0.6.0 | | :x: |
|
||||
| 0.5.4 | | :x: |
|
||||
| 0.4.1 | | :x: |
|
||||
| 0.3.1 | | :x: |
|
||||
| 0.2.0 | | :x: |
|
||||
| 0.1.9 | | :x: |
|
||||
|
||||
:ballot_box_with_check: = security/bug fixes
|
||||
:white_check_mark: = full support
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover a security vulnerability, we ask that you **do not** create an issue on GitHub. Instead, please [send a message to the core-devs](https://community.sanicframework.org/g/core-devs) on the community forums. Once logged in, you can send a message to the core-devs by clicking the message button.
|
||||
|
||||
Alternatively, you can send a private message to Adam Hopkins on Discord. Find him on the [Sanic discord server](https://discord.gg/FARQzAEMAA).
|
||||
|
||||
This will help to not publicize the issue until the team can address it and resolve it.
|
||||
|
||||
27
codecov.yml
Normal file
27
codecov.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
coverage:
|
||||
status:
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0.75
|
||||
informational: true
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0.5
|
||||
precision: 3
|
||||
codecov:
|
||||
require_ci_to_pass: false
|
||||
ignore:
|
||||
- "sanic/__main__.py"
|
||||
- "sanic/compat.py"
|
||||
- "sanic/simple.py"
|
||||
- "sanic/utils.py"
|
||||
- "sanic/cli"
|
||||
- ".github/"
|
||||
- "changelogs/"
|
||||
- "docker/"
|
||||
- "docs/"
|
||||
- "examples/"
|
||||
- "scripts/"
|
||||
- "tests/"
|
||||
9
docs/_static/custom.css
vendored
9
docs/_static/custom.css
vendored
@@ -2,3 +2,12 @@
|
||||
.wy-nav-top {
|
||||
background: #444444;
|
||||
}
|
||||
|
||||
#changelog section {
|
||||
padding-left: 3rem;
|
||||
}
|
||||
|
||||
#changelog section h2,
|
||||
#changelog section h3 {
|
||||
margin-left: -3rem;
|
||||
}
|
||||
|
||||
24
docs/conf.py
24
docs/conf.py
@@ -10,10 +10,8 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add support for auto-doc
|
||||
import recommonmark
|
||||
|
||||
from recommonmark.transform import AutoStructify
|
||||
# Add support for auto-doc
|
||||
|
||||
|
||||
# Ensure that sanic is present in the path, to allow sphinx-apidoc to
|
||||
@@ -26,7 +24,11 @@ import sanic
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
extensions = ["sphinx.ext.autodoc", "recommonmark"]
|
||||
extensions = [
|
||||
"sphinx.ext.autodoc",
|
||||
"m2r2",
|
||||
"enum_tools.autoenum",
|
||||
]
|
||||
|
||||
templates_path = ["_templates"]
|
||||
|
||||
@@ -162,20 +164,6 @@ autodoc_default_options = {
|
||||
"member-order": "groupwise",
|
||||
}
|
||||
|
||||
|
||||
# app setup hook
|
||||
def setup(app):
|
||||
app.add_config_value(
|
||||
"recommonmark_config",
|
||||
{
|
||||
"enable_eval_rst": True,
|
||||
"enable_auto_doc_ref": False,
|
||||
},
|
||||
True,
|
||||
)
|
||||
app.add_transform(AutoStructify)
|
||||
|
||||
|
||||
html_theme_options = {
|
||||
"style_external_links": False,
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ API
|
||||
===
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 3
|
||||
|
||||
👥 User Guide <https://sanicframework.org/guide/>
|
||||
sanic/api_reference
|
||||
|
||||
@@ -15,3 +15,19 @@ sanic.config
|
||||
.. automodule:: sanic.config
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.application.constants
|
||||
---------------------------
|
||||
|
||||
.. automodule:: sanic.application.constants
|
||||
:exclude-members: StrEnum
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
sanic.application.state
|
||||
-----------------------
|
||||
|
||||
.. automodule:: sanic.application.state
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -17,6 +17,14 @@ sanic.handlers
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.headers
|
||||
--------------
|
||||
|
||||
.. automodule:: sanic.headers
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.request
|
||||
-------------
|
||||
|
||||
@@ -38,10 +46,3 @@ sanic.views
|
||||
.. automodule:: sanic.views
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.websocket
|
||||
---------------
|
||||
|
||||
.. automodule:: sanic.websocket
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -16,10 +16,3 @@ sanic.server
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.worker
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.worker
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
📜 Changelog
|
||||
============
|
||||
|
||||
.. mdinclude:: ./releases/22/22.9.md
|
||||
.. mdinclude:: ./releases/22/22.6.md
|
||||
.. mdinclude:: ./releases/22/22.3.md
|
||||
.. mdinclude:: ./releases/21/21.12.md
|
||||
.. mdinclude:: ./releases/21/21.9.md
|
||||
.. include:: ../../CHANGELOG.rst
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
♥️ Contributing
|
||||
==============
|
||||
===============
|
||||
|
||||
.. include:: ../../CONTRIBUTING.rst
|
||||
|
||||
66
docs/sanic/releases/21/21.12.md
Normal file
66
docs/sanic/releases/21/21.12.md
Normal file
@@ -0,0 +1,66 @@
|
||||
## Version 21.12.1 🔷
|
||||
|
||||
_Current LTS version_
|
||||
|
||||
- [#2349](https://github.com/sanic-org/sanic/pull/2349) Only display MOTD on startup
|
||||
- [#2354](https://github.com/sanic-org/sanic/pull/2354) Ignore name argument in Python 3.7
|
||||
- [#2355](https://github.com/sanic-org/sanic/pull/2355) Add config.update support for all config values
|
||||
|
||||
## Version 21.12.0 🔹
|
||||
|
||||
### Features
|
||||
- [#2260](https://github.com/sanic-org/sanic/pull/2260) Allow early Blueprint registrations to still apply later added objects
|
||||
- [#2262](https://github.com/sanic-org/sanic/pull/2262) Noisy exceptions - force logging of all exceptions
|
||||
- [#2264](https://github.com/sanic-org/sanic/pull/2264) Optional `uvloop` by configuration
|
||||
- [#2270](https://github.com/sanic-org/sanic/pull/2270) Vhost support using multiple TLS certificates
|
||||
- [#2277](https://github.com/sanic-org/sanic/pull/2277) Change signal routing for increased consistency
|
||||
- *BREAKING CHANGE*: If you were manually routing signals there is a breaking change. The signal router's `get` is no longer 100% determinative. There is now an additional step to loop thru the returned signals for proper matching on the requirements. If signals are being dispatched using `app.dispatch` or `bp.dispatch`, there is no change.
|
||||
- [#2290](https://github.com/sanic-org/sanic/pull/2290) Add contextual exceptions
|
||||
- [#2291](https://github.com/sanic-org/sanic/pull/2291) Increase join concat performance
|
||||
- [#2295](https://github.com/sanic-org/sanic/pull/2295), [#2316](https://github.com/sanic-org/sanic/pull/2316), [#2331](https://github.com/sanic-org/sanic/pull/2331) Restructure of CLI and application state with new displays and more command parity with `app.run`
|
||||
- [#2302](https://github.com/sanic-org/sanic/pull/2302) Add route context at definition time
|
||||
- [#2304](https://github.com/sanic-org/sanic/pull/2304) Named tasks and new API for managing background tasks
|
||||
- [#2307](https://github.com/sanic-org/sanic/pull/2307) On app auto-reload, provide insight of changed files
|
||||
- [#2308](https://github.com/sanic-org/sanic/pull/2308) Auto extend application with [Sanic Extensions](https://sanicframework.org/en/plugins/sanic-ext/getting-started.html) if it is installed, and provide first class support for accessing the extensions
|
||||
- [#2309](https://github.com/sanic-org/sanic/pull/2309) Builtin signals changed to `Enum`
|
||||
- [#2313](https://github.com/sanic-org/sanic/pull/2313) Support additional config implementation use case
|
||||
- [#2321](https://github.com/sanic-org/sanic/pull/2321) Refactor environment variable hydration logic
|
||||
- [#2327](https://github.com/sanic-org/sanic/pull/2327) Prevent sending multiple or mixed responses on a single request
|
||||
- [#2330](https://github.com/sanic-org/sanic/pull/2330) Custom type casting on environment variables
|
||||
- [#2332](https://github.com/sanic-org/sanic/pull/2332) Make all deprecation notices consistent
|
||||
- [#2335](https://github.com/sanic-org/sanic/pull/2335) Allow underscore to start instance names
|
||||
|
||||
### Bugfixes
|
||||
- [#2273](https://github.com/sanic-org/sanic/pull/2273) Replace assignation by typing for `websocket_handshake`
|
||||
- [#2285](https://github.com/sanic-org/sanic/pull/2285) Fix IPv6 display in startup logs
|
||||
- [#2299](https://github.com/sanic-org/sanic/pull/2299) Dispatch `http.lifecyle.response` from exception handler
|
||||
|
||||
### Deprecations and Removals
|
||||
- [#2306](https://github.com/sanic-org/sanic/pull/2306) Removal of deprecated items
|
||||
- `Sanic` and `Blueprint` may no longer have arbitrary properties attached to them
|
||||
- `Sanic` and `Blueprint` forced to have compliant names
|
||||
- alphanumeric + `_` + `-`
|
||||
- must start with letter or `_`
|
||||
- `load_env` keyword argument of `Sanic`
|
||||
- `sanic.exceptions.abort`
|
||||
- `sanic.views.CompositionView`
|
||||
- `sanic.response.StreamingHTTPResponse`
|
||||
- *NOTE:* the `stream()` response method (where you pass a callable streaming function) has been deprecated and will be removed in v22.6. You should upgrade all streaming responses to the new style: https://sanicframework.org/en/guide/advanced/streaming.html#response-streaming
|
||||
- [#2320](https://github.com/sanic-org/sanic/pull/2320) Remove app instance from Config for error handler setting
|
||||
|
||||
### Developer infrastructure
|
||||
- [#2251](https://github.com/sanic-org/sanic/pull/2251) Change dev install command
|
||||
- [#2286](https://github.com/sanic-org/sanic/pull/2286) Change codeclimate complexity threshold from 5 to 10
|
||||
- [#2287](https://github.com/sanic-org/sanic/pull/2287) Update host test function names so they are not overwritten
|
||||
- [#2292](https://github.com/sanic-org/sanic/pull/2292) Fail CI on error
|
||||
- [#2311](https://github.com/sanic-org/sanic/pull/2311), [#2324](https://github.com/sanic-org/sanic/pull/2324) Do not run tests for draft PRs
|
||||
- [#2336](https://github.com/sanic-org/sanic/pull/2336) Remove paths from coverage checks
|
||||
- [#2338](https://github.com/sanic-org/sanic/pull/2338) Cleanup ports on tests
|
||||
|
||||
### Improved Documentation
|
||||
- [#2269](https://github.com/sanic-org/sanic/pull/2269), [#2329](https://github.com/sanic-org/sanic/pull/2329), [#2333](https://github.com/sanic-org/sanic/pull/2333) Cleanup typos and fix language
|
||||
|
||||
### Miscellaneous
|
||||
- [#2257](https://github.com/sanic-org/sanic/pull/2257), [#2294](https://github.com/sanic-org/sanic/pull/2294), [#2341](https://github.com/sanic-org/sanic/pull/2341) Add Python 3.10 support
|
||||
- [#2279](https://github.com/sanic-org/sanic/pull/2279), [#2317](https://github.com/sanic-org/sanic/pull/2317), [#2322](https://github.com/sanic-org/sanic/pull/2322) Add/correct missing type annotations
|
||||
- [#2305](https://github.com/sanic-org/sanic/pull/2305) Fix examples to use modern implementations
|
||||
50
docs/sanic/releases/21/21.9.md
Normal file
50
docs/sanic/releases/21/21.9.md
Normal file
@@ -0,0 +1,50 @@
|
||||
## Version 21.9.3
|
||||
*Rerelease of v21.9.2 with some cleanup*
|
||||
|
||||
## Version 21.9.2
|
||||
- [#2268](https://github.com/sanic-org/sanic/pull/2268) Make HTTP connections start in IDLE stage, avoiding delays and error messages
|
||||
- [#2310](https://github.com/sanic-org/sanic/pull/2310) More consistent config setting with post-FALLBACK_ERROR_FORMAT apply
|
||||
|
||||
## Version 21.9.1
|
||||
- [#2259](https://github.com/sanic-org/sanic/pull/2259) Allow non-conforming ErrorHandlers
|
||||
|
||||
## Version 21.9.0
|
||||
|
||||
### Features
|
||||
- [#2158](https://github.com/sanic-org/sanic/pull/2158), [#2248](https://github.com/sanic-org/sanic/pull/2248) Complete overhaul of I/O to websockets
|
||||
- [#2160](https://github.com/sanic-org/sanic/pull/2160) Add new 17 signals into server and request lifecycles
|
||||
- [#2162](https://github.com/sanic-org/sanic/pull/2162) Smarter `auto` fallback formatting upon exception
|
||||
- [#2184](https://github.com/sanic-org/sanic/pull/2184) Introduce implementation for copying a Blueprint
|
||||
- [#2200](https://github.com/sanic-org/sanic/pull/2200) Accept header parsing
|
||||
- [#2207](https://github.com/sanic-org/sanic/pull/2207) Log remote address if available
|
||||
- [#2209](https://github.com/sanic-org/sanic/pull/2209) Add convenience methods to BP groups
|
||||
- [#2216](https://github.com/sanic-org/sanic/pull/2216) Add default messages to SanicExceptions
|
||||
- [#2225](https://github.com/sanic-org/sanic/pull/2225) Type annotation convenience for annotated handlers with path parameters
|
||||
- [#2236](https://github.com/sanic-org/sanic/pull/2236) Allow Falsey (but not-None) responses from route handlers
|
||||
- [#2238](https://github.com/sanic-org/sanic/pull/2238) Add `exception` decorator to Blueprint Groups
|
||||
- [#2244](https://github.com/sanic-org/sanic/pull/2244) Explicit static directive for serving file or dir (ex: `static(..., resource_type="file")`)
|
||||
- [#2245](https://github.com/sanic-org/sanic/pull/2245) Close HTTP loop when connection task cancelled
|
||||
|
||||
### Bugfixes
|
||||
- [#2188](https://github.com/sanic-org/sanic/pull/2188) Fix the handling of the end of a chunked request
|
||||
- [#2195](https://github.com/sanic-org/sanic/pull/2195) Resolve unexpected error handling on static requests
|
||||
- [#2208](https://github.com/sanic-org/sanic/pull/2208) Make blueprint-based exceptions attach and trigger in a more intuitive manner
|
||||
- [#2211](https://github.com/sanic-org/sanic/pull/2211) Fixed for handling exceptions of asgi app call
|
||||
- [#2213](https://github.com/sanic-org/sanic/pull/2213) Fix bug where ws exceptions not being logged
|
||||
- [#2231](https://github.com/sanic-org/sanic/pull/2231) Cleaner closing of tasks by using `abort()` in strategic places to avoid dangling sockets
|
||||
- [#2247](https://github.com/sanic-org/sanic/pull/2247) Fix logging of auto-reload status in debug mode
|
||||
- [#2246](https://github.com/sanic-org/sanic/pull/2246) Account for BP with exception handler but no routes
|
||||
|
||||
### Developer infrastructure
|
||||
- [#2194](https://github.com/sanic-org/sanic/pull/2194) HTTP unit tests with raw client
|
||||
- [#2199](https://github.com/sanic-org/sanic/pull/2199) Switch to codeclimate
|
||||
- [#2214](https://github.com/sanic-org/sanic/pull/2214) Try Reopening Windows Tests
|
||||
- [#2229](https://github.com/sanic-org/sanic/pull/2229) Refactor `HttpProtocol` into a base class
|
||||
- [#2230](https://github.com/sanic-org/sanic/pull/2230) Refactor `server.py` into multi-file module
|
||||
|
||||
### Miscellaneous
|
||||
- [#2173](https://github.com/sanic-org/sanic/pull/2173) Remove Duplicated Dependencies and PEP 517 Support
|
||||
- [#2193](https://github.com/sanic-org/sanic/pull/2193), [#2196](https://github.com/sanic-org/sanic/pull/2196), [#2217](https://github.com/sanic-org/sanic/pull/2217) Type annotation changes
|
||||
|
||||
|
||||
|
||||
52
docs/sanic/releases/22/22.3.md
Normal file
52
docs/sanic/releases/22/22.3.md
Normal file
@@ -0,0 +1,52 @@
|
||||
## Version 22.3.0
|
||||
|
||||
### Features
|
||||
- [#2347](https://github.com/sanic-org/sanic/pull/2347) API for multi-application server
|
||||
- 🚨 *BREAKING CHANGE*: The old `sanic.worker.GunicornWorker` has been **removed**. To run Sanic with `gunicorn`, you should use it thru `uvicorn` [as described in their docs](https://www.uvicorn.org/#running-with-gunicorn).
|
||||
- 🧁 *SIDE EFFECT*: Named background tasks are now supported, even in Python 3.7
|
||||
- [#2357](https://github.com/sanic-org/sanic/pull/2357) Parse `Authorization` header as `Request.credentials`
|
||||
- [#2361](https://github.com/sanic-org/sanic/pull/2361) Add config option to skip `Touchup` step in application startup
|
||||
- [#2372](https://github.com/sanic-org/sanic/pull/2372) Updates to CLI help messaging
|
||||
- [#2382](https://github.com/sanic-org/sanic/pull/2382) Downgrade warnings to backwater debug messages
|
||||
- [#2396](https://github.com/sanic-org/sanic/pull/2396) Allow for `multidict` v0.6
|
||||
- [#2401](https://github.com/sanic-org/sanic/pull/2401) Upgrade CLI catching for alternative application run types
|
||||
- [#2402](https://github.com/sanic-org/sanic/pull/2402) Conditionally inject CLI arguments into factory
|
||||
- [#2413](https://github.com/sanic-org/sanic/pull/2413) Add new start and stop event listeners to reloader process
|
||||
- [#2414](https://github.com/sanic-org/sanic/pull/2414) Remove loop as required listener arg
|
||||
- [#2415](https://github.com/sanic-org/sanic/pull/2415) Better exception for bad URL parsing
|
||||
- [sanic-routing#47](https://github.com/sanic-org/sanic-routing/pull/47) Add a new extention parameter type: `<file:ext>`, `<file:ext=jpg>`, `<file:ext=jpg|png|gif|svg>`, `<file=int:ext>`, `<file=int:ext=jpg|png|gif|svg>`, `<file=float:ext=tar.gz>`
|
||||
- 👶 *BETA FEATURE*: This feature will not work with `path` type matching, and is being released as a beta feature only.
|
||||
- [sanic-routing#57](https://github.com/sanic-org/sanic-routing/pull/57) Change `register_pattern` to accept a `str` or `Pattern`
|
||||
- [sanic-routing#58](https://github.com/sanic-org/sanic-routing/pull/58) Default matching on non-empty strings only, and new `strorempty` pattern type
|
||||
- 🚨 *BREAKING CHANGE*: Previously a route with a dynamic string parameter (`/<foo>` or `/<foo:str>`) would match on any string, including empty strings. It will now **only** match a non-empty string. To retain the old behavior, you should use the new parameter type: `/<foo:strorempty>`.
|
||||
|
||||
### Bugfixes
|
||||
- [#2373](https://github.com/sanic-org/sanic/pull/2373) Remove `error_logger` on websockets
|
||||
- [#2381](https://github.com/sanic-org/sanic/pull/2381) Fix newly assigned `None` in task registry
|
||||
- [sanic-routing#52](https://github.com/sanic-org/sanic-routing/pull/52) Add type casting to regex route matching
|
||||
- [sanic-routing#60](https://github.com/sanic-org/sanic-routing/pull/60) Add requirements check on regex routes (this resolves, for example, multiple static directories with differing `host` values)
|
||||
|
||||
### Deprecations and Removals
|
||||
- [#2362](https://github.com/sanic-org/sanic/pull/2362) 22.3 Deprecations and changes
|
||||
1. `debug=True` and `--debug` do _NOT_ automatically run `auto_reload`
|
||||
2. Default error render is with plain text (browsers still get HTML by default because `auto` looks at headers)
|
||||
3. `config` is required for `ErrorHandler.finalize`
|
||||
4. `ErrorHandler.lookup` requires two positional args
|
||||
5. Unused websocket protocol args removed
|
||||
- [#2344](https://github.com/sanic-org/sanic/pull/2344) Deprecate loading of lowercase environment variables
|
||||
|
||||
### Developer infrastructure
|
||||
- [#2363](https://github.com/sanic-org/sanic/pull/2363) Revert code coverage back to Codecov
|
||||
- [#2405](https://github.com/sanic-org/sanic/pull/2405) Upgrade tests for `sanic-routing` changes
|
||||
- [sanic-testing#35](https://github.com/sanic-org/sanic-testing/pull/35) Allow for httpx v0.22
|
||||
|
||||
### Improved Documentation
|
||||
- [#2350](https://github.com/sanic-org/sanic/pull/2350) Fix link in README for ASGI
|
||||
- [#2398](https://github.com/sanic-org/sanic/pull/2398) Document middleware on_request and on_response
|
||||
- [#2409](https://github.com/sanic-org/sanic/pull/2409) Add missing documentation for `Request.respond`
|
||||
|
||||
### Miscellaneous
|
||||
- [#2376](https://github.com/sanic-org/sanic/pull/2376) Fix typing for `ListenerMixin.listener`
|
||||
- [#2383](https://github.com/sanic-org/sanic/pull/2383) Clear deprecation warning in `asyncio.wait`
|
||||
- [#2387](https://github.com/sanic-org/sanic/pull/2387) Cleanup `__slots__` implementations
|
||||
- [#2390](https://github.com/sanic-org/sanic/pull/2390) Clear deprecation warning in `asyncio.get_event_loop`
|
||||
54
docs/sanic/releases/22/22.6.md
Normal file
54
docs/sanic/releases/22/22.6.md
Normal file
@@ -0,0 +1,54 @@
|
||||
## Version 22.6.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- [#2522](https://github.com/sanic-org/sanic/pull/2522) Always show server location in ASGI
|
||||
|
||||
## Version 22.6.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- [#2477](https://github.com/sanic-org/sanic/pull/2477) Sanic static directory fails when folder name ends with ".."
|
||||
|
||||
|
||||
## Version 22.6.0
|
||||
|
||||
### Features
|
||||
- [#2378](https://github.com/sanic-org/sanic/pull/2378) Introduce HTTP/3 and autogeneration of TLS certificates in `DEBUG` mode
|
||||
- 👶 *EARLY RELEASE FEATURE*: Serving Sanic over HTTP/3 is an early release feature. It does not yet fully cover the HTTP/3 spec, but instead aims for feature parity with Sanic's existing HTTP/1.1 server. Websockets, WebTransport, push responses are examples of some features not yet implemented.
|
||||
- 📦 *EXTRA REQUIREMENT*: Not all HTTP clients are capable of interfacing with HTTP/3 servers. You may need to install a [HTTP/3 capable client](https://curl.se/docs/http3.html).
|
||||
- 📦 *EXTRA REQUIREMENT*: In order to use TLS autogeneration, you must install either [mkcert](https://github.com/FiloSottile/mkcert) or [trustme](https://github.com/python-trio/trustme).
|
||||
- [#2416](https://github.com/sanic-org/sanic/pull/2416) Add message to `task.cancel`
|
||||
- [#2420](https://github.com/sanic-org/sanic/pull/2420) Add exception aliases for more consistent naming with standard HTTP response types (`BadRequest`, `MethodNotAllowed`, `RangeNotSatisfiable`)
|
||||
- [#2432](https://github.com/sanic-org/sanic/pull/2432) Expose ASGI `scope` as a property on the `Request` object
|
||||
- [#2438](https://github.com/sanic-org/sanic/pull/2438) Easier access to websocket class for annotation: `from sanic import Websocket`
|
||||
- [#2439](https://github.com/sanic-org/sanic/pull/2439) New API for reading form values with options: `Request.get_form`
|
||||
- [#2445](https://github.com/sanic-org/sanic/pull/2445) Add custom `loads` function
|
||||
- [#2447](https://github.com/sanic-org/sanic/pull/2447), [#2486](https://github.com/sanic-org/sanic/pull/2486) Improved API to support setting cache control headers
|
||||
- [#2453](https://github.com/sanic-org/sanic/pull/2453) Move verbosity filtering to logger
|
||||
- [#2475](https://github.com/sanic-org/sanic/pull/2475) Expose getter for current request using `Request.get_current()`
|
||||
|
||||
### Bugfixes
|
||||
- [#2448](https://github.com/sanic-org/sanic/pull/2448) Fix to allow running with `pythonw.exe` or places where there is no `sys.stdout`
|
||||
- [#2451](https://github.com/sanic-org/sanic/pull/2451) Trigger `http.lifecycle.request` signal in ASGI mode
|
||||
- [#2455](https://github.com/sanic-org/sanic/pull/2455) Resolve typing of stacked route definitions
|
||||
- [#2463](https://github.com/sanic-org/sanic/pull/2463) Properly catch websocket CancelledError in websocket handler in Python 3.7
|
||||
|
||||
### Deprecations and Removals
|
||||
- [#2487](https://github.com/sanic-org/sanic/pull/2487) v22.6 deprecations and changes
|
||||
1. Optional application registry
|
||||
1. Execution of custom handlers after some part of response was sent
|
||||
1. Configuring fallback handlers on the `ErrorHandler`
|
||||
1. Custom `LOGO` setting
|
||||
1. `sanic.response.stream`
|
||||
1. `AsyncioServer.init`
|
||||
|
||||
### Developer infrastructure
|
||||
- [#2449](https://github.com/sanic-org/sanic/pull/2449) Clean up `black` and `isort` config
|
||||
- [#2479](https://github.com/sanic-org/sanic/pull/2479) Fix some flappy tests
|
||||
|
||||
### Improved Documentation
|
||||
- [#2461](https://github.com/sanic-org/sanic/pull/2461) Update example to match current application naming standards
|
||||
- [#2466](https://github.com/sanic-org/sanic/pull/2466) Better type annotation for `Extend`
|
||||
- [#2485](https://github.com/sanic-org/sanic/pull/2485) Improved help messages in CLI
|
||||
|
||||
47
docs/sanic/releases/22/22.9.md
Normal file
47
docs/sanic/releases/22/22.9.md
Normal file
@@ -0,0 +1,47 @@
|
||||
## Version 22.9.0 🔶
|
||||
|
||||
_Current version_
|
||||
|
||||
### Features
|
||||
|
||||
- [#2445](https://github.com/sanic-org/sanic/pull/2445) Add custom loads function
|
||||
- [#2490](https://github.com/sanic-org/sanic/pull/2490) Make `WebsocketImplProtocol` async iterable
|
||||
- [#2499](https://github.com/sanic-org/sanic/pull/2499) Sanic Server WorkerManager refactor
|
||||
- [#2506](https://github.com/sanic-org/sanic/pull/2506) Use `pathlib` for path resolution (for static file serving)
|
||||
- [#2508](https://github.com/sanic-org/sanic/pull/2508) Use `path.parts` instead of `match` (for static file serving)
|
||||
- [#2513](https://github.com/sanic-org/sanic/pull/2513) Better request cancel handling
|
||||
- [#2516](https://github.com/sanic-org/sanic/pull/2516) Add request properties for HTTP method info:
|
||||
- `request.is_safe`
|
||||
- `request.is_idempotent`
|
||||
- `request.is_cacheable`
|
||||
- *See* [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) *for more information about when these apply*
|
||||
- [#2522](https://github.com/sanic-org/sanic/pull/2522) Always show server location in ASGI
|
||||
- [#2526](https://github.com/sanic-org/sanic/pull/2526) Cache control support for static files for returning 304 when appropriate
|
||||
- [#2533](https://github.com/sanic-org/sanic/pull/2533) Refactor `_static_request_handler`
|
||||
- [#2540](https://github.com/sanic-org/sanic/pull/2540) Add signals before and after handler execution
|
||||
- `http.handler.before`
|
||||
- `http.handler.after`
|
||||
- [#2542](https://github.com/sanic-org/sanic/pull/2542) Add *[redacted]* to CLI :)
|
||||
- [#2546](https://github.com/sanic-org/sanic/pull/2546) Add deprecation warning filter
|
||||
- [#2550](https://github.com/sanic-org/sanic/pull/2550) Middleware priority and performance enhancements
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- [#2495](https://github.com/sanic-org/sanic/pull/2495) Prevent directory traversion with static files
|
||||
- [#2515](https://github.com/sanic-org/sanic/pull/2515) Do not apply double slash to paths in certain static dirs in Blueprints
|
||||
|
||||
### Deprecations and Removals
|
||||
|
||||
- [#2525](https://github.com/sanic-org/sanic/pull/2525) Warn on duplicate route names, will be prevented outright in v23.3
|
||||
- [#2537](https://github.com/sanic-org/sanic/pull/2537) Raise warning and deprecation notice on duplicate exceptions, will be prevented outright in v23.3
|
||||
|
||||
### Developer infrastructure
|
||||
|
||||
- [#2504](https://github.com/sanic-org/sanic/pull/2504) Cleanup test suite
|
||||
- [#2505](https://github.com/sanic-org/sanic/pull/2505) Replace Unsupported Python Version Number from the Contributing Doc
|
||||
- [#2530](https://github.com/sanic-org/sanic/pull/2530) Do not include tests folder in installed package resolver
|
||||
|
||||
### Improved Documentation
|
||||
|
||||
- [#2502](https://github.com/sanic-org/sanic/pull/2502) Fix a few typos
|
||||
- [#2517](https://github.com/sanic-org/sanic/pull/2517) [#2536](https://github.com/sanic-org/sanic/pull/2536) Add some type hints
|
||||
@@ -4,12 +4,14 @@ import asyncio
|
||||
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic()
|
||||
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
async def notify_server_started_after_five_seconds():
|
||||
await asyncio.sleep(5)
|
||||
print('Server successfully started!')
|
||||
print("Server successfully started!")
|
||||
|
||||
|
||||
app.add_task(notify_server_started_after_five_seconds())
|
||||
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
from sanic import Sanic
|
||||
from sanic.response import text
|
||||
from random import randint
|
||||
|
||||
app = Sanic()
|
||||
from sanic import Sanic
|
||||
from sanic.response import text
|
||||
|
||||
|
||||
@app.middleware('request')
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.middleware("request")
|
||||
def append_request(request):
|
||||
# Add new key with random value
|
||||
request['num'] = randint(0, 100)
|
||||
request.ctx.num = randint(0, 100)
|
||||
|
||||
|
||||
@app.get('/pop')
|
||||
@app.get("/pop")
|
||||
def pop_handler(request):
|
||||
# Pop key from request object
|
||||
num = request.pop('num')
|
||||
return text(num)
|
||||
return text(request.ctx.num)
|
||||
|
||||
|
||||
@app.get('/key_exist')
|
||||
@app.get("/key_exist")
|
||||
def key_exist_handler(request):
|
||||
# Check the key is exist or not
|
||||
if 'num' in request:
|
||||
return text('num exist in request')
|
||||
if hasattr(request.ctx, "num"):
|
||||
return text("num exist in request")
|
||||
|
||||
return text('num does not exist in reqeust')
|
||||
return text("num does not exist in request")
|
||||
|
||||
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from sanic import Sanic
|
||||
from functools import wraps
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
app = Sanic()
|
||||
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
def check_request_for_authorization_status(request):
|
||||
@@ -27,14 +29,16 @@ def authorized(f):
|
||||
return response
|
||||
else:
|
||||
# the user is not authorized.
|
||||
return json({'status': 'not_authorized'}, 403)
|
||||
return json({"status": "not_authorized"}, 403)
|
||||
|
||||
return decorated_function
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@authorized
|
||||
async def test(request):
|
||||
return json({'status': 'authorized'})
|
||||
return json({"status": "authorized"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -1,43 +1,53 @@
|
||||
from sanic import Sanic, Blueprint
|
||||
from sanic import Blueprint, Sanic
|
||||
from sanic.response import text
|
||||
'''
|
||||
Demonstrates that blueprint request middleware are executed in the order they
|
||||
|
||||
|
||||
"""
|
||||
Demonstrates that blueprint request middleware are executed in the order they
|
||||
are added. And blueprint response middleware are executed in _reverse_ order.
|
||||
On a valid request, it should print "1 2 3 6 5 4" to terminal
|
||||
'''
|
||||
"""
|
||||
|
||||
app = Sanic(__name__)
|
||||
app = Sanic("Example")
|
||||
|
||||
bp = Blueprint("bp_"+__name__)
|
||||
bp = Blueprint("bp_example")
|
||||
|
||||
@bp.middleware('request')
|
||||
|
||||
@bp.on_request
|
||||
def request_middleware_1(request):
|
||||
print('1')
|
||||
print("1")
|
||||
|
||||
@bp.middleware('request')
|
||||
|
||||
@bp.on_request
|
||||
def request_middleware_2(request):
|
||||
print('2')
|
||||
print("2")
|
||||
|
||||
@bp.middleware('request')
|
||||
|
||||
@bp.on_request
|
||||
def request_middleware_3(request):
|
||||
print('3')
|
||||
print("3")
|
||||
|
||||
@bp.middleware('response')
|
||||
|
||||
@bp.on_response
|
||||
def resp_middleware_4(request, response):
|
||||
print('4')
|
||||
print("4")
|
||||
|
||||
@bp.middleware('response')
|
||||
|
||||
@bp.on_response
|
||||
def resp_middleware_5(request, response):
|
||||
print('5')
|
||||
print("5")
|
||||
|
||||
@bp.middleware('response')
|
||||
|
||||
@bp.on_response
|
||||
def resp_middleware_6(request, response):
|
||||
print('6')
|
||||
print("6")
|
||||
|
||||
@bp.route('/')
|
||||
|
||||
@bp.route("/")
|
||||
def pop_handler(request):
|
||||
return text('hello world')
|
||||
return text("hello world")
|
||||
|
||||
app.blueprint(bp, url_prefix='/bp')
|
||||
|
||||
app.blueprint(bp, url_prefix="/bp")
|
||||
|
||||
app.run(host="0.0.0.0", port=8000, debug=True, auto_reload=False)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from sanic import Blueprint, Sanic
|
||||
from sanic.response import file, json
|
||||
|
||||
app = Sanic(__name__)
|
||||
blueprint = Blueprint("name", url_prefix="/my_blueprint")
|
||||
blueprint2 = Blueprint("name2", url_prefix="/my_blueprint2")
|
||||
blueprint3 = Blueprint("name3", url_prefix="/my_blueprint3")
|
||||
|
||||
app = Sanic("Example")
|
||||
blueprint = Blueprint("bp_example", url_prefix="/my_blueprint")
|
||||
blueprint2 = Blueprint("bp_example2", url_prefix="/my_blueprint2")
|
||||
blueprint3 = Blueprint("bp_example3", url_prefix="/my_blueprint3")
|
||||
|
||||
|
||||
@blueprint.route("/foo")
|
||||
|
||||
@@ -2,17 +2,21 @@ from asyncio import sleep
|
||||
|
||||
from sanic import Sanic, response
|
||||
|
||||
app = Sanic(__name__, strict_slashes=True)
|
||||
|
||||
app = Sanic("DelayedResponseApp", strict_slashes=True)
|
||||
app.config.AUTO_EXTEND = False
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def handler(request):
|
||||
return response.redirect("/sleep/3")
|
||||
|
||||
@app.get("/sleep/<t:number>")
|
||||
|
||||
@app.get("/sleep/<t:float>")
|
||||
async def handler2(request, t=0.3):
|
||||
await sleep(t)
|
||||
return response.text(f"Slept {t:.1f} seconds.\n")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -7,8 +7,10 @@ and pass in an instance of it when we create our Sanic instance. Inside this
|
||||
class' default handler, we can do anything including sending exceptions to
|
||||
an external service.
|
||||
"""
|
||||
from sanic.handlers import ErrorHandler
|
||||
from sanic.exceptions import SanicException
|
||||
from sanic.handlers import ErrorHandler
|
||||
|
||||
|
||||
"""
|
||||
Imports and code relevant for our CustomHandler class
|
||||
(Ordinarily this would be in a separate file)
|
||||
@@ -16,7 +18,6 @@ Imports and code relevant for our CustomHandler class
|
||||
|
||||
|
||||
class CustomHandler(ErrorHandler):
|
||||
|
||||
def default(self, request, exception):
|
||||
# Here, we have access to the exception object
|
||||
# and can do anything with it (log, send to external service, etc)
|
||||
@@ -38,17 +39,17 @@ server's error_handler to an instance of our CustomHandler
|
||||
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
handler = CustomHandler()
|
||||
app.error_handler = handler
|
||||
app = Sanic("Example", error_handler=handler)
|
||||
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
# Here, something occurs which causes an unexpected exception
|
||||
# This exception will flow to our custom handler.
|
||||
raise SanicException('You Broke It!')
|
||||
raise SanicException("You Broke It!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from sanic import Sanic
|
||||
from sanic import response
|
||||
from sanic import Sanic, response
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@@ -9,5 +9,5 @@ async def test(request):
|
||||
return response.json({"test": True})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
@@ -1,4 +1,6 @@
|
||||
from sanic import Sanic, response, text
|
||||
from sanic.handlers import ErrorHandler
|
||||
from sanic.server.async_server import AsyncioServer
|
||||
|
||||
|
||||
HTTP_PORT = 9999
|
||||
@@ -32,20 +34,40 @@ def proxy(request, path):
|
||||
return response.redirect(url)
|
||||
|
||||
|
||||
@https.listener("main_process_start")
|
||||
@https.main_process_start
|
||||
async def start(app, _):
|
||||
global http
|
||||
app.http_server = await http.create_server(
|
||||
http_server = await http.create_server(
|
||||
port=HTTP_PORT, return_asyncio_server=True
|
||||
)
|
||||
app.http_server.after_start()
|
||||
app.add_task(runner(http, http_server))
|
||||
app.ctx.http_server = http_server
|
||||
app.ctx.http = http
|
||||
|
||||
|
||||
@https.listener("main_process_stop")
|
||||
@https.main_process_stop
|
||||
async def stop(app, _):
|
||||
app.http_server.before_stop()
|
||||
await app.http_server.close()
|
||||
app.http_server.after_stop()
|
||||
await app.ctx.http_server.before_stop()
|
||||
await app.ctx.http_server.close()
|
||||
for connection in app.ctx.http_server.connections:
|
||||
connection.close_if_idle()
|
||||
await app.ctx.http_server.after_stop()
|
||||
app.ctx.http = False
|
||||
|
||||
|
||||
async def runner(app: Sanic, app_server: AsyncioServer):
|
||||
app.is_running = True
|
||||
try:
|
||||
app.signalize()
|
||||
app.finalize()
|
||||
ErrorHandler.finalize(app.error_handler)
|
||||
app_server.init = True
|
||||
|
||||
await app_server.before_start()
|
||||
await app_server.after_start()
|
||||
await app_server.serve_forever()
|
||||
finally:
|
||||
app.is_running = False
|
||||
app.is_stopping = True
|
||||
|
||||
|
||||
https.run(port=HTTPS_PORT, debug=True)
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
app = Sanic(__name__)
|
||||
app = Sanic("Example")
|
||||
|
||||
sem = None
|
||||
|
||||
|
||||
@app.listener('before_server_start')
|
||||
def init(sanic, loop):
|
||||
@app.before_server_start
|
||||
def init(sanic, _):
|
||||
global sem
|
||||
concurrency_per_worker = 4
|
||||
sem = asyncio.Semaphore(concurrency_per_worker, loop=loop)
|
||||
sem = asyncio.Semaphore(concurrency_per_worker)
|
||||
|
||||
|
||||
async def bounded_fetch(session, url):
|
||||
"""
|
||||
Use session object to perform 'get' request on url
|
||||
"""
|
||||
async with sem, session.get(url) as response:
|
||||
return await response.json()
|
||||
async with sem:
|
||||
response = await session.get(url)
|
||||
return response.json()
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@@ -28,9 +32,9 @@ async def test(request):
|
||||
"""
|
||||
Download and serve example JSON
|
||||
"""
|
||||
url = "https://api.github.com/repos/channelcat/sanic"
|
||||
url = "https://api.github.com/repos/sanic-org/sanic"
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with httpx.AsyncClient() as session:
|
||||
response = await bounded_fetch(session, url)
|
||||
return json(response)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
|
||||
import aiotask_context as context
|
||||
from contextvars import ContextVar
|
||||
|
||||
from sanic import Sanic, response
|
||||
|
||||
@@ -11,8 +11,8 @@ log = logging.getLogger(__name__)
|
||||
class RequestIdFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
try:
|
||||
record.request_id = context.get("X-Request-ID")
|
||||
except ValueError:
|
||||
record.request_id = app.ctx.request_id.get(None) or "n/a"
|
||||
except AttributeError:
|
||||
record.request_id = "n/a"
|
||||
return True
|
||||
|
||||
@@ -44,13 +44,12 @@ LOG_SETTINGS = {
|
||||
}
|
||||
|
||||
|
||||
app = Sanic(__name__, log_config=LOG_SETTINGS)
|
||||
app = Sanic("Example", log_config=LOG_SETTINGS)
|
||||
|
||||
|
||||
@app.on_request
|
||||
async def set_request_id(request):
|
||||
request_id = request.id
|
||||
context.set("X-Request-ID", request_id)
|
||||
request.app.ctx.request_id.set(request.id)
|
||||
log.info(f"Setting {request.id=}")
|
||||
|
||||
|
||||
@@ -61,14 +60,14 @@ async def set_request_header(request, response):
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
log.debug("X-Request-ID: %s", context.get("X-Request-ID"))
|
||||
log.debug("X-Request-ID: %s", request.id)
|
||||
log.info("Hello from test!")
|
||||
return response.json({"test": True})
|
||||
|
||||
|
||||
@app.before_server_start
|
||||
def setup(app, loop):
|
||||
loop.set_task_factory(context.task_factory)
|
||||
app.ctx.request_id = ContextVar("request_id")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from os import getenv
|
||||
from platform import node
|
||||
from uuid import getnode as get_mac
|
||||
@@ -7,10 +8,11 @@ from uuid import getnode as get_mac
|
||||
from logdna import LogDNAHandler
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
from sanic.request import Request
|
||||
from sanic.response import json
|
||||
|
||||
log = logging.getLogger('logdna')
|
||||
|
||||
log = logging.getLogger("logdna")
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
|
||||
@@ -30,16 +32,18 @@ logdna_options = {
|
||||
"index_meta": True,
|
||||
"hostname": node(),
|
||||
"ip": get_my_ip_address(),
|
||||
"mac": get_mac_address()
|
||||
"mac": get_mac_address(),
|
||||
}
|
||||
|
||||
logdna_handler = LogDNAHandler(getenv("LOGDNA_API_KEY"), options=logdna_options)
|
||||
logdna_handler = LogDNAHandler(
|
||||
getenv("LOGDNA_API_KEY"), options=logdna_options
|
||||
)
|
||||
|
||||
logdna = logging.getLogger(__name__)
|
||||
logdna.setLevel(logging.INFO)
|
||||
logdna.addHandler(logdna_handler)
|
||||
|
||||
app = Sanic(__name__)
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.middleware
|
||||
@@ -49,13 +53,8 @@ def log_request(request: Request):
|
||||
|
||||
@app.route("/")
|
||||
def default(request):
|
||||
return json({
|
||||
"response": "I was here"
|
||||
})
|
||||
return json({"response": "I was here"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(
|
||||
host="0.0.0.0",
|
||||
port=getenv("PORT", 8080)
|
||||
)
|
||||
app.run(host="0.0.0.0", port=getenv("PORT", 8080))
|
||||
|
||||
@@ -2,27 +2,29 @@
|
||||
Modify header or status in response
|
||||
"""
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic import response
|
||||
|
||||
app = Sanic(__name__)
|
||||
from sanic import Sanic, response
|
||||
|
||||
|
||||
@app.route('/')
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def handle_request(request):
|
||||
return response.json(
|
||||
{'message': 'Hello world!'},
|
||||
headers={'X-Served-By': 'sanic'},
|
||||
status=200
|
||||
{"message": "Hello world!"},
|
||||
headers={"X-Served-By": "sanic"},
|
||||
status=200,
|
||||
)
|
||||
|
||||
|
||||
@app.route('/unauthorized')
|
||||
@app.route("/unauthorized")
|
||||
def handle_request(request):
|
||||
return response.json(
|
||||
{'message': 'You are not authorized'},
|
||||
headers={'X-Served-By': 'sanic'},
|
||||
status=404
|
||||
{"message": "You are not authorized"},
|
||||
headers={"X-Served-By": "sanic"},
|
||||
status=404,
|
||||
)
|
||||
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
@@ -32,7 +32,7 @@ def test_port(worker_id):
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def app():
|
||||
app = Sanic()
|
||||
app = Sanic("Example")
|
||||
|
||||
@app.route("/")
|
||||
async def index(request):
|
||||
|
||||
@@ -8,7 +8,6 @@ from sanic.handlers import ErrorHandler
|
||||
|
||||
|
||||
class RaygunExceptionReporter(ErrorHandler):
|
||||
|
||||
def __init__(self, raygun_api_key=None):
|
||||
super().__init__()
|
||||
if raygun_api_key is None:
|
||||
@@ -22,16 +21,13 @@ class RaygunExceptionReporter(ErrorHandler):
|
||||
|
||||
|
||||
raygun_error_reporter = RaygunExceptionReporter()
|
||||
app = Sanic(__name__, error_handler=raygun_error_reporter)
|
||||
app = Sanic("Example", error_handler=raygun_error_reporter)
|
||||
|
||||
|
||||
@app.route("/raise")
|
||||
async def test(request):
|
||||
raise SanicException('You Broke It!')
|
||||
raise SanicException("You Broke It!")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(
|
||||
host="0.0.0.0",
|
||||
port=getenv("PORT", 8080)
|
||||
)
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=getenv("PORT", 8080))
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
from sanic import Sanic
|
||||
from sanic import response
|
||||
from sanic import Sanic, response
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def handle_request(request):
|
||||
return response.redirect('/redirect')
|
||||
return response.redirect("/redirect")
|
||||
|
||||
|
||||
@app.route('/redirect')
|
||||
@app.route("/redirect")
|
||||
async def test(request):
|
||||
return response.json({"Redirected": True})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -1,65 +1,63 @@
|
||||
from sanic import Sanic
|
||||
from sanic.views import CompositionView
|
||||
from sanic.views import HTTPMethodView
|
||||
from sanic.views import stream as stream_decorator
|
||||
from sanic.blueprints import Blueprint
|
||||
from sanic.response import stream, text
|
||||
from sanic.views import HTTPMethodView
|
||||
from sanic.views import stream as stream_decorator
|
||||
|
||||
bp = Blueprint('blueprint_request_stream')
|
||||
app = Sanic('request_stream')
|
||||
|
||||
bp = Blueprint("bp_example")
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
class SimpleView(HTTPMethodView):
|
||||
|
||||
@stream_decorator
|
||||
async def post(self, request):
|
||||
result = ''
|
||||
result = ""
|
||||
while True:
|
||||
body = await request.stream.get()
|
||||
if body is None:
|
||||
break
|
||||
result += body.decode('utf-8')
|
||||
result += body.decode("utf-8")
|
||||
return text(result)
|
||||
|
||||
|
||||
@app.post('/stream', stream=True)
|
||||
@app.post("/stream", stream=True)
|
||||
async def handler(request):
|
||||
async def streaming(response):
|
||||
while True:
|
||||
body = await request.stream.get()
|
||||
if body is None:
|
||||
break
|
||||
body = body.decode('utf-8').replace('1', 'A')
|
||||
body = body.decode("utf-8").replace("1", "A")
|
||||
await response.write(body)
|
||||
|
||||
return stream(streaming)
|
||||
|
||||
|
||||
@bp.put('/bp_stream', stream=True)
|
||||
@bp.put("/bp_stream", stream=True)
|
||||
async def bp_handler(request):
|
||||
result = ''
|
||||
result = ""
|
||||
while True:
|
||||
body = await request.stream.get()
|
||||
if body is None:
|
||||
break
|
||||
result += body.decode('utf-8').replace('1', 'A')
|
||||
result += body.decode("utf-8").replace("1", "A")
|
||||
return text(result)
|
||||
|
||||
|
||||
async def post_handler(request):
|
||||
result = ''
|
||||
result = ""
|
||||
while True:
|
||||
body = await request.stream.get()
|
||||
if body is None:
|
||||
break
|
||||
result += body.decode('utf-8')
|
||||
result += body.decode("utf-8")
|
||||
return text(result)
|
||||
|
||||
|
||||
app.blueprint(bp)
|
||||
app.add_route(SimpleView.as_view(), '/method_view')
|
||||
view = CompositionView()
|
||||
view.add(['POST'], post_handler, stream=True)
|
||||
app.add_route(view, '/composition_view')
|
||||
app.add_route(SimpleView.as_view(), "/method_view")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=8000)
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import asyncio
|
||||
from sanic import Sanic
|
||||
from sanic import response
|
||||
|
||||
from sanic import Sanic, response
|
||||
from sanic.config import Config
|
||||
from sanic.exceptions import RequestTimeout
|
||||
|
||||
|
||||
Config.REQUEST_TIMEOUT = 1
|
||||
app = Sanic(__name__)
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route('/')
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
await asyncio.sleep(3)
|
||||
return response.text('Hello, world!')
|
||||
return response.text("Hello, world!")
|
||||
|
||||
|
||||
@app.exception(RequestTimeout)
|
||||
def timeout(request, exception):
|
||||
return response.text('RequestTimeout from error_handler.', 408)
|
||||
return response.text("RequestTimeout from error_handler.", 408)
|
||||
|
||||
app.run(host='0.0.0.0', port=8000)
|
||||
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
from os import getenv
|
||||
|
||||
import rollbar
|
||||
|
||||
from sanic.handlers import ErrorHandler
|
||||
from sanic import Sanic
|
||||
from sanic.exceptions import SanicException
|
||||
from os import getenv
|
||||
from sanic.handlers import ErrorHandler
|
||||
|
||||
|
||||
rollbar.init(getenv("ROLLBAR_API_KEY"))
|
||||
|
||||
|
||||
class RollbarExceptionHandler(ErrorHandler):
|
||||
|
||||
def default(self, request, exception):
|
||||
rollbar.report_message(str(exception))
|
||||
return super().default(request, exception)
|
||||
|
||||
|
||||
app = Sanic(__name__, error_handler=RollbarExceptionHandler())
|
||||
app = Sanic("Example", error_handler=RollbarExceptionHandler())
|
||||
|
||||
|
||||
@app.route("/raise")
|
||||
@@ -24,7 +25,4 @@ def create_error(request):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(
|
||||
host="0.0.0.0",
|
||||
port=getenv("PORT", 8080)
|
||||
)
|
||||
app.run(host="0.0.0.0", port=getenv("PORT", 8080))
|
||||
|
||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
||||
from sanic import Sanic, response
|
||||
|
||||
|
||||
app = Sanic(__name__)
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route("/text")
|
||||
@@ -59,31 +59,31 @@ async def handler_stream(request):
|
||||
return response.stream(body)
|
||||
|
||||
|
||||
@app.listener("before_server_start")
|
||||
@app.before_server_start
|
||||
async def listener_before_server_start(*args, **kwargs):
|
||||
print("before_server_start")
|
||||
|
||||
|
||||
@app.listener("after_server_start")
|
||||
@app.after_server_start
|
||||
async def listener_after_server_start(*args, **kwargs):
|
||||
print("after_server_start")
|
||||
|
||||
|
||||
@app.listener("before_server_stop")
|
||||
@app.before_server_stop
|
||||
async def listener_before_server_stop(*args, **kwargs):
|
||||
print("before_server_stop")
|
||||
|
||||
|
||||
@app.listener("after_server_stop")
|
||||
@app.after_server_stop
|
||||
async def listener_after_server_stop(*args, **kwargs):
|
||||
print("after_server_stop")
|
||||
|
||||
|
||||
@app.middleware("request")
|
||||
@app.on_request
|
||||
async def print_on_request(request):
|
||||
print("print_on_request")
|
||||
|
||||
|
||||
@app.middleware("response")
|
||||
@app.on_response
|
||||
async def print_on_response(request, response):
|
||||
print("print_on_response")
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
from sanic import Sanic
|
||||
from sanic import response
|
||||
from signal import signal, SIGINT
|
||||
import asyncio
|
||||
|
||||
import uvloop
|
||||
|
||||
app = Sanic(__name__)
|
||||
from sanic import Sanic, response
|
||||
|
||||
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
return response.json({"answer": "42"})
|
||||
|
||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||
server = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
|
||||
loop = asyncio.get_event_loop()
|
||||
task = asyncio.ensure_future(server)
|
||||
signal(SIGINT, lambda s, f: loop.stop())
|
||||
try:
|
||||
loop.run_forever()
|
||||
except:
|
||||
loop.stop()
|
||||
|
||||
async def main():
|
||||
server = await app.create_server(
|
||||
port=8000, host="0.0.0.0", return_asyncio_server=True
|
||||
)
|
||||
|
||||
if server is None:
|
||||
return
|
||||
|
||||
await server.startup()
|
||||
await server.serve_forever()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -1,32 +1,62 @@
|
||||
from sanic import Sanic
|
||||
from sanic import response
|
||||
from signal import signal, SIGINT
|
||||
import asyncio
|
||||
|
||||
from signal import SIGINT, signal
|
||||
|
||||
import uvloop
|
||||
|
||||
app = Sanic(__name__)
|
||||
from sanic import Sanic, response
|
||||
from sanic.server import AsyncioServer
|
||||
|
||||
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.before_server_start
|
||||
async def before_server_start(app, loop):
|
||||
print("Async Server starting")
|
||||
|
||||
|
||||
@app.after_server_start
|
||||
async def after_server_start(app, loop):
|
||||
print("Async Server started")
|
||||
|
||||
|
||||
@app.before_server_stop
|
||||
async def before_server_stop(app, loop):
|
||||
print("Async Server stopping")
|
||||
|
||||
|
||||
@app.after_server_stop
|
||||
async def after_server_stop(app, loop):
|
||||
print("Async Server stopped")
|
||||
|
||||
@app.listener('after_server_start')
|
||||
async def after_start_test(app, loop):
|
||||
print("Async Server Started!")
|
||||
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
return response.json({"answer": "42"})
|
||||
|
||||
|
||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||
serv_coro = app.create_server(host="0.0.0.0", port=8000, return_asyncio_server=True)
|
||||
serv_coro = app.create_server(
|
||||
host="0.0.0.0", port=8000, return_asyncio_server=True
|
||||
)
|
||||
loop = asyncio.get_event_loop()
|
||||
serv_task = asyncio.ensure_future(serv_coro, loop=loop)
|
||||
signal(SIGINT, lambda s, f: loop.stop())
|
||||
server = loop.run_until_complete(serv_task)
|
||||
server.after_start()
|
||||
server: AsyncioServer = loop.run_until_complete(serv_task)
|
||||
loop.run_until_complete(server.startup())
|
||||
|
||||
# When using app.run(), this actually triggers before the serv_coro.
|
||||
# But, in this example, we are using the convenience method, even if it is
|
||||
# out of order.
|
||||
loop.run_until_complete(server.before_start())
|
||||
loop.run_until_complete(server.after_start())
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt as e:
|
||||
except KeyboardInterrupt:
|
||||
loop.stop()
|
||||
finally:
|
||||
server.before_stop()
|
||||
loop.run_until_complete(server.before_stop())
|
||||
|
||||
# Wait for server to close
|
||||
close_task = server.close()
|
||||
@@ -35,4 +65,4 @@ finally:
|
||||
# Complete all tasks on the loop
|
||||
for connection in server.connections:
|
||||
connection.close_if_idle()
|
||||
server.after_stop()
|
||||
loop.run_until_complete(server.after_stop())
|
||||
|
||||
@@ -6,20 +6,19 @@ from sentry_sdk.integrations.sanic import SanicIntegration
|
||||
from sanic import Sanic
|
||||
from sanic.response import json
|
||||
|
||||
|
||||
sentry_init(
|
||||
dsn=getenv("SENTRY_DSN"),
|
||||
integrations=[SanicIntegration()],
|
||||
)
|
||||
|
||||
app = Sanic(__name__)
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@app.route("/working")
|
||||
async def working_path(request):
|
||||
return json({
|
||||
"response": "Working API Response"
|
||||
})
|
||||
return json({"response": "Working API Response"})
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@@ -28,8 +27,5 @@ async def raise_error(request):
|
||||
raise Exception("Testing Sentry Integration")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(
|
||||
host="0.0.0.0",
|
||||
port=getenv("PORT", 8080)
|
||||
)
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=getenv("PORT", 8080))
|
||||
|
||||
@@ -1,42 +1,41 @@
|
||||
from sanic import Sanic
|
||||
from sanic.views import HTTPMethodView
|
||||
from sanic.response import text
|
||||
from sanic.views import HTTPMethodView
|
||||
|
||||
app = Sanic('some_name')
|
||||
|
||||
app = Sanic("some_name")
|
||||
|
||||
|
||||
class SimpleView(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
return text('I am get method')
|
||||
return text("I am get method")
|
||||
|
||||
def post(self, request):
|
||||
return text('I am post method')
|
||||
return text("I am post method")
|
||||
|
||||
def put(self, request):
|
||||
return text('I am put method')
|
||||
return text("I am put method")
|
||||
|
||||
def patch(self, request):
|
||||
return text('I am patch method')
|
||||
return text("I am patch method")
|
||||
|
||||
def delete(self, request):
|
||||
return text('I am delete method')
|
||||
return text("I am delete method")
|
||||
|
||||
|
||||
class SimpleAsyncView(HTTPMethodView):
|
||||
|
||||
async def get(self, request):
|
||||
return text('I am async get method')
|
||||
return text("I am async get method")
|
||||
|
||||
async def post(self, request):
|
||||
return text('I am async post method')
|
||||
return text("I am async post method")
|
||||
|
||||
async def put(self, request):
|
||||
return text('I am async put method')
|
||||
return text("I am async put method")
|
||||
|
||||
|
||||
app.add_route(SimpleView.as_view(), '/')
|
||||
app.add_route(SimpleAsyncView.as_view(), '/async')
|
||||
app.add_route(SimpleView.as_view(), "/")
|
||||
app.add_route(SimpleAsyncView.as_view(), "/async")
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from sanic import Sanic
|
||||
|
||||
|
||||
app = Sanic(__name__)
|
||||
app = Sanic("Example")
|
||||
|
||||
app.static("/", "./static")
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from sanic import Sanic
|
||||
from sanic import response as res
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
async def test(req):
|
||||
return res.text("I\'m a teapot", status=418)
|
||||
return res.text("I'm a teapot", status=418)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import os
|
||||
|
||||
from sanic import Sanic
|
||||
from sanic.log import logger as log
|
||||
from sanic import response
|
||||
from sanic import Sanic, response
|
||||
from sanic.exceptions import ServerError
|
||||
from sanic.log import logger as log
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
@@ -13,7 +13,7 @@ async def test_async(request):
|
||||
return response.json({"test": True})
|
||||
|
||||
|
||||
@app.route("/sync", methods=['GET', 'POST'])
|
||||
@app.route("/sync", methods=["GET", "POST"])
|
||||
def test_sync(request):
|
||||
return response.json({"test": True})
|
||||
|
||||
@@ -31,6 +31,7 @@ def exception(request):
|
||||
@app.route("/await")
|
||||
async def test_await(request):
|
||||
import asyncio
|
||||
|
||||
await asyncio.sleep(5)
|
||||
return response.text("I'm feeling sleepy")
|
||||
|
||||
@@ -42,8 +43,10 @@ async def test_file(request):
|
||||
|
||||
@app.route("/file_stream")
|
||||
async def test_file_stream(request):
|
||||
return await response.file_stream(os.path.abspath("setup.py"),
|
||||
chunk_size=1024)
|
||||
return await response.file_stream(
|
||||
os.path.abspath("setup.py"), chunk_size=1024
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------- #
|
||||
# Exceptions
|
||||
@@ -52,14 +55,17 @@ async def test_file_stream(request):
|
||||
|
||||
@app.exception(ServerError)
|
||||
async def test(request, exception):
|
||||
return response.json({"exception": "{}".format(exception), "status": exception.status_code},
|
||||
status=exception.status_code)
|
||||
return response.json(
|
||||
{"exception": str(exception), "status": exception.status_code},
|
||||
status=exception.status_code,
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------- #
|
||||
# Read from request
|
||||
# ----------------------------------------------- #
|
||||
|
||||
|
||||
@app.route("/json")
|
||||
def post_json(request):
|
||||
return response.json({"received": True, "message": request.json})
|
||||
@@ -67,38 +73,51 @@ def post_json(request):
|
||||
|
||||
@app.route("/form")
|
||||
def post_form_json(request):
|
||||
return response.json({"received": True, "form_data": request.form, "test": request.form.get('test')})
|
||||
return response.json(
|
||||
{
|
||||
"received": True,
|
||||
"form_data": request.form,
|
||||
"test": request.form.get("test"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@app.route("/query_string")
|
||||
def query_string(request):
|
||||
return response.json({"parsed": True, "args": request.args, "url": request.url,
|
||||
"query_string": request.query_string})
|
||||
return response.json(
|
||||
{
|
||||
"parsed": True,
|
||||
"args": request.args,
|
||||
"url": request.url,
|
||||
"query_string": request.query_string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------- #
|
||||
# Run Server
|
||||
# ----------------------------------------------- #
|
||||
|
||||
@app.listener('before_server_start')
|
||||
|
||||
@app.before_server_start
|
||||
def before_start(app, loop):
|
||||
log.info("SERVER STARTING")
|
||||
|
||||
|
||||
@app.listener('after_server_start')
|
||||
@app.after_server_start
|
||||
def after_start(app, loop):
|
||||
log.info("OH OH OH OH OHHHHHHHH")
|
||||
|
||||
|
||||
@app.listener('before_server_stop')
|
||||
@app.before_server_stop
|
||||
def before_stop(app, loop):
|
||||
log.info("SERVER STOPPING")
|
||||
|
||||
|
||||
@app.listener('after_server_stop')
|
||||
@app.after_server_stop
|
||||
def after_stop(app, loop):
|
||||
log.info("TRIED EVERYTHING")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
from sanic import Sanic
|
||||
from sanic import response
|
||||
import socket
|
||||
import os
|
||||
from sanic import Sanic, response
|
||||
|
||||
app = Sanic(__name__)
|
||||
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route("/test")
|
||||
async def test(request):
|
||||
return response.text("OK")
|
||||
|
||||
if __name__ == '__main__':
|
||||
server_address = './uds_socket'
|
||||
# Make sure the socket does not already exist
|
||||
try:
|
||||
os.unlink(server_address)
|
||||
except OSError:
|
||||
if os.path.exists(server_address):
|
||||
raise
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.bind(server_address)
|
||||
app.run(sock=sock)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(unix="./uds_socket")
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
from sanic import Sanic
|
||||
from sanic import response
|
||||
|
||||
app = Sanic(__name__)
|
||||
from sanic import Sanic, response
|
||||
|
||||
|
||||
@app.route('/')
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
async def index(request):
|
||||
# generate a URL for the endpoint `post_handler`
|
||||
url = app.url_for('post_handler', post_id=5)
|
||||
url = app.url_for("post_handler", post_id=5)
|
||||
# the URL is `/posts/5`, redirect to it
|
||||
return response.redirect(url)
|
||||
|
||||
|
||||
@app.route('/posts/<post_id>')
|
||||
@app.route("/posts/<post_id>")
|
||||
async def post_handler(request, post_id):
|
||||
return response.text('Post - {}'.format(post_id))
|
||||
|
||||
if __name__ == '__main__':
|
||||
return response.text("Post - {}".format(post_id))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
@@ -8,7 +8,9 @@ app = Sanic(name="blue-print-group-version-example")
|
||||
bp1 = Blueprint(name="ultron", url_prefix="/ultron")
|
||||
bp2 = Blueprint(name="vision", url_prefix="/vision", strict_slashes=None)
|
||||
|
||||
bpg = Blueprint.group([bp1, bp2], url_prefix="/sentient/robot", version=1, strict_slashes=True)
|
||||
bpg = Blueprint.group(
|
||||
bp1, bp2, url_prefix="/sentient/robot", version=1, strict_slashes=True
|
||||
)
|
||||
|
||||
|
||||
@bp1.get("/name")
|
||||
@@ -31,5 +33,5 @@ async def bp2_revised_name(request):
|
||||
|
||||
app.blueprint(bpg)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -8,7 +8,7 @@ from sanic.blueprints import Blueprint
|
||||
# curl -H "Host: bp.example.com" localhost:8000/question
|
||||
# curl -H "Host: bp.example.com" localhost:8000/answer
|
||||
|
||||
app = Sanic(__name__)
|
||||
app = Sanic("Example")
|
||||
bp = Blueprint("bp", host="bp.example.com")
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
from sanic import Sanic
|
||||
from sanic.response import file
|
||||
|
||||
app = Sanic(__name__)
|
||||
from sanic.response import redirect
|
||||
|
||||
|
||||
@app.route('/')
|
||||
async def index(request):
|
||||
return await file('websocket.html')
|
||||
app = Sanic("Example")
|
||||
|
||||
|
||||
@app.websocket('/feed')
|
||||
app.static("index.html", "websocket.html")
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def index(request):
|
||||
return redirect("index.html")
|
||||
|
||||
|
||||
@app.websocket("/feed")
|
||||
async def feed(request, ws):
|
||||
while True:
|
||||
data = 'hello!'
|
||||
print('Sending: ' + data)
|
||||
data = "hello!"
|
||||
print("Sending: " + data)
|
||||
await ws.send(data)
|
||||
data = await ws.recv()
|
||||
print('Received: ' + data)
|
||||
print("Received: " + data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, debug=True)
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
FROM catthehacker/ubuntu:act-latest
|
||||
SHELL [ "/bin/bash", "-c" ]
|
||||
ENTRYPOINT []
|
||||
RUN apt-get update
|
||||
RUN apt-get install gcc -y
|
||||
RUN apt-get install -y --no-install-recommends g++
|
||||
26
pyproject.toml
Normal file
26
pyproject.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[build-system]
|
||||
requires = ["setuptools<60.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.black]
|
||||
line-length = 79
|
||||
|
||||
[tool.isort]
|
||||
atomic = true
|
||||
default_section = "THIRDPARTY"
|
||||
include_trailing_comma = true
|
||||
known_first_party = "sanic"
|
||||
known_third_party = "pytest"
|
||||
line_length = 79
|
||||
lines_after_imports = 2
|
||||
lines_between_types = 1
|
||||
multi_line_output = 3
|
||||
profile = "black"
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"httptools.*",
|
||||
"trustme.*",
|
||||
"sanic_routing.*",
|
||||
]
|
||||
ignore_missing_imports = true
|
||||
@@ -6,4 +6,4 @@ python:
|
||||
path: .
|
||||
extra_requirements:
|
||||
- docs
|
||||
system_packages: true
|
||||
system_packages: true
|
||||
|
||||
@@ -3,7 +3,16 @@ from sanic.app import Sanic
|
||||
from sanic.blueprints import Blueprint
|
||||
from sanic.constants import HTTPMethod
|
||||
from sanic.request import Request
|
||||
from sanic.response import HTTPResponse, html, json, text
|
||||
from sanic.response import (
|
||||
HTTPResponse,
|
||||
empty,
|
||||
file,
|
||||
html,
|
||||
json,
|
||||
redirect,
|
||||
text,
|
||||
)
|
||||
from sanic.server.websockets.impl import WebsocketImplProtocol as Websocket
|
||||
|
||||
|
||||
__all__ = (
|
||||
@@ -13,7 +22,11 @@ __all__ = (
|
||||
"HTTPMethod",
|
||||
"HTTPResponse",
|
||||
"Request",
|
||||
"Websocket",
|
||||
"empty",
|
||||
"file",
|
||||
"html",
|
||||
"json",
|
||||
"redirect",
|
||||
"text",
|
||||
)
|
||||
|
||||
@@ -1,196 +1,15 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from argparse import ArgumentParser, RawTextHelpFormatter
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sanic_routing import __version__ as __routing_version__ # type: ignore
|
||||
|
||||
from sanic import __version__
|
||||
from sanic.app import Sanic
|
||||
from sanic.config import BASE_LOGO
|
||||
from sanic.log import error_logger
|
||||
from sanic.simple import create_simple_server
|
||||
from sanic.cli.app import SanicCLI
|
||||
from sanic.compat import OS_IS_WINDOWS, enable_windows_color_support
|
||||
|
||||
|
||||
class SanicArgumentParser(ArgumentParser):
|
||||
def add_bool_arguments(self, *args, **kwargs):
|
||||
group = self.add_mutually_exclusive_group()
|
||||
group.add_argument(*args, action="store_true", **kwargs)
|
||||
kwargs["help"] = f"no {kwargs['help']}\n "
|
||||
group.add_argument(
|
||||
"--no-" + args[0][2:], *args[1:], action="store_false", **kwargs
|
||||
)
|
||||
if OS_IS_WINDOWS:
|
||||
enable_windows_color_support()
|
||||
|
||||
|
||||
def main():
|
||||
parser = SanicArgumentParser(
|
||||
prog="sanic",
|
||||
description=BASE_LOGO,
|
||||
formatter_class=lambda prog: RawTextHelpFormatter(
|
||||
prog, max_help_position=33
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"Sanic {__version__}; Routing {__routing_version__}",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--factory",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Treat app as an application factory, "
|
||||
"i.e. a () -> <Sanic app> callable"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--simple",
|
||||
dest="simple",
|
||||
action="store_true",
|
||||
help="Run Sanic as a Simple Server (module arg should be a path)\n ",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-H",
|
||||
"--host",
|
||||
dest="host",
|
||||
type=str,
|
||||
default="127.0.0.1",
|
||||
help="Host address [default 127.0.0.1]",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--port",
|
||||
dest="port",
|
||||
type=int,
|
||||
default=8000,
|
||||
help="Port to serve on [default 8000]",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-u",
|
||||
"--unix",
|
||||
dest="unix",
|
||||
type=str,
|
||||
default="",
|
||||
help="location of unix socket\n ",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cert", dest="cert", type=str, help="Location of certificate for SSL"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--key", dest="key", type=str, help="location of keyfile for SSL\n "
|
||||
)
|
||||
parser.add_bool_arguments(
|
||||
"--access-logs", dest="access_log", help="display access logs"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-w",
|
||||
"--workers",
|
||||
dest="workers",
|
||||
type=int,
|
||||
default=1,
|
||||
help="number of worker processes [default 1]\n ",
|
||||
)
|
||||
parser.add_argument("-d", "--debug", dest="debug", action="store_true")
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--reload",
|
||||
"--auto-reload",
|
||||
dest="auto_reload",
|
||||
action="store_true",
|
||||
help="Watch source directory for file changes and reload on changes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-R",
|
||||
"--reload-dir",
|
||||
dest="path",
|
||||
action="append",
|
||||
help="Extra directories to watch and reload on changes\n ",
|
||||
)
|
||||
parser.add_argument(
|
||||
"module",
|
||||
help=(
|
||||
"Path to your Sanic app. Example: path.to.server:app\n"
|
||||
"If running a Simple Server, path to directory to serve. "
|
||||
"Example: ./\n"
|
||||
),
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
module_path = os.path.abspath(os.getcwd())
|
||||
if module_path not in sys.path:
|
||||
sys.path.append(module_path)
|
||||
|
||||
if args.simple:
|
||||
path = Path(args.module)
|
||||
app = create_simple_server(path)
|
||||
else:
|
||||
delimiter = ":" if ":" in args.module else "."
|
||||
module_name, app_name = args.module.rsplit(delimiter, 1)
|
||||
|
||||
if app_name.endswith("()"):
|
||||
args.factory = True
|
||||
app_name = app_name[:-2]
|
||||
|
||||
module = import_module(module_name)
|
||||
app = getattr(module, app_name, None)
|
||||
if args.factory:
|
||||
app = app()
|
||||
|
||||
app_type_name = type(app).__name__
|
||||
|
||||
if not isinstance(app, Sanic):
|
||||
raise ValueError(
|
||||
f"Module is not a Sanic app, it is a {app_type_name}. "
|
||||
f"Perhaps you meant {args.module}.app?"
|
||||
)
|
||||
if args.cert is not None or args.key is not None:
|
||||
ssl: Optional[Dict[str, Any]] = {
|
||||
"cert": args.cert,
|
||||
"key": args.key,
|
||||
}
|
||||
else:
|
||||
ssl = None
|
||||
|
||||
kwargs = {
|
||||
"host": args.host,
|
||||
"port": args.port,
|
||||
"unix": args.unix,
|
||||
"workers": args.workers,
|
||||
"debug": args.debug,
|
||||
"access_log": args.access_log,
|
||||
"ssl": ssl,
|
||||
}
|
||||
if args.auto_reload:
|
||||
kwargs["auto_reload"] = True
|
||||
|
||||
if args.path:
|
||||
if args.auto_reload or args.debug:
|
||||
kwargs["reload_dir"] = args.path
|
||||
else:
|
||||
error_logger.warning(
|
||||
"Ignoring '--reload-dir' since auto reloading was not "
|
||||
"enabled. If you would like to watch directories for "
|
||||
"changes, consider using --debug or --auto-reload."
|
||||
)
|
||||
|
||||
app.run(**kwargs)
|
||||
except ImportError as e:
|
||||
if module_name.startswith(e.name):
|
||||
error_logger.error(
|
||||
f"No module named {e.name} found.\n"
|
||||
" Example File: project/sanic_server.py -> app\n"
|
||||
" Example Module: project.sanic_server.app"
|
||||
)
|
||||
else:
|
||||
raise e
|
||||
except ValueError:
|
||||
error_logger.exception("Failed to run app")
|
||||
def main(args=None):
|
||||
cli = SanicCLI()
|
||||
cli.attach()
|
||||
cli.run(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "21.6.2"
|
||||
__version__ = "22.12.0a0"
|
||||
|
||||
1353
sanic/app.py
1353
sanic/app.py
File diff suppressed because it is too large
Load Diff
0
sanic/application/__init__.py
Normal file
0
sanic/application/__init__.py
Normal file
32
sanic/application/constants.py
Normal file
32
sanic/application/constants.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from enum import Enum, IntEnum, auto
|
||||
|
||||
|
||||
class StrEnum(str, Enum): # no cov
|
||||
def _generate_next_value_(name: str, *args) -> str: # type: ignore
|
||||
return name.lower()
|
||||
|
||||
def __eq__(self, value: object) -> bool:
|
||||
value = str(value).upper()
|
||||
return super().__eq__(value)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.value)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
||||
class Server(StrEnum):
|
||||
SANIC = auto()
|
||||
ASGI = auto()
|
||||
|
||||
|
||||
class Mode(StrEnum):
|
||||
PRODUCTION = auto()
|
||||
DEBUG = auto()
|
||||
|
||||
|
||||
class ServerStage(IntEnum):
|
||||
STOPPED = auto()
|
||||
PARTIAL = auto()
|
||||
SERVING = auto()
|
||||
34
sanic/application/ext.py
Normal file
34
sanic/application/ext.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import suppress
|
||||
from importlib import import_module
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic import Sanic
|
||||
|
||||
|
||||
def setup_ext(app: Sanic, *, fail: bool = False, **kwargs):
|
||||
if not app.config.AUTO_EXTEND:
|
||||
return
|
||||
|
||||
sanic_ext = None
|
||||
with suppress(ModuleNotFoundError):
|
||||
sanic_ext = import_module("sanic_ext")
|
||||
|
||||
if not sanic_ext: # no cov
|
||||
if fail:
|
||||
raise RuntimeError(
|
||||
"Sanic Extensions is not installed. You can add it to your "
|
||||
"environment using:\n$ pip install sanic[ext]\nor\n$ pip "
|
||||
"install sanic-ext"
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
if not getattr(app, "_ext", None):
|
||||
Ext = getattr(sanic_ext, "Extend")
|
||||
app._ext = Ext(app, **kwargs)
|
||||
|
||||
return app.ext
|
||||
59
sanic/application/logo.py
Normal file
59
sanic/application/logo.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
from os import environ
|
||||
|
||||
from sanic.compat import is_atty
|
||||
|
||||
|
||||
BASE_LOGO = """
|
||||
|
||||
Sanic
|
||||
Build Fast. Run Fast.
|
||||
|
||||
"""
|
||||
COFFEE_LOGO = """\033[48;2;255;13;104m \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ▄████████▄ \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ██ ██▀▀▄ \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ███████████ █ \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ███████████▄▄▀ \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ▀███████▀ \033[0m
|
||||
\033[48;2;255;13;104m \033[0m
|
||||
Dark roast. No sugar."""
|
||||
|
||||
COLOR_LOGO = """\033[48;2;255;13;104m \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ▄███ █████ ██ \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ██ \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ▀███████ ███▄ \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ██ \033[0m
|
||||
\033[38;2;255;255;255;48;2;255;13;104m ████ ████████▀ \033[0m
|
||||
\033[48;2;255;13;104m \033[0m
|
||||
Build Fast. Run Fast."""
|
||||
|
||||
FULL_COLOR_LOGO = """
|
||||
|
||||
\033[38;2;255;13;104m ▄███ █████ ██ \033[0m ▄█▄ ██ █ █ ▄██████████
|
||||
\033[38;2;255;13;104m ██ \033[0m █ █ █ ██ █ █ ██
|
||||
\033[38;2;255;13;104m ▀███████ ███▄ \033[0m ▀ █ █ ██ ▄ █ ██
|
||||
\033[38;2;255;13;104m ██\033[0m █████████ █ ██ █ █ ▄▄
|
||||
\033[38;2;255;13;104m ████ ████████▀ \033[0m █ █ █ ██ █ ▀██ ███████
|
||||
|
||||
""" # noqa
|
||||
|
||||
ansi_pattern = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
|
||||
|
||||
|
||||
def get_logo(full=False, coffee=False):
|
||||
logo = (
|
||||
(FULL_COLOR_LOGO if full else (COFFEE_LOGO if coffee else COLOR_LOGO))
|
||||
if is_atty()
|
||||
else BASE_LOGO
|
||||
)
|
||||
|
||||
if (
|
||||
sys.platform == "darwin"
|
||||
and environ.get("TERM_PROGRAM") == "Apple_Terminal"
|
||||
):
|
||||
logo = ansi_pattern.sub("", logo)
|
||||
|
||||
return logo
|
||||
145
sanic/application/motd.py
Normal file
145
sanic/application/motd.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from shutil import get_terminal_size
|
||||
from textwrap import indent, wrap
|
||||
from typing import Dict, Optional
|
||||
|
||||
from sanic import __version__
|
||||
from sanic.compat import is_atty
|
||||
from sanic.log import logger
|
||||
|
||||
|
||||
class MOTD(ABC):
|
||||
def __init__(
|
||||
self,
|
||||
logo: Optional[str],
|
||||
serve_location: str,
|
||||
data: Dict[str, str],
|
||||
extra: Dict[str, str],
|
||||
) -> None:
|
||||
self.logo = logo
|
||||
self.serve_location = serve_location
|
||||
self.data = data
|
||||
self.extra = extra
|
||||
self.key_width = 0
|
||||
self.value_width = 0
|
||||
|
||||
@abstractmethod
|
||||
def display(self):
|
||||
... # noqa
|
||||
|
||||
@classmethod
|
||||
def output(
|
||||
cls,
|
||||
logo: Optional[str],
|
||||
serve_location: str,
|
||||
data: Dict[str, str],
|
||||
extra: Dict[str, str],
|
||||
) -> None:
|
||||
motd_class = MOTDTTY if is_atty() else MOTDBasic
|
||||
motd_class(logo, serve_location, data, extra).display()
|
||||
|
||||
|
||||
class MOTDBasic(MOTD):
|
||||
def display(self):
|
||||
if self.logo:
|
||||
logger.debug(self.logo)
|
||||
lines = [f"Sanic v{__version__}"]
|
||||
if self.serve_location:
|
||||
lines.append(f"Goin' Fast @ {self.serve_location}")
|
||||
lines += [
|
||||
*(f"{key}: {value}" for key, value in self.data.items()),
|
||||
*(f"{key}: {value}" for key, value in self.extra.items()),
|
||||
]
|
||||
for line in lines:
|
||||
logger.info(line)
|
||||
|
||||
|
||||
class MOTDTTY(MOTD):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_variables()
|
||||
|
||||
def set_variables(self): # no cov
|
||||
fallback = (108, 24)
|
||||
terminal_width = max(
|
||||
get_terminal_size(fallback=fallback).columns, fallback[0]
|
||||
)
|
||||
self.max_value_width = terminal_width - fallback[0] + 36
|
||||
|
||||
self.key_width = 4
|
||||
self.value_width = self.max_value_width
|
||||
if self.data:
|
||||
self.key_width = max(map(len, self.data.keys()))
|
||||
self.value_width = min(
|
||||
max(map(len, self.data.values())), self.max_value_width
|
||||
)
|
||||
self.logo_lines = self.logo.split("\n") if self.logo else []
|
||||
self.logo_line_length = 24
|
||||
self.centering_length = (
|
||||
self.key_width + self.value_width + 2 + self.logo_line_length
|
||||
)
|
||||
self.display_length = self.key_width + self.value_width + 2
|
||||
|
||||
def display(self, version=True, action="Goin' Fast", out=None):
|
||||
if not out:
|
||||
out = logger.info
|
||||
header = "Sanic"
|
||||
if version:
|
||||
header += f" v{__version__}"
|
||||
header = header.center(self.centering_length)
|
||||
running = (
|
||||
f"{action} @ {self.serve_location}" if self.serve_location else ""
|
||||
).center(self.centering_length)
|
||||
length = len(header) + 2 - self.logo_line_length
|
||||
first_filler = "─" * (self.logo_line_length - 1)
|
||||
second_filler = "─" * length
|
||||
display_filler = "─" * (self.display_length + 2)
|
||||
lines = [
|
||||
f"\n┌{first_filler}─{second_filler}┐",
|
||||
f"│ {header} │",
|
||||
f"│ {running} │",
|
||||
f"├{first_filler}┬{second_filler}┤",
|
||||
]
|
||||
|
||||
self._render_data(lines, self.data, 0)
|
||||
if self.extra:
|
||||
logo_part = self._get_logo_part(len(lines) - 4)
|
||||
lines.append(f"| {logo_part} ├{display_filler}┤")
|
||||
self._render_data(lines, self.extra, len(lines) - 4)
|
||||
|
||||
self._render_fill(lines)
|
||||
|
||||
lines.append(f"└{first_filler}┴{second_filler}┘\n")
|
||||
out(indent("\n".join(lines), " "))
|
||||
|
||||
def _render_data(self, lines, data, start):
|
||||
offset = 0
|
||||
for idx, (key, value) in enumerate(data.items(), start=start):
|
||||
key = key.rjust(self.key_width)
|
||||
|
||||
wrapped = wrap(value, self.max_value_width, break_on_hyphens=False)
|
||||
for wrap_index, part in enumerate(wrapped):
|
||||
part = part.ljust(self.value_width)
|
||||
logo_part = self._get_logo_part(idx + offset + wrap_index)
|
||||
display = (
|
||||
f"{key}: {part}"
|
||||
if wrap_index == 0
|
||||
else (" " * len(key) + f" {part}")
|
||||
)
|
||||
lines.append(f"│ {logo_part} │ {display} │")
|
||||
if wrap_index:
|
||||
offset += 1
|
||||
|
||||
def _render_fill(self, lines):
|
||||
filler = " " * self.display_length
|
||||
idx = len(lines) - 5
|
||||
for i in range(1, len(self.logo_lines) - idx):
|
||||
logo_part = self.logo_lines[idx + i]
|
||||
lines.append(f"│ {logo_part} │ {filler} │")
|
||||
|
||||
def _get_logo_part(self, idx):
|
||||
try:
|
||||
logo_part = self.logo_lines[idx]
|
||||
except IndexError:
|
||||
logo_part = " " * (self.logo_line_length - 3)
|
||||
return logo_part
|
||||
86
sanic/application/spinner.py
Normal file
86
sanic/application/spinner.py
Normal file
@@ -0,0 +1,86 @@
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from contextlib import contextmanager
|
||||
from queue import Queue
|
||||
from threading import Thread
|
||||
|
||||
|
||||
if os.name == "nt": # noqa
|
||||
import ctypes # noqa
|
||||
|
||||
class _CursorInfo(ctypes.Structure):
|
||||
_fields_ = [("size", ctypes.c_int), ("visible", ctypes.c_byte)]
|
||||
|
||||
|
||||
class Spinner: # noqa
|
||||
def __init__(self, message: str) -> None:
|
||||
self.message = message
|
||||
self.queue: Queue[int] = Queue()
|
||||
self.spinner = self.cursor()
|
||||
self.thread = Thread(target=self.run)
|
||||
|
||||
def start(self):
|
||||
self.queue.put(1)
|
||||
self.thread.start()
|
||||
self.hide()
|
||||
|
||||
def run(self):
|
||||
while self.queue.get():
|
||||
output = f"\r{self.message} [{next(self.spinner)}]"
|
||||
sys.stdout.write(output)
|
||||
sys.stdout.flush()
|
||||
time.sleep(0.1)
|
||||
self.queue.put(1)
|
||||
|
||||
def stop(self):
|
||||
self.queue.put(0)
|
||||
self.thread.join()
|
||||
self.show()
|
||||
|
||||
@staticmethod
|
||||
def cursor():
|
||||
while True:
|
||||
for cursor in "|/-\\":
|
||||
yield cursor
|
||||
|
||||
@staticmethod
|
||||
def hide():
|
||||
if os.name == "nt":
|
||||
ci = _CursorInfo()
|
||||
handle = ctypes.windll.kernel32.GetStdHandle(-11)
|
||||
ctypes.windll.kernel32.GetConsoleCursorInfo(
|
||||
handle, ctypes.byref(ci)
|
||||
)
|
||||
ci.visible = False
|
||||
ctypes.windll.kernel32.SetConsoleCursorInfo(
|
||||
handle, ctypes.byref(ci)
|
||||
)
|
||||
elif os.name == "posix":
|
||||
sys.stdout.write("\033[?25l")
|
||||
sys.stdout.flush()
|
||||
|
||||
@staticmethod
|
||||
def show():
|
||||
if os.name == "nt":
|
||||
ci = _CursorInfo()
|
||||
handle = ctypes.windll.kernel32.GetStdHandle(-11)
|
||||
ctypes.windll.kernel32.GetConsoleCursorInfo(
|
||||
handle, ctypes.byref(ci)
|
||||
)
|
||||
ci.visible = True
|
||||
ctypes.windll.kernel32.SetConsoleCursorInfo(
|
||||
handle, ctypes.byref(ci)
|
||||
)
|
||||
elif os.name == "posix":
|
||||
sys.stdout.write("\033[?25h")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def loading(message: str = "Loading"): # noqa
|
||||
spinner = Spinner(message)
|
||||
spinner.start()
|
||||
yield
|
||||
spinner.stop()
|
||||
91
sanic/application/state.py
Normal file
91
sanic/application/state.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from socket import socket
|
||||
from ssl import SSLContext
|
||||
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union
|
||||
|
||||
from sanic.application.constants import Mode, Server, ServerStage
|
||||
from sanic.log import VerbosityFilter, logger
|
||||
from sanic.server.async_server import AsyncioServer
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic import Sanic
|
||||
|
||||
|
||||
@dataclass
|
||||
class ApplicationServerInfo:
|
||||
settings: Dict[str, Any]
|
||||
stage: ServerStage = field(default=ServerStage.STOPPED)
|
||||
server: Optional[AsyncioServer] = field(default=None)
|
||||
|
||||
|
||||
@dataclass
|
||||
class ApplicationState:
|
||||
app: Sanic
|
||||
asgi: bool = field(default=False)
|
||||
coffee: bool = field(default=False)
|
||||
fast: bool = field(default=False)
|
||||
host: str = field(default="")
|
||||
port: int = field(default=0)
|
||||
ssl: Optional[SSLContext] = field(default=None)
|
||||
sock: Optional[socket] = field(default=None)
|
||||
unix: Optional[str] = field(default=None)
|
||||
mode: Mode = field(default=Mode.PRODUCTION)
|
||||
reload_dirs: Set[Path] = field(default_factory=set)
|
||||
auto_reload: bool = field(default=False)
|
||||
server: Server = field(default=Server.SANIC)
|
||||
is_running: bool = field(default=False)
|
||||
is_started: bool = field(default=False)
|
||||
is_stopping: bool = field(default=False)
|
||||
verbosity: int = field(default=0)
|
||||
workers: int = field(default=0)
|
||||
primary: bool = field(default=True)
|
||||
server_info: List[ApplicationServerInfo] = field(default_factory=list)
|
||||
|
||||
# This property relates to the ApplicationState instance and should
|
||||
# not be changed except in the __post_init__ method
|
||||
_init: bool = field(default=False)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
self._init = True
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
if self._init and name == "_init":
|
||||
raise RuntimeError(
|
||||
"Cannot change the value of _init after instantiation"
|
||||
)
|
||||
super().__setattr__(name, value)
|
||||
if self._init and hasattr(self, f"set_{name}"):
|
||||
getattr(self, f"set_{name}")(value)
|
||||
|
||||
def set_mode(self, value: Union[str, Mode]):
|
||||
if hasattr(self.app, "error_handler"):
|
||||
self.app.error_handler.debug = self.app.debug
|
||||
if getattr(self.app, "configure_logging", False) and self.app.debug:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
def set_verbosity(self, value: int):
|
||||
VerbosityFilter.verbosity = value
|
||||
|
||||
@property
|
||||
def is_debug(self):
|
||||
return self.mode is Mode.DEBUG
|
||||
|
||||
@property
|
||||
def stage(self) -> ServerStage:
|
||||
if not self.server_info:
|
||||
return ServerStage.STOPPED
|
||||
|
||||
if all(info.stage is ServerStage.SERVING for info in self.server_info):
|
||||
return ServerStage.SERVING
|
||||
elif any(
|
||||
info.stage is ServerStage.SERVING for info in self.server_info
|
||||
):
|
||||
return ServerStage.PARTIAL
|
||||
|
||||
return ServerStage.STOPPED
|
||||
@@ -1,36 +1,51 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
|
||||
from inspect import isawaitable
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from urllib.parse import quote
|
||||
|
||||
import sanic.app # noqa
|
||||
|
||||
from sanic.compat import Header
|
||||
from sanic.exceptions import ServerError
|
||||
from sanic.helpers import Default
|
||||
from sanic.http import Stage
|
||||
from sanic.log import logger
|
||||
from sanic.models.asgi import ASGIReceive, ASGIScope, ASGISend, MockTransport
|
||||
from sanic.request import Request
|
||||
from sanic.response import BaseHTTPResponse
|
||||
from sanic.server import ConnInfo
|
||||
from sanic.websocket import WebSocketConnection
|
||||
from sanic.server.websockets.connection import WebSocketConnection
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic import Sanic
|
||||
|
||||
|
||||
class Lifespan:
|
||||
def __init__(self, asgi_app: "ASGIApp") -> None:
|
||||
def __init__(self, asgi_app: ASGIApp) -> None:
|
||||
self.asgi_app = asgi_app
|
||||
|
||||
if "before_server_start" in self.asgi_app.sanic_app.listeners:
|
||||
warnings.warn(
|
||||
if (
|
||||
"server.init.before"
|
||||
in self.asgi_app.sanic_app.signal_router.name_index
|
||||
):
|
||||
logger.debug(
|
||||
'You have set a listener for "before_server_start" '
|
||||
"in ASGI mode. "
|
||||
"It will be executed as early as possible, but not before "
|
||||
"the ASGI server is started."
|
||||
"the ASGI server is started.",
|
||||
extra={"verbosity": 1},
|
||||
)
|
||||
if "after_server_stop" in self.asgi_app.sanic_app.listeners:
|
||||
warnings.warn(
|
||||
if (
|
||||
"server.shutdown.after"
|
||||
in self.asgi_app.sanic_app.signal_router.name_index
|
||||
):
|
||||
logger.debug(
|
||||
'You have set a listener for "after_server_stop" '
|
||||
"in ASGI mode. "
|
||||
"It will be executed as late as possible, but not after "
|
||||
"the ASGI server is stopped."
|
||||
"the ASGI server is stopped.",
|
||||
extra={"verbosity": 1},
|
||||
)
|
||||
|
||||
async def startup(self) -> None:
|
||||
@@ -42,19 +57,16 @@ class Lifespan:
|
||||
in sequence since the ASGI lifespan protocol only supports a single
|
||||
startup event.
|
||||
"""
|
||||
self.asgi_app.sanic_app.router.finalize()
|
||||
if self.asgi_app.sanic_app.signal_router.routes:
|
||||
self.asgi_app.sanic_app.signal_router.finalize()
|
||||
listeners = self.asgi_app.sanic_app.listeners.get(
|
||||
"before_server_start", []
|
||||
) + self.asgi_app.sanic_app.listeners.get("after_server_start", [])
|
||||
await self.asgi_app.sanic_app._startup()
|
||||
await self.asgi_app.sanic_app._server_event("init", "before")
|
||||
await self.asgi_app.sanic_app._server_event("init", "after")
|
||||
|
||||
for handler in listeners:
|
||||
response = handler(
|
||||
self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop
|
||||
if not isinstance(self.asgi_app.sanic_app.config.USE_UVLOOP, Default):
|
||||
warnings.warn(
|
||||
"You have set the USE_UVLOOP configuration option, but Sanic "
|
||||
"cannot control the event loop when running in ASGI mode."
|
||||
"This option will be ignored."
|
||||
)
|
||||
if response and isawaitable(response):
|
||||
await response
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""
|
||||
@@ -65,16 +77,8 @@ class Lifespan:
|
||||
in sequence since the ASGI lifespan protocol only supports a single
|
||||
shutdown event.
|
||||
"""
|
||||
listeners = self.asgi_app.sanic_app.listeners.get(
|
||||
"before_server_stop", []
|
||||
) + self.asgi_app.sanic_app.listeners.get("after_server_stop", [])
|
||||
|
||||
for handler in listeners:
|
||||
response = handler(
|
||||
self.asgi_app.sanic_app, self.asgi_app.sanic_app.loop
|
||||
)
|
||||
if response and isawaitable(response):
|
||||
await response
|
||||
await self.asgi_app.sanic_app._server_event("shutdown", "before")
|
||||
await self.asgi_app.sanic_app._server_event("shutdown", "after")
|
||||
|
||||
async def __call__(
|
||||
self, scope: ASGIScope, receive: ASGIReceive, send: ASGISend
|
||||
@@ -91,11 +95,13 @@ class Lifespan:
|
||||
|
||||
|
||||
class ASGIApp:
|
||||
sanic_app: "sanic.app.Sanic"
|
||||
sanic_app: Sanic
|
||||
request: Request
|
||||
transport: MockTransport
|
||||
lifespan: Lifespan
|
||||
ws: Optional[WebSocketConnection]
|
||||
stage: Stage
|
||||
response: Optional[BaseHTTPResponse]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.ws = None
|
||||
@@ -108,6 +114,8 @@ class ASGIApp:
|
||||
instance.sanic_app = sanic_app
|
||||
instance.transport = MockTransport(scope, receive, send)
|
||||
instance.transport.loop = sanic_app.loop
|
||||
instance.stage = Stage.IDLE
|
||||
instance.response = None
|
||||
setattr(instance.transport, "add_task", sanic_app.loop.create_task)
|
||||
|
||||
headers = Header(
|
||||
@@ -156,12 +164,21 @@ class ASGIApp:
|
||||
instance.request_body = True
|
||||
instance.request.conn_info = ConnInfo(instance.transport)
|
||||
|
||||
await sanic_app.dispatch(
|
||||
"http.lifecycle.request",
|
||||
inline=True,
|
||||
context={"request": instance.request},
|
||||
fail_not_found=False,
|
||||
)
|
||||
|
||||
return instance
|
||||
|
||||
async def read(self) -> Optional[bytes]:
|
||||
"""
|
||||
Read and stream the body in chunks from an incoming ASGI message.
|
||||
"""
|
||||
if self.stage is Stage.IDLE:
|
||||
self.stage = Stage.REQUEST
|
||||
message = await self.transport.receive()
|
||||
body = message.get("body", b"")
|
||||
if not message.get("more_body", False):
|
||||
@@ -176,11 +193,17 @@ class ASGIApp:
|
||||
if data:
|
||||
yield data
|
||||
|
||||
def respond(self, response):
|
||||
def respond(self, response: BaseHTTPResponse):
|
||||
if self.stage is not Stage.HANDLER:
|
||||
self.stage = Stage.FAILED
|
||||
raise RuntimeError("Response already started")
|
||||
if self.response is not None:
|
||||
self.response.stream = None
|
||||
response.stream, self.response = self, response
|
||||
return response
|
||||
|
||||
async def send(self, data, end_stream):
|
||||
self.stage = Stage.IDLE if end_stream else Stage.RESPONSE
|
||||
if self.response:
|
||||
response, self.response = self.response, None
|
||||
await self.transport.send(
|
||||
@@ -208,6 +231,10 @@ class ASGIApp:
|
||||
Handle the incoming request.
|
||||
"""
|
||||
try:
|
||||
self.stage = Stage.HANDLER
|
||||
await self.sanic_app.handle_request(self.request)
|
||||
except Exception as e:
|
||||
await self.sanic_app.handle_exception(self.request, e)
|
||||
try:
|
||||
await self.sanic_app.handle_exception(self.request, e)
|
||||
except Exception as exc:
|
||||
await self.sanic_app.handle_exception(self.request, exc, False)
|
||||
|
||||
0
sanic/base/__init__.py
Normal file
0
sanic/base/__init__.py
Normal file
6
sanic/base/meta.py
Normal file
6
sanic/base/meta.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class SanicMeta(type):
|
||||
@classmethod
|
||||
def __prepare__(metaclass, name, bases, **kwds):
|
||||
cls = super().__prepare__(metaclass, name, bases, **kwds)
|
||||
cls["__slots__"] = ()
|
||||
return cls
|
||||
@@ -1,8 +1,8 @@
|
||||
import re
|
||||
|
||||
from typing import Any, Tuple
|
||||
from warnings import warn
|
||||
from typing import Any
|
||||
|
||||
from sanic.base.meta import SanicMeta
|
||||
from sanic.exceptions import SanicException
|
||||
from sanic.mixins.exceptions import ExceptionMixin
|
||||
from sanic.mixins.listeners import ListenerMixin
|
||||
@@ -11,7 +11,7 @@ from sanic.mixins.routes import RouteMixin
|
||||
from sanic.mixins.signals import SignalMixin
|
||||
|
||||
|
||||
VALID_NAME = re.compile(r"^[a-zA-Z][a-zA-Z0-9_\-]*$")
|
||||
VALID_NAME = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_\-]*$")
|
||||
|
||||
|
||||
class BaseSanic(
|
||||
@@ -20,10 +20,11 @@ class BaseSanic(
|
||||
ListenerMixin,
|
||||
ExceptionMixin,
|
||||
SignalMixin,
|
||||
metaclass=SanicMeta,
|
||||
):
|
||||
__fake_slots__: Tuple[str, ...]
|
||||
__slots__ = ("name",)
|
||||
|
||||
def __init__(self, name: str = None, *args, **kwargs) -> None:
|
||||
def __init__(self, name: str = None, *args: Any, **kwargs: Any) -> None:
|
||||
class_name = self.__class__.__name__
|
||||
|
||||
if name is None:
|
||||
@@ -33,11 +34,10 @@ class BaseSanic(
|
||||
)
|
||||
|
||||
if not VALID_NAME.match(name):
|
||||
warn(
|
||||
f"{class_name} instance named '{name}' uses a format that is"
|
||||
f"deprecated. Starting in version 21.12, {class_name} objects "
|
||||
"must be named only using alphanumeric characters, _, or -.",
|
||||
DeprecationWarning,
|
||||
raise SanicException(
|
||||
f"{class_name} instance named '{name}' uses an invalid "
|
||||
"format. Names must begin with a character and may only "
|
||||
"contain alphanumeric characters, _, or -."
|
||||
)
|
||||
|
||||
self.name = name
|
||||
@@ -52,15 +52,12 @@ class BaseSanic(
|
||||
return f'{self.__class__.__name__}(name="{self.name}")'
|
||||
|
||||
def __setattr__(self, name: str, value: Any) -> None:
|
||||
# This is a temporary compat layer so we can raise a warning until
|
||||
# setting attributes on the app instance can be removed and deprecated
|
||||
# with a proper implementation of __slots__
|
||||
if name not in self.__fake_slots__:
|
||||
warn(
|
||||
try:
|
||||
super().__setattr__(name, value)
|
||||
except AttributeError as e:
|
||||
raise AttributeError(
|
||||
f"Setting variables on {self.__class__.__name__} instances is "
|
||||
"deprecated and will be removed in version 21.9. You should "
|
||||
f"change your {self.__class__.__name__} instance to use "
|
||||
"not allowed. You should change your "
|
||||
f"{self.__class__.__name__} instance to use "
|
||||
f"instance.ctx.{name} instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
super().__setattr__(name, value)
|
||||
) from e
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import MutableSequence
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, List, Optional, Union
|
||||
|
||||
|
||||
@@ -196,6 +197,27 @@ class BlueprintGroup(MutableSequence):
|
||||
"""
|
||||
self._blueprints.append(value)
|
||||
|
||||
def exception(self, *exceptions, **kwargs):
|
||||
"""
|
||||
A decorator that can be used to implement a global exception handler
|
||||
for all the Blueprints that belong to this Blueprint Group.
|
||||
|
||||
In case of nested Blueprint Groups, the same handler is applied
|
||||
across each of the Blueprints recursively.
|
||||
|
||||
:param args: List of Python exceptions to be caught by the handler
|
||||
:param kwargs: Additional optional arguments to be passed to the
|
||||
exception handler
|
||||
:return: a decorated method to handle global exceptions for any
|
||||
blueprint registered under this group.
|
||||
"""
|
||||
|
||||
def register_exception_handler_for_blueprints(fn):
|
||||
for blueprint in self.blueprints:
|
||||
blueprint.exception(*exceptions, **kwargs)(fn)
|
||||
|
||||
return register_exception_handler_for_blueprints
|
||||
|
||||
def insert(self, index: int, item: Blueprint) -> None:
|
||||
"""
|
||||
The Abstract class `MutableSequence` leverages this insert method to
|
||||
@@ -229,3 +251,15 @@ class BlueprintGroup(MutableSequence):
|
||||
args = list(args)[1:]
|
||||
return register_middleware_for_blueprints(fn)
|
||||
return register_middleware_for_blueprints
|
||||
|
||||
def on_request(self, middleware=None):
|
||||
if callable(middleware):
|
||||
return self.middleware(middleware, "request")
|
||||
else:
|
||||
return partial(self.middleware, attach_to="request")
|
||||
|
||||
def on_response(self, middleware=None):
|
||||
if callable(middleware):
|
||||
return self.middleware(middleware, "response")
|
||||
else:
|
||||
return partial(self.middleware, attach_to="response")
|
||||
|
||||
@@ -3,15 +3,31 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
from functools import wraps
|
||||
from inspect import isfunction
|
||||
from itertools import chain
|
||||
from types import SimpleNamespace
|
||||
from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Union
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Dict,
|
||||
Iterable,
|
||||
List,
|
||||
Optional,
|
||||
Sequence,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
from sanic_routing.exceptions import NotFound # type: ignore
|
||||
from sanic_routing.route import Route # type: ignore
|
||||
from sanic_routing.exceptions import NotFound
|
||||
from sanic_routing.route import Route
|
||||
|
||||
from sanic.base import BaseSanic
|
||||
from sanic.base.root import BaseSanic
|
||||
from sanic.blueprint_group import BlueprintGroup
|
||||
from sanic.exceptions import SanicException
|
||||
from sanic.helpers import Default, _default
|
||||
from sanic.models.futures import FutureRoute, FutureStatic
|
||||
from sanic.models.handler_types import (
|
||||
ListenerType,
|
||||
@@ -21,7 +37,33 @@ from sanic.models.handler_types import (
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sanic import Sanic # noqa
|
||||
from sanic import Sanic
|
||||
|
||||
|
||||
def lazy(func, as_decorator=True):
|
||||
@wraps(func)
|
||||
def decorator(bp, *args, **kwargs):
|
||||
nonlocal as_decorator
|
||||
kwargs["apply"] = False
|
||||
pass_handler = None
|
||||
|
||||
if args and isfunction(args[0]):
|
||||
as_decorator = False
|
||||
|
||||
def wrapper(handler):
|
||||
future = func(bp, *args, **kwargs)
|
||||
if as_decorator:
|
||||
future = future(handler)
|
||||
|
||||
if bp.registered:
|
||||
for app in bp.apps:
|
||||
bp.register(app, {})
|
||||
|
||||
return future
|
||||
|
||||
return wrapper if as_decorator else wrapper(pass_handler)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class Blueprint(BaseSanic):
|
||||
@@ -37,13 +79,13 @@ class Blueprint(BaseSanic):
|
||||
|
||||
:param name: unique name of the blueprint
|
||||
:param url_prefix: URL to be prefixed before all route URLs
|
||||
:param host: IP Address of FQDN for the sanic server to use.
|
||||
:param host: IP Address or FQDN for the sanic server to use.
|
||||
:param version: Blueprint Version
|
||||
:param strict_slashes: Enforce the API urls are requested with a
|
||||
training */*
|
||||
trailing */*
|
||||
"""
|
||||
|
||||
__fake_slots__ = (
|
||||
__slots__ = (
|
||||
"_apps",
|
||||
"_future_routes",
|
||||
"_future_statics",
|
||||
@@ -56,7 +98,6 @@ class Blueprint(BaseSanic):
|
||||
"host",
|
||||
"listeners",
|
||||
"middlewares",
|
||||
"name",
|
||||
"routes",
|
||||
"statics",
|
||||
"strict_slashes",
|
||||
@@ -70,21 +111,15 @@ class Blueprint(BaseSanic):
|
||||
self,
|
||||
name: str = None,
|
||||
url_prefix: Optional[str] = None,
|
||||
host: Optional[str] = None,
|
||||
host: Optional[Union[List[str], str]] = None,
|
||||
version: Optional[Union[int, str, float]] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
version_prefix: str = "/v",
|
||||
):
|
||||
super().__init__(name=name)
|
||||
|
||||
self._apps: Set[Sanic] = set()
|
||||
self.reset()
|
||||
self.ctx = SimpleNamespace()
|
||||
self.exceptions: List[RouteHandler] = []
|
||||
self.host = host
|
||||
self.listeners: Dict[str, List[ListenerType]] = {}
|
||||
self.middlewares: List[MiddlewareType] = []
|
||||
self.routes: List[Route] = []
|
||||
self.statics: List[RouteHandler] = []
|
||||
self.strict_slashes = strict_slashes
|
||||
self.url_prefix = (
|
||||
url_prefix[:-1]
|
||||
@@ -93,7 +128,6 @@ class Blueprint(BaseSanic):
|
||||
)
|
||||
self.version = version
|
||||
self.version_prefix = version_prefix
|
||||
self.websocket_routes: List[Route] = []
|
||||
|
||||
def __repr__(self) -> str:
|
||||
args = ", ".join(
|
||||
@@ -120,38 +154,100 @@ class Blueprint(BaseSanic):
|
||||
)
|
||||
return self._apps
|
||||
|
||||
def route(self, *args, **kwargs):
|
||||
kwargs["apply"] = False
|
||||
return super().route(*args, **kwargs)
|
||||
@property
|
||||
def registered(self) -> bool:
|
||||
return bool(self._apps)
|
||||
|
||||
def static(self, *args, **kwargs):
|
||||
kwargs["apply"] = False
|
||||
return super().static(*args, **kwargs)
|
||||
exception = lazy(BaseSanic.exception)
|
||||
listener = lazy(BaseSanic.listener)
|
||||
middleware = lazy(BaseSanic.middleware)
|
||||
route = lazy(BaseSanic.route)
|
||||
signal = lazy(BaseSanic.signal)
|
||||
static = lazy(BaseSanic.static, as_decorator=False)
|
||||
|
||||
def middleware(self, *args, **kwargs):
|
||||
kwargs["apply"] = False
|
||||
return super().middleware(*args, **kwargs)
|
||||
def reset(self):
|
||||
self._apps: Set[Sanic] = set()
|
||||
self.exceptions: List[RouteHandler] = []
|
||||
self.listeners: Dict[str, List[ListenerType[Any]]] = {}
|
||||
self.middlewares: List[MiddlewareType] = []
|
||||
self.routes: List[Route] = []
|
||||
self.statics: List[RouteHandler] = []
|
||||
self.websocket_routes: List[Route] = []
|
||||
|
||||
def listener(self, *args, **kwargs):
|
||||
kwargs["apply"] = False
|
||||
return super().listener(*args, **kwargs)
|
||||
def copy(
|
||||
self,
|
||||
name: str,
|
||||
url_prefix: Optional[Union[str, Default]] = _default,
|
||||
version: Optional[Union[int, str, float, Default]] = _default,
|
||||
version_prefix: Union[str, Default] = _default,
|
||||
strict_slashes: Optional[Union[bool, Default]] = _default,
|
||||
with_registration: bool = True,
|
||||
with_ctx: bool = False,
|
||||
):
|
||||
"""
|
||||
Copy a blueprint instance with some optional parameters to
|
||||
override the values of attributes in the old instance.
|
||||
|
||||
def exception(self, *args, **kwargs):
|
||||
kwargs["apply"] = False
|
||||
return super().exception(*args, **kwargs)
|
||||
:param name: unique name of the blueprint
|
||||
:param url_prefix: URL to be prefixed before all route URLs
|
||||
:param version: Blueprint Version
|
||||
:param version_prefix: the prefix of the version number shown in the
|
||||
URL.
|
||||
:param strict_slashes: Enforce the API urls are requested with a
|
||||
trailing */*
|
||||
:param with_registration: whether register new blueprint instance with
|
||||
sanic apps that were registered with the old instance or not.
|
||||
:param with_ctx: whether ``ctx`` will be copied or not.
|
||||
"""
|
||||
|
||||
def signal(self, event: str, *args, **kwargs):
|
||||
kwargs["apply"] = False
|
||||
return super().signal(event, *args, **kwargs)
|
||||
attrs_backup = {
|
||||
"_apps": self._apps,
|
||||
"routes": self.routes,
|
||||
"websocket_routes": self.websocket_routes,
|
||||
"middlewares": self.middlewares,
|
||||
"exceptions": self.exceptions,
|
||||
"listeners": self.listeners,
|
||||
"statics": self.statics,
|
||||
}
|
||||
|
||||
self.reset()
|
||||
new_bp = deepcopy(self)
|
||||
new_bp.name = name
|
||||
|
||||
if not isinstance(url_prefix, Default):
|
||||
new_bp.url_prefix = url_prefix
|
||||
if not isinstance(version, Default):
|
||||
new_bp.version = version
|
||||
if not isinstance(strict_slashes, Default):
|
||||
new_bp.strict_slashes = strict_slashes
|
||||
if not isinstance(version_prefix, Default):
|
||||
new_bp.version_prefix = version_prefix
|
||||
|
||||
for key, value in attrs_backup.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
if with_registration and self._apps:
|
||||
if new_bp._future_statics:
|
||||
raise SanicException(
|
||||
"Static routes registered with the old blueprint instance,"
|
||||
" cannot be registered again."
|
||||
)
|
||||
for app in self._apps:
|
||||
app.blueprint(new_bp)
|
||||
|
||||
if not with_ctx:
|
||||
new_bp.ctx = SimpleNamespace()
|
||||
|
||||
return new_bp
|
||||
|
||||
@staticmethod
|
||||
def group(
|
||||
*blueprints,
|
||||
url_prefix="",
|
||||
version=None,
|
||||
strict_slashes=None,
|
||||
*blueprints: Union[Blueprint, BlueprintGroup],
|
||||
url_prefix: Optional[str] = None,
|
||||
version: Optional[Union[int, str, float]] = None,
|
||||
strict_slashes: Optional[bool] = None,
|
||||
version_prefix: str = "/v",
|
||||
):
|
||||
) -> BlueprintGroup:
|
||||
"""
|
||||
Create a list of blueprints, optionally grouping them under a
|
||||
general URL prefix.
|
||||
@@ -196,11 +292,15 @@ class Blueprint(BaseSanic):
|
||||
opt_version = options.get("version", None)
|
||||
opt_strict_slashes = options.get("strict_slashes", None)
|
||||
opt_version_prefix = options.get("version_prefix", self.version_prefix)
|
||||
error_format = options.get(
|
||||
"error_format", app.config.FALLBACK_ERROR_FORMAT
|
||||
)
|
||||
|
||||
routes = []
|
||||
middleware = []
|
||||
exception_handlers = []
|
||||
listeners = defaultdict(list)
|
||||
registered = set()
|
||||
|
||||
# Routes
|
||||
for future in self._future_routes:
|
||||
@@ -208,7 +308,7 @@ class Blueprint(BaseSanic):
|
||||
# prefixed properly in the router
|
||||
future.handler.__blueprintname__ = self.name
|
||||
# Prepend the blueprint URI prefix if available
|
||||
uri = url_prefix + future.uri if url_prefix else future.uri
|
||||
uri = self._setup_uri(future.uri, url_prefix)
|
||||
|
||||
version_prefix = self.version_prefix
|
||||
for prefix in (
|
||||
@@ -227,12 +327,15 @@ class Blueprint(BaseSanic):
|
||||
)
|
||||
|
||||
name = app._generate_name(future.name)
|
||||
host = future.host or self.host
|
||||
if isinstance(host, list):
|
||||
host = tuple(host)
|
||||
|
||||
apply_route = FutureRoute(
|
||||
future.handler,
|
||||
uri[1:] if uri.startswith("//") else uri,
|
||||
uri,
|
||||
future.methods,
|
||||
future.host or self.host,
|
||||
host,
|
||||
strict_slashes,
|
||||
future.stream,
|
||||
version,
|
||||
@@ -243,8 +346,14 @@ class Blueprint(BaseSanic):
|
||||
future.unquote,
|
||||
future.static,
|
||||
version_prefix,
|
||||
error_format,
|
||||
future.route_context,
|
||||
)
|
||||
|
||||
if (self, apply_route) in app._future_registry:
|
||||
continue
|
||||
|
||||
registered.add(apply_route)
|
||||
route = app._apply_route(apply_route)
|
||||
operation = (
|
||||
routes.extend if isinstance(route, list) else routes.append
|
||||
@@ -254,41 +363,71 @@ class Blueprint(BaseSanic):
|
||||
# Static Files
|
||||
for future in self._future_statics:
|
||||
# Prepend the blueprint URI prefix if available
|
||||
uri = url_prefix + future.uri if url_prefix else future.uri
|
||||
uri = self._setup_uri(future.uri, url_prefix)
|
||||
apply_route = FutureStatic(uri, *future[1:])
|
||||
|
||||
if (self, apply_route) in app._future_registry:
|
||||
continue
|
||||
|
||||
registered.add(apply_route)
|
||||
route = app._apply_static(apply_route)
|
||||
routes.append(route)
|
||||
|
||||
route_names = [route.name for route in routes if route]
|
||||
|
||||
# Middleware
|
||||
if route_names:
|
||||
# Middleware
|
||||
for future in self._future_middleware:
|
||||
if (self, future) in app._future_registry:
|
||||
continue
|
||||
middleware.append(app._apply_middleware(future, route_names))
|
||||
|
||||
# Exceptions
|
||||
for future in self._future_exceptions:
|
||||
exception_handlers.append(app._apply_exception_handler(future))
|
||||
# Exceptions
|
||||
for future in self._future_exceptions:
|
||||
if (self, future) in app._future_registry:
|
||||
continue
|
||||
exception_handlers.append(
|
||||
app._apply_exception_handler(future, route_names)
|
||||
)
|
||||
|
||||
# Event listeners
|
||||
for listener in self._future_listeners:
|
||||
listeners[listener.event].append(app._apply_listener(listener))
|
||||
for future in self._future_listeners:
|
||||
if (self, future) in app._future_registry:
|
||||
continue
|
||||
listeners[future.event].append(app._apply_listener(future))
|
||||
|
||||
for signal in self._future_signals:
|
||||
signal.condition.update({"blueprint": self.name})
|
||||
app._apply_signal(signal)
|
||||
# Signals
|
||||
for future in self._future_signals:
|
||||
if (self, future) in app._future_registry:
|
||||
continue
|
||||
future.condition.update({"__blueprint__": self.name})
|
||||
# Force exclusive to be False
|
||||
app._apply_signal(tuple((*future[:-1], False)))
|
||||
|
||||
self.routes = [route for route in routes if isinstance(route, Route)]
|
||||
self.websocket_routes = [
|
||||
route for route in self.routes if route.ctx.websocket
|
||||
self.routes += [route for route in routes if isinstance(route, Route)]
|
||||
self.websocket_routes += [
|
||||
route for route in self.routes if route.extra.websocket
|
||||
]
|
||||
self.middlewares = middleware
|
||||
self.exceptions = exception_handlers
|
||||
self.listeners = dict(listeners)
|
||||
self.middlewares += middleware
|
||||
self.exceptions += exception_handlers
|
||||
self.listeners.update(dict(listeners))
|
||||
|
||||
if self.registered:
|
||||
self.register_futures(
|
||||
self.apps,
|
||||
self,
|
||||
chain(
|
||||
registered,
|
||||
self._future_middleware,
|
||||
self._future_exceptions,
|
||||
self._future_listeners,
|
||||
self._future_signals,
|
||||
),
|
||||
)
|
||||
|
||||
async def dispatch(self, *args, **kwargs):
|
||||
condition = kwargs.pop("condition", {})
|
||||
condition.update({"blueprint": self.name})
|
||||
condition.update({"__blueprint__": self.name})
|
||||
kwargs["condition"] = condition
|
||||
await asyncio.gather(
|
||||
*[app.dispatch(*args, **kwargs) for app in self.apps]
|
||||
@@ -316,3 +455,22 @@ class Blueprint(BaseSanic):
|
||||
value = v
|
||||
break
|
||||
return value
|
||||
|
||||
@staticmethod
|
||||
def _setup_uri(base: str, prefix: Optional[str]):
|
||||
uri = base
|
||||
if prefix:
|
||||
uri = prefix
|
||||
if base.startswith("/") and prefix.endswith("/"):
|
||||
uri += base[1:]
|
||||
else:
|
||||
uri += base
|
||||
|
||||
return uri[1:] if uri.startswith("//") else uri
|
||||
|
||||
@staticmethod
|
||||
def register_futures(
|
||||
apps: Set[Sanic], bp: Blueprint, futures: Sequence[Tuple[Any, ...]]
|
||||
):
|
||||
for app in apps:
|
||||
app._future_registry.update(set((bp, item) for item in futures))
|
||||
|
||||
0
sanic/cli/__init__.py
Normal file
0
sanic/cli/__init__.py
Normal file
208
sanic/cli/app.py
Normal file
208
sanic/cli/app.py
Normal file
@@ -0,0 +1,208 @@
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
from argparse import ArgumentParser, RawTextHelpFormatter
|
||||
from functools import partial
|
||||
from textwrap import indent
|
||||
from typing import Any, List, Union
|
||||
|
||||
from sanic.app import Sanic
|
||||
from sanic.application.logo import get_logo
|
||||
from sanic.cli.arguments import Group
|
||||
from sanic.log import error_logger
|
||||
from sanic.worker.inspector import inspect
|
||||
from sanic.worker.loader import AppLoader
|
||||
|
||||
|
||||
class SanicArgumentParser(ArgumentParser):
|
||||
...
|
||||
|
||||
|
||||
class SanicCLI:
|
||||
DESCRIPTION = indent(
|
||||
f"""
|
||||
{get_logo(True)}
|
||||
|
||||
To start running a Sanic application, provide a path to the module, where
|
||||
app is a Sanic() instance:
|
||||
|
||||
$ sanic path.to.server:app
|
||||
|
||||
Or, a path to a callable that returns a Sanic() instance:
|
||||
|
||||
$ sanic path.to.factory:create_app --factory
|
||||
|
||||
Or, a path to a directory to run as a simple HTTP server:
|
||||
|
||||
$ sanic ./path/to/static --simple
|
||||
""",
|
||||
prefix=" ",
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
width = shutil.get_terminal_size().columns
|
||||
self.parser = SanicArgumentParser(
|
||||
prog="sanic",
|
||||
description=self.DESCRIPTION,
|
||||
formatter_class=lambda prog: RawTextHelpFormatter(
|
||||
prog,
|
||||
max_help_position=36 if width > 96 else 24,
|
||||
indent_increment=4,
|
||||
width=None,
|
||||
),
|
||||
)
|
||||
self.parser._positionals.title = "Required\n========\n Positional"
|
||||
self.parser._optionals.title = "Optional\n========\n General"
|
||||
self.main_process = (
|
||||
os.environ.get("SANIC_RELOADER_PROCESS", "") != "true"
|
||||
)
|
||||
self.args: List[Any] = []
|
||||
self.groups: List[Group] = []
|
||||
|
||||
def attach(self):
|
||||
for group in Group._registry:
|
||||
instance = group.create(self.parser)
|
||||
instance.attach()
|
||||
self.groups.append(instance)
|
||||
|
||||
def run(self, parse_args=None):
|
||||
legacy_version = False
|
||||
if not parse_args:
|
||||
# This is to provide backwards compat -v to display version
|
||||
legacy_version = len(sys.argv) == 2 and sys.argv[-1] == "-v"
|
||||
parse_args = ["--version"] if legacy_version else None
|
||||
elif parse_args == ["-v"]:
|
||||
parse_args = ["--version"]
|
||||
|
||||
if not legacy_version:
|
||||
parsed, unknown = self.parser.parse_known_args(args=parse_args)
|
||||
if unknown and parsed.factory:
|
||||
for arg in unknown:
|
||||
if arg.startswith("--"):
|
||||
self.parser.add_argument(arg.split("=")[0])
|
||||
|
||||
self.args = self.parser.parse_args(args=parse_args)
|
||||
self._precheck()
|
||||
app_loader = AppLoader(
|
||||
self.args.module,
|
||||
self.args.factory,
|
||||
self.args.simple,
|
||||
self.args,
|
||||
)
|
||||
|
||||
try:
|
||||
app = self._get_app(app_loader)
|
||||
kwargs = self._build_run_kwargs()
|
||||
except ValueError as e:
|
||||
error_logger.exception(f"Failed to run app: {e}")
|
||||
else:
|
||||
if self.args.inspect or self.args.inspect_raw or self.args.trigger:
|
||||
os.environ["SANIC_IGNORE_PRODUCTION_WARNING"] = "true"
|
||||
else:
|
||||
for http_version in self.args.http:
|
||||
app.prepare(**kwargs, version=http_version)
|
||||
|
||||
if self.args.inspect or self.args.inspect_raw or self.args.trigger:
|
||||
action = self.args.trigger or (
|
||||
"raw" if self.args.inspect_raw else "pretty"
|
||||
)
|
||||
inspect(
|
||||
app.config.INSPECTOR_HOST,
|
||||
app.config.INSPECTOR_PORT,
|
||||
action,
|
||||
)
|
||||
del os.environ["SANIC_IGNORE_PRODUCTION_WARNING"]
|
||||
return
|
||||
|
||||
if self.args.single:
|
||||
serve = Sanic.serve_single
|
||||
elif self.args.legacy:
|
||||
serve = Sanic.serve_legacy
|
||||
else:
|
||||
serve = partial(Sanic.serve, app_loader=app_loader)
|
||||
serve(app)
|
||||
|
||||
def _precheck(self):
|
||||
# Custom TLS mismatch handling for better diagnostics
|
||||
if self.main_process and (
|
||||
# one of cert/key missing
|
||||
bool(self.args.cert) != bool(self.args.key)
|
||||
# new and old style self.args used together
|
||||
or self.args.tls
|
||||
and self.args.cert
|
||||
# strict host checking without certs would always fail
|
||||
or self.args.tlshost
|
||||
and not self.args.tls
|
||||
and not self.args.cert
|
||||
):
|
||||
self.parser.print_usage(sys.stderr)
|
||||
message = (
|
||||
"TLS certificates must be specified by either of:\n"
|
||||
" --cert certdir/fullchain.pem --key certdir/privkey.pem\n"
|
||||
" --tls certdir (equivalent to the above)"
|
||||
)
|
||||
error_logger.error(message)
|
||||
sys.exit(1)
|
||||
if self.args.inspect or self.args.inspect_raw:
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def _get_app(self, app_loader: AppLoader):
|
||||
try:
|
||||
app = app_loader.load()
|
||||
except ImportError as e:
|
||||
if app_loader.module_name.startswith(e.name): # type: ignore
|
||||
error_logger.error(
|
||||
f"No module named {e.name} found.\n"
|
||||
" Example File: project/sanic_server.py -> app\n"
|
||||
" Example Module: project.sanic_server.app"
|
||||
)
|
||||
sys.exit(1)
|
||||
else:
|
||||
raise e
|
||||
return app
|
||||
|
||||
def _build_run_kwargs(self):
|
||||
for group in self.groups:
|
||||
group.prepare(self.args)
|
||||
ssl: Union[None, dict, str, list] = []
|
||||
if self.args.tlshost:
|
||||
ssl.append(None)
|
||||
if self.args.cert is not None or self.args.key is not None:
|
||||
ssl.append(dict(cert=self.args.cert, key=self.args.key))
|
||||
if self.args.tls:
|
||||
ssl += self.args.tls
|
||||
if not ssl:
|
||||
ssl = None
|
||||
elif len(ssl) == 1 and ssl[0] is not None:
|
||||
# Use only one cert, no TLSSelector.
|
||||
ssl = ssl[0]
|
||||
|
||||
kwargs = {
|
||||
"access_log": self.args.access_log,
|
||||
"coffee": self.args.coffee,
|
||||
"debug": self.args.debug,
|
||||
"fast": self.args.fast,
|
||||
"host": self.args.host,
|
||||
"motd": self.args.motd,
|
||||
"noisy_exceptions": self.args.noisy_exceptions,
|
||||
"port": self.args.port,
|
||||
"ssl": ssl,
|
||||
"unix": self.args.unix,
|
||||
"verbosity": self.args.verbosity or 0,
|
||||
"workers": self.args.workers,
|
||||
"auto_tls": self.args.auto_tls,
|
||||
"single_process": self.args.single,
|
||||
"legacy": self.args.legacy,
|
||||
}
|
||||
|
||||
for maybe_arg in ("auto_reload", "dev"):
|
||||
if getattr(self.args, maybe_arg, False):
|
||||
kwargs[maybe_arg] = True
|
||||
|
||||
if self.args.path:
|
||||
kwargs["auto_reload"] = True
|
||||
kwargs["reload_dir"] = self.args.path
|
||||
|
||||
return kwargs
|
||||
331
sanic/cli/arguments.py
Normal file
331
sanic/cli/arguments.py
Normal file
@@ -0,0 +1,331 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from argparse import ArgumentParser, _ArgumentGroup
|
||||
from typing import List, Optional, Type, Union
|
||||
|
||||
from sanic_routing import __version__ as __routing_version__
|
||||
|
||||
from sanic import __version__
|
||||
from sanic.http.constants import HTTP
|
||||
|
||||
|
||||
class Group:
|
||||
name: Optional[str]
|
||||
container: Union[ArgumentParser, _ArgumentGroup]
|
||||
_registry: List[Type[Group]] = []
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
Group._registry.append(cls)
|
||||
|
||||
def __init__(self, parser: ArgumentParser, title: Optional[str]):
|
||||
self.parser = parser
|
||||
|
||||
if title:
|
||||
self.container = self.parser.add_argument_group(title=f" {title}")
|
||||
else:
|
||||
self.container = self.parser
|
||||
|
||||
@classmethod
|
||||
def create(cls, parser: ArgumentParser):
|
||||
instance = cls(parser, cls.name)
|
||||
return instance
|
||||
|
||||
def add_bool_arguments(self, *args, nullable=False, **kwargs):
|
||||
group = self.container.add_mutually_exclusive_group()
|
||||
kwargs["help"] = kwargs["help"].capitalize()
|
||||
group.add_argument(*args, action="store_true", **kwargs)
|
||||
kwargs["help"] = f"no {kwargs['help'].lower()}".capitalize()
|
||||
group.add_argument(
|
||||
"--no-" + args[0][2:], *args[1:], action="store_false", **kwargs
|
||||
)
|
||||
if nullable:
|
||||
params = {args[0][2:].replace("-", "_"): None}
|
||||
group.set_defaults(**params)
|
||||
|
||||
def prepare(self, args) -> None:
|
||||
...
|
||||
|
||||
|
||||
class GeneralGroup(Group):
|
||||
name = None
|
||||
|
||||
def attach(self):
|
||||
self.container.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"Sanic {__version__}; Routing {__routing_version__}",
|
||||
)
|
||||
|
||||
self.container.add_argument(
|
||||
"module",
|
||||
help=(
|
||||
"Path to your Sanic app. Example: path.to.server:app\n"
|
||||
"If running a Simple Server, path to directory to serve. "
|
||||
"Example: ./\n"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ApplicationGroup(Group):
|
||||
name = "Application"
|
||||
|
||||
def attach(self):
|
||||
group = self.container.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
"--factory",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Treat app as an application factory, "
|
||||
"i.e. a () -> <Sanic app> callable"
|
||||
),
|
||||
)
|
||||
group.add_argument(
|
||||
"-s",
|
||||
"--simple",
|
||||
dest="simple",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Run Sanic as a Simple Server, and serve the contents of "
|
||||
"a directory\n(module arg should be a path)"
|
||||
),
|
||||
)
|
||||
group.add_argument(
|
||||
"--inspect",
|
||||
dest="inspect",
|
||||
action="store_true",
|
||||
help=("Inspect the state of a running instance, human readable"),
|
||||
)
|
||||
group.add_argument(
|
||||
"--inspect-raw",
|
||||
dest="inspect_raw",
|
||||
action="store_true",
|
||||
help=("Inspect the state of a running instance, JSON output"),
|
||||
)
|
||||
group.add_argument(
|
||||
"--trigger-reload",
|
||||
dest="trigger",
|
||||
action="store_const",
|
||||
const="reload",
|
||||
help=("Trigger worker processes to reload"),
|
||||
)
|
||||
group.add_argument(
|
||||
"--trigger-shutdown",
|
||||
dest="trigger",
|
||||
action="store_const",
|
||||
const="shutdown",
|
||||
help=("Trigger all processes to shutdown"),
|
||||
)
|
||||
|
||||
|
||||
class HTTPVersionGroup(Group):
|
||||
name = "HTTP version"
|
||||
|
||||
def attach(self):
|
||||
http_values = [http.value for http in HTTP.__members__.values()]
|
||||
|
||||
self.container.add_argument(
|
||||
"--http",
|
||||
dest="http",
|
||||
action="append",
|
||||
choices=http_values,
|
||||
type=int,
|
||||
help=(
|
||||
"Which HTTP version to use: HTTP/1.1 or HTTP/3. Value should\n"
|
||||
"be either 1, or 3. [default 1]"
|
||||
),
|
||||
)
|
||||
self.container.add_argument(
|
||||
"-1",
|
||||
dest="http",
|
||||
action="append_const",
|
||||
const=1,
|
||||
help=("Run Sanic server using HTTP/1.1"),
|
||||
)
|
||||
self.container.add_argument(
|
||||
"-3",
|
||||
dest="http",
|
||||
action="append_const",
|
||||
const=3,
|
||||
help=("Run Sanic server using HTTP/3"),
|
||||
)
|
||||
|
||||
def prepare(self, args):
|
||||
if not args.http:
|
||||
args.http = [1]
|
||||
args.http = tuple(sorted(set(map(HTTP, args.http)), reverse=True))
|
||||
|
||||
|
||||
class SocketGroup(Group):
|
||||
name = "Socket binding"
|
||||
|
||||
def attach(self):
|
||||
self.container.add_argument(
|
||||
"-H",
|
||||
"--host",
|
||||
dest="host",
|
||||
type=str,
|
||||
help="Host address [default 127.0.0.1]",
|
||||
)
|
||||
self.container.add_argument(
|
||||
"-p",
|
||||
"--port",
|
||||
dest="port",
|
||||
type=int,
|
||||
help="Port to serve on [default 8000]",
|
||||
)
|
||||
self.container.add_argument(
|
||||
"-u",
|
||||
"--unix",
|
||||
dest="unix",
|
||||
type=str,
|
||||
default="",
|
||||
help="location of unix socket",
|
||||
)
|
||||
|
||||
|
||||
class TLSGroup(Group):
|
||||
name = "TLS certificate"
|
||||
|
||||
def attach(self):
|
||||
self.container.add_argument(
|
||||
"--cert",
|
||||
dest="cert",
|
||||
type=str,
|
||||
help="Location of fullchain.pem, bundle.crt or equivalent",
|
||||
)
|
||||
self.container.add_argument(
|
||||
"--key",
|
||||
dest="key",
|
||||
type=str,
|
||||
help="Location of privkey.pem or equivalent .key file",
|
||||
)
|
||||
self.container.add_argument(
|
||||
"--tls",
|
||||
metavar="DIR",
|
||||
type=str,
|
||||
action="append",
|
||||
help=(
|
||||
"TLS certificate folder with fullchain.pem and privkey.pem\n"
|
||||
"May be specified multiple times to choose multiple "
|
||||
"certificates"
|
||||
),
|
||||
)
|
||||
self.container.add_argument(
|
||||
"--tls-strict-host",
|
||||
dest="tlshost",
|
||||
action="store_true",
|
||||
help="Only allow clients that send an SNI matching server certs",
|
||||
)
|
||||
|
||||
|
||||
class WorkerGroup(Group):
|
||||
name = "Worker"
|
||||
|
||||
def attach(self):
|
||||
group = self.container.add_mutually_exclusive_group()
|
||||
group.add_argument(
|
||||
"-w",
|
||||
"--workers",
|
||||
dest="workers",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Number of worker processes [default 1]",
|
||||
)
|
||||
group.add_argument(
|
||||
"--fast",
|
||||
dest="fast",
|
||||
action="store_true",
|
||||
help="Set the number of workers to max allowed",
|
||||
)
|
||||
group.add_argument(
|
||||
"--single-process",
|
||||
dest="single",
|
||||
action="store_true",
|
||||
help="Do not use multiprocessing, run server in a single process",
|
||||
)
|
||||
self.container.add_argument(
|
||||
"--legacy",
|
||||
action="store_true",
|
||||
help="Use the legacy server manager",
|
||||
)
|
||||
self.add_bool_arguments(
|
||||
"--access-logs",
|
||||
dest="access_log",
|
||||
help="display access logs",
|
||||
default=None,
|
||||
)
|
||||
|
||||
|
||||
class DevelopmentGroup(Group):
|
||||
name = "Development"
|
||||
|
||||
def attach(self):
|
||||
self.container.add_argument(
|
||||
"--debug",
|
||||
dest="debug",
|
||||
action="store_true",
|
||||
help="Run the server in debug mode",
|
||||
)
|
||||
self.container.add_argument(
|
||||
"-r",
|
||||
"--reload",
|
||||
"--auto-reload",
|
||||
dest="auto_reload",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Watch source directory for file changes and reload on "
|
||||
"changes"
|
||||
),
|
||||
)
|
||||
self.container.add_argument(
|
||||
"-R",
|
||||
"--reload-dir",
|
||||
dest="path",
|
||||
action="append",
|
||||
help="Extra directories to watch and reload on changes",
|
||||
)
|
||||
self.container.add_argument(
|
||||
"-d",
|
||||
"--dev",
|
||||
dest="dev",
|
||||
action="store_true",
|
||||
help=("debug + auto reload"),
|
||||
)
|
||||
self.container.add_argument(
|
||||
"--auto-tls",
|
||||
dest="auto_tls",
|
||||
action="store_true",
|
||||
help=(
|
||||
"Create a temporary TLS certificate for local development "
|
||||
"(requires mkcert or trustme)"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class OutputGroup(Group):
|
||||
name = "Output"
|
||||
|
||||
def attach(self):
|
||||
self.add_bool_arguments(
|
||||
"--coffee",
|
||||
dest="coffee",
|
||||
default=False,
|
||||
help="Uhm, coffee?",
|
||||
)
|
||||
self.add_bool_arguments(
|
||||
"--motd",
|
||||
dest="motd",
|
||||
default=True,
|
||||
help="Show the startup display",
|
||||
)
|
||||
self.container.add_argument(
|
||||
"-v",
|
||||
"--verbosity",
|
||||
action="count",
|
||||
help="Control logging noise, eg. -vv or --verbosity=2 [default 0]",
|
||||
)
|
||||
self.add_bool_arguments(
|
||||
"--noisy-exceptions",
|
||||
dest="noisy_exceptions",
|
||||
help="Output stack traces for all exceptions",
|
||||
)
|
||||
@@ -1,13 +1,55 @@
|
||||
import asyncio
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from sys import argv
|
||||
from enum import Enum
|
||||
from typing import Awaitable
|
||||
|
||||
from multidict import CIMultiDict # type: ignore
|
||||
|
||||
|
||||
OS_IS_WINDOWS = os.name == "nt"
|
||||
UVLOOP_INSTALLED = False
|
||||
|
||||
try:
|
||||
import uvloop # type: ignore # noqa
|
||||
|
||||
UVLOOP_INSTALLED = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
# Python 3.11 changed the way Enum formatting works for mixed-in types.
|
||||
if sys.version_info < (3, 11, 0):
|
||||
|
||||
class StrEnum(str, Enum):
|
||||
pass
|
||||
|
||||
else:
|
||||
from enum import StrEnum # type: ignore # noqa
|
||||
|
||||
|
||||
class UpperStrEnum(StrEnum):
|
||||
def _generate_next_value_(name, start, count, last_values):
|
||||
return name.upper()
|
||||
|
||||
def __eq__(self, value: object) -> bool:
|
||||
value = str(value).upper()
|
||||
return super().__eq__(value)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.value)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
|
||||
def enable_windows_color_support():
|
||||
import ctypes
|
||||
|
||||
kernel = ctypes.windll.kernel32
|
||||
kernel.SetConsoleMode(kernel.GetStdHandle(-11), 7)
|
||||
|
||||
|
||||
class Header(CIMultiDict):
|
||||
@@ -32,12 +74,12 @@ class Header(CIMultiDict):
|
||||
return self.getall(key, default=[])
|
||||
|
||||
|
||||
use_trio = argv[0].endswith("hypercorn") and "trio" in argv
|
||||
use_trio = sys.argv[0].endswith("hypercorn") and "trio" in sys.argv
|
||||
|
||||
if use_trio: # pragma: no cover
|
||||
import trio # type: ignore
|
||||
|
||||
def stat_async(path):
|
||||
def stat_async(path) -> Awaitable[os.stat_result]:
|
||||
return trio.Path(path).stat()
|
||||
|
||||
open_async = trio.open_file
|
||||
@@ -57,7 +99,7 @@ def ctrlc_workaround_for_windows(app):
|
||||
"""Asyncio wakeups to allow receiving SIGINT in Python"""
|
||||
while not die:
|
||||
# If someone else stopped the app, just exit
|
||||
if app.is_stopping:
|
||||
if app.state.is_stopping:
|
||||
return
|
||||
# Windows Python blocks signal handlers while the event loop is
|
||||
# waiting for I/O. Frequent wakeups keep interrupts flowing.
|
||||
@@ -74,3 +116,7 @@ def ctrlc_workaround_for_windows(app):
|
||||
die = False
|
||||
signal.signal(signal.SIGINT, ctrlc_handler)
|
||||
app.add_task(stay_active)
|
||||
|
||||
|
||||
def is_atty() -> bool:
|
||||
return bool(sys.stdout and sys.stdout.isatty())
|
||||
|
||||
261
sanic/config.py
261
sanic/config.py
@@ -1,85 +1,142 @@
|
||||
from inspect import isclass
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from inspect import getmembers, isclass, isdatadescriptor
|
||||
from os import environ
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional, Union
|
||||
from warnings import warn
|
||||
from typing import Any, Callable, Dict, Optional, Sequence, Union
|
||||
from warnings import filterwarnings
|
||||
|
||||
from sanic.constants import LocalCertCreator, RestartOrder
|
||||
from sanic.errorpages import DEFAULT_FORMAT, check_error_format
|
||||
from sanic.helpers import Default, _default
|
||||
from sanic.http import Http
|
||||
from sanic.log import error_logger
|
||||
from sanic.utils import load_module_from_file_location, str_to_bool
|
||||
|
||||
from .utils import load_module_from_file_location, str_to_bool
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
from typing import Literal
|
||||
|
||||
FilterWarningType = Union[
|
||||
Literal["default"],
|
||||
Literal["error"],
|
||||
Literal["ignore"],
|
||||
Literal["always"],
|
||||
Literal["module"],
|
||||
Literal["once"],
|
||||
]
|
||||
else:
|
||||
FilterWarningType = str
|
||||
|
||||
SANIC_PREFIX = "SANIC_"
|
||||
BASE_LOGO = """
|
||||
|
||||
Sanic
|
||||
Build Fast. Run Fast.
|
||||
|
||||
"""
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"ACCESS_LOG": True,
|
||||
"_FALLBACK_ERROR_FORMAT": _default,
|
||||
"ACCESS_LOG": False,
|
||||
"AUTO_EXTEND": True,
|
||||
"AUTO_RELOAD": False,
|
||||
"EVENT_AUTOREGISTER": False,
|
||||
"FALLBACK_ERROR_FORMAT": "html",
|
||||
"DEPRECATION_FILTER": "once",
|
||||
"FORWARDED_FOR_HEADER": "X-Forwarded-For",
|
||||
"FORWARDED_SECRET": None,
|
||||
"GRACEFUL_SHUTDOWN_TIMEOUT": 15.0, # 15 sec
|
||||
"INSPECTOR": False,
|
||||
"INSPECTOR_HOST": "localhost",
|
||||
"INSPECTOR_PORT": 6457,
|
||||
"KEEP_ALIVE_TIMEOUT": 5, # 5 seconds
|
||||
"KEEP_ALIVE": True,
|
||||
"LOCAL_CERT_CREATOR": LocalCertCreator.AUTO,
|
||||
"LOCAL_TLS_KEY": _default,
|
||||
"LOCAL_TLS_CERT": _default,
|
||||
"LOCALHOST": "localhost",
|
||||
"MOTD": True,
|
||||
"MOTD_DISPLAY": {},
|
||||
"NOISY_EXCEPTIONS": False,
|
||||
"PROXIES_COUNT": None,
|
||||
"REAL_IP_HEADER": None,
|
||||
"REGISTER": True,
|
||||
"REQUEST_BUFFER_SIZE": 65536, # 64 KiB
|
||||
"REQUEST_MAX_HEADER_SIZE": 8192, # 8 KiB, but cannot exceed 16384
|
||||
"REQUEST_ID_HEADER": "X-Request-ID",
|
||||
"REQUEST_MAX_SIZE": 100000000, # 100 megabytes
|
||||
"REQUEST_TIMEOUT": 60, # 60 seconds
|
||||
"RESPONSE_TIMEOUT": 60, # 60 seconds
|
||||
"WEBSOCKET_MAX_QUEUE": 32,
|
||||
"WEBSOCKET_MAX_SIZE": 2 ** 20, # 1 megabyte
|
||||
"RESTART_ORDER": RestartOrder.SHUTDOWN_FIRST,
|
||||
"TLS_CERT_PASSWORD": "",
|
||||
"TOUCHUP": _default,
|
||||
"USE_UVLOOP": _default,
|
||||
"WEBSOCKET_MAX_SIZE": 2**20, # 1 megabyte
|
||||
"WEBSOCKET_PING_INTERVAL": 20,
|
||||
"WEBSOCKET_PING_TIMEOUT": 20,
|
||||
"WEBSOCKET_READ_LIMIT": 2 ** 16,
|
||||
"WEBSOCKET_WRITE_LIMIT": 2 ** 16,
|
||||
}
|
||||
|
||||
|
||||
class Config(dict):
|
||||
class DescriptorMeta(type):
|
||||
def __init__(cls, *_):
|
||||
cls.__setters__ = {name for name, _ in getmembers(cls, cls._is_setter)}
|
||||
|
||||
@staticmethod
|
||||
def _is_setter(member: object):
|
||||
return isdatadescriptor(member) and hasattr(member, "setter")
|
||||
|
||||
|
||||
class Config(dict, metaclass=DescriptorMeta):
|
||||
ACCESS_LOG: bool
|
||||
AUTO_EXTEND: bool
|
||||
AUTO_RELOAD: bool
|
||||
EVENT_AUTOREGISTER: bool
|
||||
FALLBACK_ERROR_FORMAT: str
|
||||
DEPRECATION_FILTER: FilterWarningType
|
||||
FORWARDED_FOR_HEADER: str
|
||||
FORWARDED_SECRET: Optional[str]
|
||||
GRACEFUL_SHUTDOWN_TIMEOUT: float
|
||||
INSPECTOR: bool
|
||||
INSPECTOR_HOST: str
|
||||
INSPECTOR_PORT: int
|
||||
KEEP_ALIVE_TIMEOUT: int
|
||||
KEEP_ALIVE: bool
|
||||
LOCAL_CERT_CREATOR: Union[str, LocalCertCreator]
|
||||
LOCAL_TLS_KEY: Union[Path, str, Default]
|
||||
LOCAL_TLS_CERT: Union[Path, str, Default]
|
||||
LOCALHOST: str
|
||||
MOTD: bool
|
||||
MOTD_DISPLAY: Dict[str, str]
|
||||
NOISY_EXCEPTIONS: bool
|
||||
PROXIES_COUNT: Optional[int]
|
||||
REAL_IP_HEADER: Optional[str]
|
||||
REGISTER: bool
|
||||
REQUEST_BUFFER_SIZE: int
|
||||
REQUEST_MAX_HEADER_SIZE: int
|
||||
REQUEST_ID_HEADER: str
|
||||
REQUEST_MAX_SIZE: int
|
||||
REQUEST_TIMEOUT: int
|
||||
RESPONSE_TIMEOUT: int
|
||||
WEBSOCKET_MAX_QUEUE: int
|
||||
RESTART_ORDER: Union[str, RestartOrder]
|
||||
SERVER_NAME: str
|
||||
TLS_CERT_PASSWORD: str
|
||||
TOUCHUP: Union[Default, bool]
|
||||
USE_UVLOOP: Union[Default, bool]
|
||||
WEBSOCKET_MAX_SIZE: int
|
||||
WEBSOCKET_PING_INTERVAL: int
|
||||
WEBSOCKET_PING_TIMEOUT: int
|
||||
WEBSOCKET_READ_LIMIT: int
|
||||
WEBSOCKET_WRITE_LIMIT: int
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
defaults: Dict[str, Union[str, bool, int, float, None]] = None,
|
||||
load_env: Optional[Union[bool, str]] = True,
|
||||
env_prefix: Optional[str] = SANIC_PREFIX,
|
||||
keep_alive: Optional[bool] = None,
|
||||
*,
|
||||
converters: Optional[Sequence[Callable[[str], Any]]] = None,
|
||||
):
|
||||
defaults = defaults or {}
|
||||
super().__init__({**DEFAULT_CONFIG, **defaults})
|
||||
self._configure_warnings()
|
||||
|
||||
self.LOGO = BASE_LOGO
|
||||
self._converters = [str, str_to_bool, float, int]
|
||||
|
||||
if converters:
|
||||
for converter in converters:
|
||||
self.register_type(converter)
|
||||
|
||||
if keep_alive is not None:
|
||||
self.KEEP_ALIVE = keep_alive
|
||||
@@ -87,34 +144,83 @@ class Config(dict):
|
||||
if env_prefix != SANIC_PREFIX:
|
||||
if env_prefix:
|
||||
self.load_environment_vars(env_prefix)
|
||||
elif load_env is not True:
|
||||
if load_env:
|
||||
self.load_environment_vars(prefix=load_env)
|
||||
warn(
|
||||
"Use of load_env is deprecated and will be removed in "
|
||||
"21.12. Modify the configuration prefix by passing "
|
||||
"env_prefix instead.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
else:
|
||||
self.load_environment_vars(SANIC_PREFIX)
|
||||
|
||||
self._configure_header_size()
|
||||
self._check_error_format()
|
||||
self._init = True
|
||||
|
||||
def __getattr__(self, attr):
|
||||
def __getattr__(self, attr: Any):
|
||||
try:
|
||||
return self[attr]
|
||||
except KeyError as ke:
|
||||
raise AttributeError(f"Config has no '{ke.args[0]}'")
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
self[attr] = value
|
||||
if attr in (
|
||||
"REQUEST_MAX_HEADER_SIZE",
|
||||
"REQUEST_BUFFER_SIZE",
|
||||
"REQUEST_MAX_SIZE",
|
||||
def __setattr__(self, attr: str, value: Any) -> None:
|
||||
self.update({attr: value})
|
||||
|
||||
def __setitem__(self, attr: str, value: Any) -> None:
|
||||
self.update({attr: value})
|
||||
|
||||
def update(self, *other: Any, **kwargs: Any) -> None:
|
||||
kwargs.update({k: v for item in other for k, v in dict(item).items()})
|
||||
setters: Dict[str, Any] = {
|
||||
k: kwargs.pop(k)
|
||||
for k in {**kwargs}.keys()
|
||||
if k in self.__class__.__setters__
|
||||
}
|
||||
|
||||
for key, value in setters.items():
|
||||
try:
|
||||
super().__setattr__(key, value)
|
||||
except AttributeError:
|
||||
...
|
||||
|
||||
super().update(**kwargs)
|
||||
for attr, value in {**setters, **kwargs}.items():
|
||||
self._post_set(attr, value)
|
||||
|
||||
def _post_set(self, attr, value) -> None:
|
||||
if self.get("_init"):
|
||||
if attr in (
|
||||
"REQUEST_MAX_HEADER_SIZE",
|
||||
"REQUEST_BUFFER_SIZE",
|
||||
"REQUEST_MAX_SIZE",
|
||||
):
|
||||
self._configure_header_size()
|
||||
|
||||
if attr == "LOCAL_CERT_CREATOR" and not isinstance(
|
||||
self.LOCAL_CERT_CREATOR, LocalCertCreator
|
||||
):
|
||||
self._configure_header_size()
|
||||
self.LOCAL_CERT_CREATOR = LocalCertCreator[
|
||||
self.LOCAL_CERT_CREATOR.upper()
|
||||
]
|
||||
elif attr == "RESTART_ORDER" and not isinstance(
|
||||
self.RESTART_ORDER, RestartOrder
|
||||
):
|
||||
self.RESTART_ORDER = RestartOrder[self.RESTART_ORDER.upper()]
|
||||
elif attr == "DEPRECATION_FILTER":
|
||||
self._configure_warnings()
|
||||
|
||||
@property
|
||||
def FALLBACK_ERROR_FORMAT(self) -> str:
|
||||
if isinstance(self._FALLBACK_ERROR_FORMAT, Default):
|
||||
return DEFAULT_FORMAT
|
||||
return self._FALLBACK_ERROR_FORMAT
|
||||
|
||||
@FALLBACK_ERROR_FORMAT.setter
|
||||
def FALLBACK_ERROR_FORMAT(self, value):
|
||||
self._check_error_format(value)
|
||||
if (
|
||||
not isinstance(self._FALLBACK_ERROR_FORMAT, Default)
|
||||
and value != self._FALLBACK_ERROR_FORMAT
|
||||
):
|
||||
error_logger.warning(
|
||||
"Setting config.FALLBACK_ERROR_FORMAT on an already "
|
||||
"configured value may have unintended consequences."
|
||||
)
|
||||
self._FALLBACK_ERROR_FORMAT = value
|
||||
|
||||
def _configure_header_size(self):
|
||||
Http.set_header_max_size(
|
||||
@@ -123,33 +229,60 @@ class Config(dict):
|
||||
self.REQUEST_MAX_SIZE,
|
||||
)
|
||||
|
||||
def _configure_warnings(self):
|
||||
filterwarnings(
|
||||
self.DEPRECATION_FILTER,
|
||||
category=DeprecationWarning,
|
||||
module=r"sanic.*",
|
||||
)
|
||||
|
||||
def _check_error_format(self, format: Optional[str] = None):
|
||||
check_error_format(format or self.FALLBACK_ERROR_FORMAT)
|
||||
|
||||
def load_environment_vars(self, prefix=SANIC_PREFIX):
|
||||
"""
|
||||
Looks for prefixed environment variables and applies
|
||||
them to the configuration if present. This is called automatically when
|
||||
Sanic starts up to load environment variables into config.
|
||||
Looks for prefixed environment variables and applies them to the
|
||||
configuration if present. This is called automatically when Sanic
|
||||
starts up to load environment variables into config. Environment
|
||||
variables should start with the defined prefix and should only
|
||||
contain uppercase letters.
|
||||
|
||||
It will automatically hyrdate the following types:
|
||||
It will automatically hydrate the following types:
|
||||
|
||||
- ``int``
|
||||
- ``float``
|
||||
- ``bool``
|
||||
|
||||
Anything else will be imported as a ``str``.
|
||||
Anything else will be imported as a ``str``. If you would like to add
|
||||
additional types to this list, you can use
|
||||
:meth:`sanic.config.Config.register_type`. Just make sure that they
|
||||
are registered before you instantiate your application.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Foo:
|
||||
def __init__(self, name) -> None:
|
||||
self.name = name
|
||||
|
||||
|
||||
config = Config(converters=[Foo])
|
||||
app = Sanic(__name__, config=config)
|
||||
|
||||
`See user guide re: config
|
||||
<https://sanicframework.org/guide/deployment/configuration.html>`__
|
||||
"""
|
||||
for k, v in environ.items():
|
||||
if k.startswith(prefix):
|
||||
_, config_key = k.split(prefix, 1)
|
||||
for key, value in environ.items():
|
||||
if not key.startswith(prefix) or not key.isupper():
|
||||
continue
|
||||
|
||||
_, config_key = key.split(prefix, 1)
|
||||
|
||||
for converter in reversed(self._converters):
|
||||
try:
|
||||
self[config_key] = int(v)
|
||||
self[config_key] = converter(value)
|
||||
break
|
||||
except ValueError:
|
||||
try:
|
||||
self[config_key] = float(v)
|
||||
except ValueError:
|
||||
try:
|
||||
self[config_key] = str_to_bool(v)
|
||||
except ValueError:
|
||||
self[config_key] = v
|
||||
pass
|
||||
|
||||
def update_config(self, config: Union[bytes, str, dict, Any]):
|
||||
"""
|
||||
@@ -219,3 +352,17 @@ class Config(dict):
|
||||
self.update(config)
|
||||
|
||||
load = update_config
|
||||
|
||||
def register_type(self, converter: Callable[[str], Any]) -> None:
|
||||
"""
|
||||
Allows for adding custom function to cast from a string value to any
|
||||
other type. The function should raise ValueError if it is not the
|
||||
correct type.
|
||||
"""
|
||||
if converter in self._converters:
|
||||
error_logger.warning(
|
||||
f"Configuration value converter '{converter.__name__}' has "
|
||||
"already been registered"
|
||||
)
|
||||
return
|
||||
self._converters.append(converter)
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
from enum import Enum, auto
|
||||
from enum import auto
|
||||
|
||||
from sanic.compat import UpperStrEnum
|
||||
|
||||
|
||||
class HTTPMethod(str, Enum):
|
||||
def _generate_next_value_(name, start, count, last_values):
|
||||
return name.upper()
|
||||
|
||||
def __eq__(self, value: object) -> bool:
|
||||
value = str(value).upper()
|
||||
return super().__eq__(value)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.value)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
class HTTPMethod(UpperStrEnum):
|
||||
|
||||
GET = auto()
|
||||
POST = auto()
|
||||
@@ -24,5 +14,29 @@ class HTTPMethod(str, Enum):
|
||||
DELETE = auto()
|
||||
|
||||
|
||||
class LocalCertCreator(UpperStrEnum):
|
||||
|
||||
AUTO = auto()
|
||||
TRUSTME = auto()
|
||||
MKCERT = auto()
|
||||
|
||||
|
||||
class RestartOrder(UpperStrEnum):
|
||||
|
||||
SHUTDOWN_FIRST = auto()
|
||||
STARTUP_FIRST = auto()
|
||||
|
||||
|
||||
HTTP_METHODS = tuple(HTTPMethod.__members__.values())
|
||||
SAFE_HTTP_METHODS = (HTTPMethod.GET, HTTPMethod.HEAD, HTTPMethod.OPTIONS)
|
||||
IDEMPOTENT_HTTP_METHODS = (
|
||||
HTTPMethod.GET,
|
||||
HTTPMethod.HEAD,
|
||||
HTTPMethod.PUT,
|
||||
HTTPMethod.DELETE,
|
||||
HTTPMethod.OPTIONS,
|
||||
)
|
||||
CACHEABLE_HTTP_METHODS = (HTTPMethod.GET, HTTPMethod.HEAD)
|
||||
DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream"
|
||||
DEFAULT_LOCAL_TLS_KEY = "key.pem"
|
||||
DEFAULT_LOCAL_TLS_CERT = "cert.pem"
|
||||
|
||||
@@ -19,20 +19,22 @@ import typing as t
|
||||
from functools import partial
|
||||
from traceback import extract_tb
|
||||
|
||||
from sanic.exceptions import InvalidUsage, SanicException
|
||||
from sanic.exceptions import BadRequest, SanicException
|
||||
from sanic.helpers import STATUS_CODES
|
||||
from sanic.request import Request
|
||||
from sanic.response import HTTPResponse, html, json, text
|
||||
|
||||
|
||||
dumps: t.Callable[..., str]
|
||||
try:
|
||||
from ujson import dumps
|
||||
|
||||
dumps = partial(dumps, escape_forward_slashes=False)
|
||||
except ImportError: # noqa
|
||||
from json import dumps # type: ignore
|
||||
from json import dumps
|
||||
|
||||
|
||||
DEFAULT_FORMAT = "auto"
|
||||
FALLBACK_TEXT = (
|
||||
"The server encountered an internal error and "
|
||||
"cannot complete your request."
|
||||
@@ -45,6 +47,8 @@ class BaseRenderer:
|
||||
Base class that all renderers must inherit from.
|
||||
"""
|
||||
|
||||
dumps = staticmethod(dumps)
|
||||
|
||||
def __init__(self, request, exception, debug):
|
||||
self.request = request
|
||||
self.exception = exception
|
||||
@@ -112,14 +116,16 @@ class HTMLRenderer(BaseRenderer):
|
||||
TRACEBACK_STYLE = """
|
||||
html { font-family: sans-serif }
|
||||
h2 { color: #888; }
|
||||
.tb-wrapper p { margin: 0 }
|
||||
.tb-wrapper p, dl, dd { margin: 0 }
|
||||
.frame-border { margin: 1rem }
|
||||
.frame-line > * { padding: 0.3rem 0.6rem }
|
||||
.frame-line { margin-bottom: 0.3rem }
|
||||
.frame-code { font-size: 16px; padding-left: 4ch }
|
||||
.tb-wrapper { border: 1px solid #eee }
|
||||
.tb-header { background: #eee; padding: 0.3rem; font-weight: bold }
|
||||
.frame-descriptor { background: #e2eafb; font-size: 14px }
|
||||
.frame-line > *, dt, dd { padding: 0.3rem 0.6rem }
|
||||
.frame-line, dl { margin-bottom: 0.3rem }
|
||||
.frame-code, dd { font-size: 16px; padding-left: 4ch }
|
||||
.tb-wrapper, dl { border: 1px solid #eee }
|
||||
.tb-header,.obj-header {
|
||||
background: #eee; padding: 0.3rem; font-weight: bold
|
||||
}
|
||||
.frame-descriptor, dt { background: #e2eafb; font-size: 14px }
|
||||
"""
|
||||
TRACEBACK_WRAPPER_HTML = (
|
||||
"<div class=tb-header>{exc_name}: {exc_value}</div>"
|
||||
@@ -138,6 +144,11 @@ class HTMLRenderer(BaseRenderer):
|
||||
"<p class=frame-code><code>{0.line}</code>"
|
||||
"</div>"
|
||||
)
|
||||
OBJECT_WRAPPER_HTML = (
|
||||
"<div class=obj-header>{title}</div>"
|
||||
"<dl class={obj_type}>{display_html}</dl>"
|
||||
)
|
||||
OBJECT_DISPLAY_HTML = "<dt>{key}</dt><dd><code>{value}</code></dd>"
|
||||
OUTPUT_HTML = (
|
||||
"<!DOCTYPE html><html lang=en>"
|
||||
"<meta charset=UTF-8><title>{title}</title>\n"
|
||||
@@ -152,7 +163,7 @@ class HTMLRenderer(BaseRenderer):
|
||||
title=self.title,
|
||||
text=self.text,
|
||||
style=self.TRACEBACK_STYLE,
|
||||
body=self._generate_body(),
|
||||
body=self._generate_body(full=True),
|
||||
),
|
||||
status=self.status,
|
||||
)
|
||||
@@ -163,7 +174,7 @@ class HTMLRenderer(BaseRenderer):
|
||||
title=self.title,
|
||||
text=self.text,
|
||||
style=self.TRACEBACK_STYLE,
|
||||
body="",
|
||||
body=self._generate_body(full=False),
|
||||
),
|
||||
status=self.status,
|
||||
headers=self.headers,
|
||||
@@ -177,27 +188,49 @@ class HTMLRenderer(BaseRenderer):
|
||||
def title(self):
|
||||
return escape(f"⚠️ {super().title}")
|
||||
|
||||
def _generate_body(self):
|
||||
_, exc_value, __ = sys.exc_info()
|
||||
exceptions = []
|
||||
while exc_value:
|
||||
exceptions.append(self._format_exc(exc_value))
|
||||
exc_value = exc_value.__cause__
|
||||
def _generate_body(self, *, full):
|
||||
lines = []
|
||||
if full:
|
||||
_, exc_value, __ = sys.exc_info()
|
||||
exceptions = []
|
||||
while exc_value:
|
||||
exceptions.append(self._format_exc(exc_value))
|
||||
exc_value = exc_value.__cause__
|
||||
|
||||
traceback_html = self.TRACEBACK_BORDER.join(reversed(exceptions))
|
||||
appname = escape(self.request.app.name)
|
||||
name = escape(self.exception.__class__.__name__)
|
||||
value = escape(self.exception)
|
||||
path = escape(self.request.path)
|
||||
lines += [
|
||||
f"<h2>Traceback of {appname} " "(most recent call last):</h2>",
|
||||
f"{traceback_html}",
|
||||
"<div class=summary><p>",
|
||||
f"<b>{name}: {value}</b> "
|
||||
f"while handling path <code>{path}</code>",
|
||||
"</div>",
|
||||
]
|
||||
|
||||
for attr, display in (("context", True), ("extra", bool(full))):
|
||||
info = getattr(self.exception, attr, None)
|
||||
if info and display:
|
||||
lines.append(self._generate_object_display(info, attr))
|
||||
|
||||
traceback_html = self.TRACEBACK_BORDER.join(reversed(exceptions))
|
||||
appname = escape(self.request.app.name)
|
||||
name = escape(self.exception.__class__.__name__)
|
||||
value = escape(self.exception)
|
||||
path = escape(self.request.path)
|
||||
lines = [
|
||||
f"<h2>Traceback of {appname} (most recent call last):</h2>",
|
||||
f"{traceback_html}",
|
||||
"<div class=summary><p>",
|
||||
f"<b>{name}: {value}</b> while handling path <code>{path}</code>",
|
||||
"</div>",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
def _generate_object_display(
|
||||
self, obj: t.Dict[str, t.Any], descriptor: str
|
||||
) -> str:
|
||||
display = "".join(
|
||||
self.OBJECT_DISPLAY_HTML.format(key=key, value=value)
|
||||
for key, value in obj.items()
|
||||
)
|
||||
return self.OBJECT_WRAPPER_HTML.format(
|
||||
title=descriptor.title(),
|
||||
display_html=display,
|
||||
obj_type=descriptor.lower(),
|
||||
)
|
||||
|
||||
def _format_exc(self, exc):
|
||||
frames = extract_tb(exc.__traceback__)
|
||||
frame_html = "".join(
|
||||
@@ -224,7 +257,7 @@ class TextRenderer(BaseRenderer):
|
||||
title=self.title,
|
||||
text=self.text,
|
||||
bar=("=" * len(self.title)),
|
||||
body=self._generate_body(),
|
||||
body=self._generate_body(full=True),
|
||||
),
|
||||
status=self.status,
|
||||
)
|
||||
@@ -235,7 +268,7 @@ class TextRenderer(BaseRenderer):
|
||||
title=self.title,
|
||||
text=self.text,
|
||||
bar=("=" * len(self.title)),
|
||||
body="",
|
||||
body=self._generate_body(full=False),
|
||||
),
|
||||
status=self.status,
|
||||
headers=self.headers,
|
||||
@@ -245,21 +278,31 @@ class TextRenderer(BaseRenderer):
|
||||
def title(self):
|
||||
return f"⚠️ {super().title}"
|
||||
|
||||
def _generate_body(self):
|
||||
_, exc_value, __ = sys.exc_info()
|
||||
exceptions = []
|
||||
def _generate_body(self, *, full):
|
||||
lines = []
|
||||
if full:
|
||||
_, exc_value, __ = sys.exc_info()
|
||||
exceptions = []
|
||||
|
||||
lines = [
|
||||
f"{self.exception.__class__.__name__}: {self.exception} while "
|
||||
f"handling path {self.request.path}",
|
||||
f"Traceback of {self.request.app.name} (most recent call last):\n",
|
||||
]
|
||||
lines += [
|
||||
f"{self.exception.__class__.__name__}: {self.exception} while "
|
||||
f"handling path {self.request.path}",
|
||||
f"Traceback of {self.request.app.name} "
|
||||
"(most recent call last):\n",
|
||||
]
|
||||
|
||||
while exc_value:
|
||||
exceptions.append(self._format_exc(exc_value))
|
||||
exc_value = exc_value.__cause__
|
||||
while exc_value:
|
||||
exceptions.append(self._format_exc(exc_value))
|
||||
exc_value = exc_value.__cause__
|
||||
|
||||
return "\n".join(lines + exceptions[::-1])
|
||||
lines += exceptions[::-1]
|
||||
|
||||
for attr, display in (("context", True), ("extra", bool(full))):
|
||||
info = getattr(self.exception, attr, None)
|
||||
if info and display:
|
||||
lines += self._generate_object_display_list(info, attr)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _format_exc(self, exc):
|
||||
frames = "\n\n".join(
|
||||
@@ -272,6 +315,13 @@ class TextRenderer(BaseRenderer):
|
||||
)
|
||||
return f"{self.SPACER}{exc.__class__.__name__}: {exc}\n{frames}"
|
||||
|
||||
def _generate_object_display_list(self, obj, descriptor):
|
||||
lines = [f"\n{descriptor.title()}"]
|
||||
for key, value in obj.items():
|
||||
display = self.dumps(value)
|
||||
lines.append(f"{self.SPACER * 2}{key}: {display}")
|
||||
return lines
|
||||
|
||||
|
||||
class JSONRenderer(BaseRenderer):
|
||||
"""
|
||||
@@ -280,11 +330,11 @@ class JSONRenderer(BaseRenderer):
|
||||
|
||||
def full(self) -> HTTPResponse:
|
||||
output = self._generate_output(full=True)
|
||||
return json(output, status=self.status, dumps=dumps)
|
||||
return json(output, status=self.status, dumps=self.dumps)
|
||||
|
||||
def minimal(self) -> HTTPResponse:
|
||||
output = self._generate_output(full=False)
|
||||
return json(output, status=self.status, dumps=dumps)
|
||||
return json(output, status=self.status, dumps=self.dumps)
|
||||
|
||||
def _generate_output(self, *, full):
|
||||
output = {
|
||||
@@ -293,6 +343,11 @@ class JSONRenderer(BaseRenderer):
|
||||
"message": self.text,
|
||||
}
|
||||
|
||||
for attr, display in (("context", True), ("extra", bool(full))):
|
||||
info = getattr(self.exception, attr, None)
|
||||
if info and display:
|
||||
output[attr] = info
|
||||
|
||||
if full:
|
||||
_, exc_value, __ = sys.exc_info()
|
||||
exceptions = []
|
||||
@@ -340,41 +395,139 @@ RENDERERS_BY_CONFIG = {
|
||||
}
|
||||
|
||||
RENDERERS_BY_CONTENT_TYPE = {
|
||||
"multipart/form-data": HTMLRenderer,
|
||||
"application/json": JSONRenderer,
|
||||
"text/plain": TextRenderer,
|
||||
"application/json": JSONRenderer,
|
||||
"multipart/form-data": HTMLRenderer,
|
||||
"text/html": HTMLRenderer,
|
||||
}
|
||||
CONTENT_TYPE_BY_RENDERERS = {
|
||||
v: k for k, v in RENDERERS_BY_CONTENT_TYPE.items()
|
||||
}
|
||||
|
||||
RESPONSE_MAPPING = {
|
||||
"empty": "html",
|
||||
"json": "json",
|
||||
"text": "text",
|
||||
"raw": "text",
|
||||
"html": "html",
|
||||
"file": "html",
|
||||
"file_stream": "text",
|
||||
"stream": "text",
|
||||
"redirect": "html",
|
||||
"text/plain": "text",
|
||||
"text/html": "html",
|
||||
"application/json": "json",
|
||||
}
|
||||
|
||||
|
||||
def check_error_format(format):
|
||||
if format not in RENDERERS_BY_CONFIG and format != "auto":
|
||||
raise SanicException(f"Unknown format: {format}")
|
||||
|
||||
|
||||
def exception_response(
|
||||
request: Request,
|
||||
exception: Exception,
|
||||
debug: bool,
|
||||
fallback: str,
|
||||
base: t.Type[BaseRenderer],
|
||||
renderer: t.Type[t.Optional[BaseRenderer]] = None,
|
||||
) -> HTTPResponse:
|
||||
"""
|
||||
Render a response for the default FALLBACK exception handler.
|
||||
"""
|
||||
content_type = None
|
||||
|
||||
if not renderer:
|
||||
renderer = HTMLRenderer
|
||||
# Make sure we have something set
|
||||
renderer = base
|
||||
render_format = fallback
|
||||
|
||||
if request:
|
||||
if request.app.config.FALLBACK_ERROR_FORMAT == "auto":
|
||||
# If there is a request, try and get the format
|
||||
# from the route
|
||||
if request.route:
|
||||
try:
|
||||
renderer = JSONRenderer if request.json else HTMLRenderer
|
||||
except InvalidUsage:
|
||||
if request.route.extra.error_format:
|
||||
render_format = request.route.extra.error_format
|
||||
except AttributeError:
|
||||
...
|
||||
|
||||
content_type = request.headers.getone("content-type", "").split(
|
||||
";"
|
||||
)[0]
|
||||
|
||||
acceptable = request.accept
|
||||
|
||||
# If the format is auto still, make a guess
|
||||
if render_format == "auto":
|
||||
# First, if there is an Accept header, check if text/html
|
||||
# is the first option
|
||||
# According to MDN Web Docs, all major browsers use text/html
|
||||
# as the primary value in Accept (with the exception of IE 8,
|
||||
# and, well, if you are supporting IE 8, then you have bigger
|
||||
# problems to concern yourself with than what default exception
|
||||
# renderer is used)
|
||||
# Source:
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values
|
||||
|
||||
if acceptable and acceptable[0].match(
|
||||
"text/html",
|
||||
allow_type_wildcard=False,
|
||||
allow_subtype_wildcard=False,
|
||||
):
|
||||
renderer = HTMLRenderer
|
||||
|
||||
content_type, *_ = request.headers.getone(
|
||||
"content-type", ""
|
||||
).split(";")
|
||||
renderer = RENDERERS_BY_CONTENT_TYPE.get(
|
||||
content_type, renderer
|
||||
)
|
||||
# Second, if there is an Accept header, check if
|
||||
# application/json is an option, or if the content-type
|
||||
# is application/json
|
||||
elif (
|
||||
acceptable
|
||||
and acceptable.match(
|
||||
"application/json",
|
||||
allow_type_wildcard=False,
|
||||
allow_subtype_wildcard=False,
|
||||
)
|
||||
or content_type == "application/json"
|
||||
):
|
||||
renderer = JSONRenderer
|
||||
|
||||
# Third, if there is no Accept header, assume we want text.
|
||||
# The likely use case here is a raw socket.
|
||||
elif not acceptable:
|
||||
renderer = TextRenderer
|
||||
else:
|
||||
# Fourth, look to see if there was a JSON body
|
||||
# When in this situation, the request is probably coming
|
||||
# from curl, an API client like Postman or Insomnia, or a
|
||||
# package like requests or httpx
|
||||
try:
|
||||
# Give them the benefit of the doubt if they did:
|
||||
# $ curl localhost:8000 -d '{"foo": "bar"}'
|
||||
# And provide them with JSONRenderer
|
||||
renderer = JSONRenderer if request.json else base
|
||||
except BadRequest:
|
||||
renderer = base
|
||||
else:
|
||||
render_format = request.app.config.FALLBACK_ERROR_FORMAT
|
||||
renderer = RENDERERS_BY_CONFIG.get(render_format, renderer)
|
||||
|
||||
# Lastly, if there is an Accept header, make sure
|
||||
# our choice is okay
|
||||
if acceptable:
|
||||
type_ = CONTENT_TYPE_BY_RENDERERS.get(renderer) # type: ignore
|
||||
if type_ and type_ not in acceptable:
|
||||
# If the renderer selected is not in the Accept header
|
||||
# look through what is in the Accept header, and select
|
||||
# the first option that matches. Otherwise, just drop back
|
||||
# to the original default
|
||||
for accept in acceptable:
|
||||
mtype = f"{accept.type_}/{accept.subtype}"
|
||||
maybe = RENDERERS_BY_CONTENT_TYPE.get(mtype)
|
||||
if maybe:
|
||||
renderer = maybe
|
||||
break
|
||||
else:
|
||||
renderer = base
|
||||
|
||||
renderer = t.cast(t.Type[BaseRenderer], renderer)
|
||||
return renderer(request, exception, debug).render()
|
||||
|
||||
@@ -1,19 +1,36 @@
|
||||
from typing import Optional, Union
|
||||
from asyncio import CancelledError
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from sanic.helpers import STATUS_CODES
|
||||
|
||||
|
||||
class RequestCancelled(CancelledError):
|
||||
quiet = True
|
||||
|
||||
|
||||
class ServerKilled(Exception):
|
||||
...
|
||||
|
||||
|
||||
class SanicException(Exception):
|
||||
message: str = ""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: Optional[Union[str, bytes]] = None,
|
||||
status_code: Optional[int] = None,
|
||||
quiet: Optional[bool] = None,
|
||||
context: Optional[Dict[str, Any]] = None,
|
||||
extra: Optional[Dict[str, Any]] = None,
|
||||
) -> None:
|
||||
|
||||
if message is None and status_code is not None:
|
||||
msg: bytes = STATUS_CODES.get(status_code, b"")
|
||||
message = msg.decode("utf8")
|
||||
self.context = context
|
||||
self.extra = extra
|
||||
if message is None:
|
||||
if self.message:
|
||||
message = self.message
|
||||
elif status_code is not None:
|
||||
msg: bytes = STATUS_CODES.get(status_code, b"")
|
||||
message = msg.decode("utf8")
|
||||
|
||||
super().__init__(message)
|
||||
|
||||
@@ -34,7 +51,7 @@ class NotFound(SanicException):
|
||||
quiet = True
|
||||
|
||||
|
||||
class InvalidUsage(SanicException):
|
||||
class BadRequest(SanicException):
|
||||
"""
|
||||
**Status**: 400 Bad Request
|
||||
"""
|
||||
@@ -43,7 +60,14 @@ class InvalidUsage(SanicException):
|
||||
quiet = True
|
||||
|
||||
|
||||
class MethodNotSupported(SanicException):
|
||||
InvalidUsage = BadRequest
|
||||
|
||||
|
||||
class BadURL(BadRequest):
|
||||
...
|
||||
|
||||
|
||||
class MethodNotAllowed(SanicException):
|
||||
"""
|
||||
**Status**: 405 Method Not Allowed
|
||||
"""
|
||||
@@ -56,6 +80,9 @@ class MethodNotSupported(SanicException):
|
||||
self.headers = {"Allow": ", ".join(allowed_methods)}
|
||||
|
||||
|
||||
MethodNotSupported = MethodNotAllowed
|
||||
|
||||
|
||||
class ServerError(SanicException):
|
||||
"""
|
||||
**Status**: 500 Internal Server Error
|
||||
@@ -117,16 +144,19 @@ class PayloadTooLarge(SanicException):
|
||||
quiet = True
|
||||
|
||||
|
||||
class HeaderNotFound(InvalidUsage):
|
||||
class HeaderNotFound(BadRequest):
|
||||
"""
|
||||
**Status**: 400 Bad Request
|
||||
"""
|
||||
|
||||
status_code = 400
|
||||
quiet = True
|
||||
|
||||
class InvalidHeader(BadRequest):
|
||||
"""
|
||||
**Status**: 400 Bad Request
|
||||
"""
|
||||
|
||||
|
||||
class ContentRangeError(SanicException):
|
||||
class RangeNotSatisfiable(SanicException):
|
||||
"""
|
||||
**Status**: 416 Range Not Satisfiable
|
||||
"""
|
||||
@@ -139,7 +169,10 @@ class ContentRangeError(SanicException):
|
||||
self.headers = {"Content-Range": f"bytes */{content_range.total}"}
|
||||
|
||||
|
||||
class HeaderExpectationFailed(SanicException):
|
||||
ContentRangeError = RangeNotSatisfiable
|
||||
|
||||
|
||||
class ExpectationFailed(SanicException):
|
||||
"""
|
||||
**Status**: 417 Expectation Failed
|
||||
"""
|
||||
@@ -148,6 +181,9 @@ class HeaderExpectationFailed(SanicException):
|
||||
quiet = True
|
||||
|
||||
|
||||
HeaderExpectationFailed = ExpectationFailed
|
||||
|
||||
|
||||
class Forbidden(SanicException):
|
||||
"""
|
||||
**Status**: 403 Forbidden
|
||||
@@ -157,7 +193,7 @@ class Forbidden(SanicException):
|
||||
quiet = True
|
||||
|
||||
|
||||
class InvalidRangeType(ContentRangeError):
|
||||
class InvalidRangeType(RangeNotSatisfiable):
|
||||
"""
|
||||
**Status**: 416 Range Not Satisfiable
|
||||
"""
|
||||
@@ -230,23 +266,6 @@ class InvalidSignal(SanicException):
|
||||
pass
|
||||
|
||||
|
||||
def abort(status_code: int, message: Optional[Union[str, bytes]] = None):
|
||||
"""
|
||||
Raise an exception based on SanicException. Returns the HTTP response
|
||||
message appropriate for the given status code, unless provided.
|
||||
|
||||
STATUS_CODES from sanic.helpers for the given status code.
|
||||
|
||||
:param status_code: The HTTP status code to return.
|
||||
:param message: The HTTP response body. Defaults to the messages in
|
||||
"""
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"sanic.exceptions.abort has been marked as deprecated, and will be "
|
||||
"removed in release 21.12.\n To migrate your code, simply replace "
|
||||
"abort(status_code, msg) with raise SanicException(msg, status_code), "
|
||||
"or even better, raise an appropriate SanicException subclass."
|
||||
)
|
||||
|
||||
raise SanicException(message=message, status_code=status_code)
|
||||
class WebsocketClosed(SanicException):
|
||||
quiet = True
|
||||
message = "Client has closed the websocket connection"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user