Compare commits
338 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a5a9658896 | ||
![]() |
91d7e6a77d | ||
![]() |
eb3d78f687 | ||
![]() |
d255d1aae1 | ||
![]() |
47215d4635 | ||
![]() |
38ff9069f3 | ||
![]() |
4dde4572ec | ||
![]() |
31d14704cb | ||
![]() |
6a89f4b2fe | ||
![]() |
16256522f6 | ||
![]() |
205795d1e8 | ||
![]() |
9cbe1fb8ad | ||
![]() |
31d7ba8f8c | ||
![]() |
dc3c4d1393 | ||
![]() |
929d270569 | ||
![]() |
93714df051 | ||
![]() |
6e61eab872 | ||
![]() |
6848ff24d8 | ||
![]() |
666371bb92 | ||
![]() |
4a2b82e42e | ||
![]() |
5dd1623192 | ||
![]() |
976da69e79 | ||
![]() |
11a0b15194 | ||
![]() |
c21999a248 | ||
![]() |
c17230ef94 | ||
![]() |
049983cb70 | ||
![]() |
e374409567 | ||
![]() |
4068a0d83d | ||
![]() |
70da5e9879 | ||
![]() |
f48506d620 | ||
![]() |
f2cc83c1ba | ||
![]() |
273825dab6 | ||
![]() |
9a7dafd531 | ||
![]() |
50117d174c | ||
![]() |
af67801062 | ||
![]() |
6eaab2a7e5 | ||
![]() |
d680af3709 | ||
![]() |
a8c2d77c91 | ||
![]() |
6e1c787e5d | ||
![]() |
932088e37e | ||
![]() |
1a63b9bec0 | ||
![]() |
61aa16f6ac | ||
![]() |
71cd53b64e | ||
![]() |
89188f5fc6 | ||
![]() |
a245ab3773 | ||
![]() |
ac1f56118a | ||
![]() |
53820bc241 | ||
![]() |
009954003c | ||
![]() |
8f265b8169 | ||
![]() |
5ee36fd933 | ||
![]() |
08a81c81be | ||
![]() |
5a0ed75171 | ||
![]() |
d62a92fac9 | ||
![]() |
88c918e72f | ||
![]() |
c8aab8fb3d | ||
![]() |
ecacfd396b | ||
![]() |
3c361e9852 | ||
![]() |
a5d7d03413 | ||
![]() |
259e458847 | ||
![]() |
cb49c2b26d | ||
![]() |
dfc0704831 | ||
![]() |
d238995f1b | ||
![]() |
6f5303e080 | ||
![]() |
5e7f6998bd | ||
![]() |
c7a71cd00c | ||
![]() |
9cb9e88678 | ||
![]() |
30c53b6857 | ||
![]() |
4ad8168bb0 | ||
![]() |
28f5b3c301 | ||
![]() |
c573019e7f | ||
![]() |
029f564032 | ||
![]() |
2abe66b670 | ||
![]() |
911485d52e | ||
![]() |
4744a89c33 | ||
![]() |
f7040ccec8 | ||
![]() |
518152d97e | ||
![]() |
0e44e9cacb | ||
![]() |
bfb54b0969 | ||
![]() |
154863d6c6 | ||
![]() |
a3ff0c13b7 | ||
![]() |
95ee518aec | ||
![]() |
71d3d87bcc | ||
![]() |
b276b91c21 | ||
![]() |
064168f3c8 | ||
![]() |
db39e127bf | ||
![]() |
13e9ab7ba9 | ||
![]() |
92e7463721 | ||
![]() |
8e720365c2 | ||
![]() |
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 | ||
![]() |
5308fec354 | ||
![]() |
0ba57d4701 | ||
![]() |
54ca6a6178 | ||
![]() |
7dd4a78cf2 | ||
![]() |
52ff49512a | ||
![]() |
5a48b94089 | ||
![]() |
ba1c73d947 | ||
![]() |
4732b6bdfa | ||
![]() |
a6e78b70ab | ||
![]() |
bb1174afc5 | ||
![]() |
df8abe9cfd | ||
![]() |
c3bca97ee1 | ||
![]() |
c3b6fa1bba | ||
![]() |
94d496afe1 | ||
![]() |
7b7a572f9b | ||
![]() |
1b8cb742f9 | ||
![]() |
3492d180a8 | ||
![]() |
021da38373 | ||
![]() |
ac784759d5 | ||
![]() |
36eda2cd62 | ||
![]() |
08a4b3013f | ||
![]() |
1dd0332e8b | ||
![]() |
a90877ac31 | ||
![]() |
8b7ea27a48 | ||
![]() |
8df80e276b | ||
![]() |
30572c972d | ||
![]() |
53da4dd091 | ||
![]() |
108a4a99c7 | ||
![]() |
7c180376d6 | ||
![]() |
f39b8b32f7 | ||
![]() |
c543d19f8a | ||
![]() |
80fca9aef7 | ||
![]() |
5bb9aa0c2c | ||
![]() |
83c746ee57 | ||
![]() |
aff6604636 | ||
![]() |
2c80571a8a | ||
![]() |
d964b552af | ||
![]() |
48f8b37b74 | ||
![]() |
141be0028d | ||
![]() |
a140c47195 | ||
![]() |
0c3a8392f2 | ||
![]() |
16875b1f41 | ||
![]() |
b1f31f2eeb | ||
![]() |
d16b9e5a02 | ||
![]() |
680484bdc8 | ||
![]() |
05cd44b5dd | ||
![]() |
ba374139f4 | ||
![]() |
72a745bfd5 | ||
![]() |
3a6fac7d59 | ||
![]() |
28ba8e53df | ||
![]() |
9b26358e63 | ||
![]() |
e21521f45c | ||
![]() |
30479765cb | ||
![]() |
53a571ec6c | ||
![]() |
ad97cac313 | ||
![]() |
1a352ddf55 | ||
![]() |
5ba43decf2 | ||
![]() |
8f06d035cb | ||
![]() |
b716f48c84 | ||
![]() |
42b1e7143e | ||
![]() |
eba7821a6d |
@ -1,2 +0,0 @@
|
||||
[tool.black]
|
||||
line-length = 79
|
12
.coveragerc
12
.coveragerc
@ -1,7 +1,15 @@
|
||||
[run]
|
||||
branch = True
|
||||
source = sanic
|
||||
omit = site-packages, sanic/utils.py, sanic/__main__.py
|
||||
omit =
|
||||
site-packages
|
||||
sanic/__main__.py
|
||||
sanic/server/legacy.py
|
||||
sanic/compat.py
|
||||
sanic/simple.py
|
||||
sanic/utils.py
|
||||
sanic/cli
|
||||
sanic/pages
|
||||
|
||||
[html]
|
||||
directory = coverage
|
||||
@ -13,3 +21,5 @@ exclude_lines =
|
||||
noqa
|
||||
NOQA
|
||||
pragma: no cover
|
||||
TYPE_CHECKING
|
||||
skip_empty = True
|
||||
|
78
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
78
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
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. Please try and format your code so that it is easier to read. For example:
|
||||
|
||||
```python
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic("Example")
|
||||
```
|
||||
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: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: What OS?
|
||||
options:
|
||||
- Linux
|
||||
- MacOS
|
||||
- Windows
|
||||
- Other (tell us in the description)
|
||||
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.
|
33
.github/ISSUE_TEMPLATE/rfc.yml
vendored
Normal file
33
.github/ISSUE_TEMPLATE/rfc.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
name: 💡 Request for Comments
|
||||
description: Open an RFC for discussion
|
||||
labels: ["RFC"]
|
||||
body:
|
||||
- type: input
|
||||
id: compare
|
||||
attributes:
|
||||
label: Link to code
|
||||
description: If available, share a [comparison](https://github.com/sanic-org/sanic/compare) from a POC branch to main
|
||||
placeholder: https://github.com/sanic-org/sanic/compare/main...some-new-branch
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposal
|
||||
description: A thorough discussion of the proposal discussing the problem it solves, potential code, use cases, and impacts
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context that is relevant
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: breaking
|
||||
attributes:
|
||||
label: Is this a breaking change?
|
||||
options:
|
||||
- label: "Yes"
|
||||
required: false
|
45
.github/workflows/codeql-analysis.yml
vendored
45
.github/workflows/codeql-analysis.yml
vendored
@ -1,27 +1,23 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches:
|
||||
- main
|
||||
- current-release
|
||||
- "*LTS"
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
branches:
|
||||
- main
|
||||
- current-release
|
||||
- "*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
|
||||
|
||||
@ -29,39 +25,18 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
34
.github/workflows/coverage.yml
vendored
Normal file
34
.github/workflows/coverage.yml
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
name: Coverage check
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- current-release
|
||||
- "*LTS"
|
||||
tags:
|
||||
- "!*" # Do not execute on tags
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- current-release
|
||||
- "*LTS"
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
name: Check coverage
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Run coverage
|
||||
uses: sanic-org/simple-tox-action@v1
|
||||
with:
|
||||
python-version: "3.11"
|
||||
tox-env: coverage
|
||||
ignore-errors: true
|
||||
- name: Run Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: ./coverage.xml
|
||||
fail_ci_if_error: false
|
174
.github/workflows/publish-release.yml
vendored
Normal file
174
.github/workflows/publish-release.yml
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
name: Publish release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
env:
|
||||
IS_TEST: false
|
||||
DOCKER_ORG_NAME: sanicframework
|
||||
DOCKER_IMAGE_NAME: sanic
|
||||
DOCKER_BASE_IMAGE_NAME: sanic-build
|
||||
DOCKER_IMAGE_DOCKERFILE: ./docker/Dockerfile
|
||||
DOCKER_BASE_IMAGE_DOCKERFILE: ./docker/Dockerfile-base
|
||||
|
||||
jobs:
|
||||
generate_info:
|
||||
name: Generate info
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
docker-tags: ${{ steps.generate_docker_info.outputs.tags }}
|
||||
pypi-version: ${{ steps.parse_version_tag.outputs.pypi-version }}
|
||||
steps:
|
||||
- name: Parse version tag
|
||||
id: parse_version_tag
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.release.tag_name }}
|
||||
run: |
|
||||
tag_name="${{ env.TAG_NAME }}"
|
||||
|
||||
if [[ ! "${tag_name}" =~ ^v([0-9]{2})\.([0-9]{1,2})\.([0-9]+)$ ]]; then
|
||||
echo "::error::Tag name must be in the format vYY.MM.MICRO"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
year_output="year=${BASH_REMATCH[1]}"
|
||||
month_output="month=${BASH_REMATCH[2]}"
|
||||
pypi_output="pypi-version=${tag_name#v}"
|
||||
|
||||
echo "${year_output}"
|
||||
echo "${month_output}"
|
||||
echo "${pypi_output}"
|
||||
|
||||
echo "${year_output}" >> $GITHUB_OUTPUT
|
||||
echo "${month_output}" >> $GITHUB_OUTPUT
|
||||
echo "${pypi_output}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get latest release
|
||||
id: get_latest_release
|
||||
run: |
|
||||
latest_tag=$(
|
||||
curl -L \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${{ github.token }}" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/${{ github.repository }}/releases/latest \
|
||||
| jq -r '.tag_name'
|
||||
)
|
||||
echo "latest_tag=$latest_tag" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate Docker info
|
||||
id: generate_docker_info
|
||||
run: |
|
||||
tag_year="${{ steps.parse_version_tag.outputs.year }}"
|
||||
tag_month="${{ steps.parse_version_tag.outputs.month }}"
|
||||
latest_tag="${{ steps.get_latest_release.outputs.latest_tag }}"
|
||||
tag="${{ github.event.release.tag_name }}"
|
||||
|
||||
tags="${tag_year}.${tag_month}"
|
||||
|
||||
if [[ "${tag_month}" == "12" ]]; then
|
||||
tags+=",LTS"
|
||||
echo "::notice::Tag ${tag} is LTS version"
|
||||
else
|
||||
echo "::notice::Tag ${tag} is not LTS version"
|
||||
fi
|
||||
|
||||
if [[ "${latest_tag}" == "${{ github.event.release.tag_name }}" ]]; then
|
||||
tags+=",latest"
|
||||
echo "::notice::Tag ${tag} is marked as latest"
|
||||
else
|
||||
echo "::notice::Tag ${tag} is not marked as latest"
|
||||
fi
|
||||
|
||||
tags_output="tags=${tags}"
|
||||
|
||||
echo "${tags_output}"
|
||||
echo "${tags_output}" >> $GITHUB_OUTPUT
|
||||
|
||||
publish_package:
|
||||
name: Build and publish package
|
||||
runs-on: ubuntu-latest
|
||||
needs: generate_info
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install build twine
|
||||
|
||||
- name: Update package version
|
||||
run: |
|
||||
echo "__version__ = \"${{ needs.generate_info.outputs.pypi-version }}\"" > sanic/__version__.py
|
||||
|
||||
- name: Build a binary wheel and a source tarball
|
||||
run: python -m build --sdist --wheel --outdir dist/ .
|
||||
|
||||
- name: Publish to PyPi 🚀
|
||||
run: twine upload --non-interactive --disable-progress-bar dist/*
|
||||
env:
|
||||
TWINE_USERNAME: __token__
|
||||
TWINE_PASSWORD: ${{ env.IS_TEST == 'true' && secrets.SANIC_TEST_PYPI_API_TOKEN || secrets.SANIC_PYPI_API_TOKEN }}
|
||||
TWINE_REPOSITORY: ${{ env.IS_TEST == 'true' && 'testpypi' || 'pypi' }}
|
||||
|
||||
publish_docker:
|
||||
name: Publish Docker / Python ${{ matrix.python-version }}
|
||||
needs: [generate_info, publish_package]
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11"]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_ACCESS_USER }}
|
||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||
|
||||
- name: Build and push base image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
push: ${{ env.IS_TEST == 'false' }}
|
||||
file: ${{ env.DOCKER_BASE_IMAGE_DOCKERFILE }}
|
||||
tags: ${{ env.DOCKER_ORG_NAME }}/${{ env.DOCKER_BASE_IMAGE_NAME }}:${{ matrix.python-version }}
|
||||
build-args: |
|
||||
PYTHON_VERSION=${{ matrix.python-version }}
|
||||
|
||||
- name: Parse tags for this Python version
|
||||
id: parse_tags
|
||||
run: |
|
||||
IFS=',' read -ra tags <<< "${{ needs.generate_info.outputs.docker-tags }}"
|
||||
tag_args=""
|
||||
|
||||
for tag in "${tags[@]}"; do
|
||||
tag_args+=",${{ env.DOCKER_ORG_NAME }}/${{ env.DOCKER_IMAGE_NAME }}:${tag}-py${{ matrix.python-version }}"
|
||||
done
|
||||
|
||||
tag_args_output="tag_args=${tag_args:1}"
|
||||
|
||||
echo "${tag_args_output}"
|
||||
echo "${tag_args_output}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push Sanic image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
push: ${{ env.IS_TEST == 'false' }}
|
||||
file: ${{ env.DOCKER_IMAGE_DOCKERFILE }}
|
||||
tags: ${{ steps.parse_tags.outputs.tag_args }}
|
||||
build-args: |
|
||||
BASE_IMAGE_ORG=${{ env.DOCKER_ORG_NAME }}
|
||||
BASE_IMAGE_NAME=${{ env.DOCKER_BASE_IMAGE_NAME }}
|
||||
BASE_IMAGE_TAG=${{ matrix.python-version }}
|
||||
SANIC_PYPI_VERSION=${{ needs.generate_info.outputs.pypi-version }}
|
56
.github/workflows/tests.yml
vendored
Normal file
56
.github/workflows/tests.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- current-release
|
||||
- "*LTS"
|
||||
tags:
|
||||
- "!*"
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- current-release
|
||||
- "*LTS"
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
|
||||
jobs:
|
||||
run_tests:
|
||||
name: "${{ matrix.config.platform == 'windows-latest' && 'Windows' || 'Linux' }} / Python ${{ matrix.config.python-version }} / tox -e ${{ matrix.config.tox-env }}"
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ${{ matrix.config.platform || 'ubuntu-latest' }}
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
config:
|
||||
- { python-version: "3.8", tox-env: security }
|
||||
- { python-version: "3.9", tox-env: security }
|
||||
- { python-version: "3.10", tox-env: security }
|
||||
- { python-version: "3.11", tox-env: security }
|
||||
- { python-version: "3.10", tox-env: lint }
|
||||
# - { python-version: "3.10", tox-env: docs }
|
||||
- { python-version: "3.8", tox-env: type-checking }
|
||||
- { python-version: "3.9", tox-env: type-checking }
|
||||
- { python-version: "3.10", tox-env: type-checking }
|
||||
- { python-version: "3.11", tox-env: type-checking }
|
||||
- { python-version: "3.8", tox-env: py38, max-attempts: 3 }
|
||||
- { python-version: "3.8", tox-env: py38-no-ext, max-attempts: 3 }
|
||||
- { python-version: "3.9", tox-env: py39, max-attempts: 3 }
|
||||
- { python-version: "3.9", tox-env: py39-no-ext, max-attempts: 3 }
|
||||
- { python-version: "3.10", tox-env: py310, max-attempts: 3 }
|
||||
- { python-version: "3.10", tox-env: py310-no-ext, max-attempts: 3 }
|
||||
- { python-version: "3.11", tox-env: py311, max-attempts: 3 }
|
||||
- { python-version: "3.11", tox-env: py311-no-ext, max-attempts: 3 }
|
||||
- { python-version: "3.8", tox-env: py38-no-ext, platform: windows-latest, ignore-errors: true }
|
||||
- { python-version: "3.9", tox-env: py39-no-ext, platform: windows-latest, ignore-errors: true }
|
||||
- { python-version: "3.10", tox-env: py310-no-ext, platform: windows-latest, ignore-errors: true }
|
||||
- { python-version: "3.11", tox-env: py310-no-ext, platform: windows-latest, ignore-errors: true }
|
||||
steps:
|
||||
- name: Run tests
|
||||
uses: sanic-org/simple-tox-action@v1
|
||||
with:
|
||||
python-version: ${{ matrix.config.python-version }}
|
||||
tox-env: ${{ matrix.config.tox-env }}
|
||||
max-attempts: ${{ matrix.config.max-attempts || 1 }}
|
||||
ignore-errors: ${{ matrix.config.ignore-errors || false }}
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -6,6 +6,7 @@
|
||||
.coverage
|
||||
.coverage.*
|
||||
coverage
|
||||
coverage.xml
|
||||
.tox
|
||||
settings.py
|
||||
.idea/*
|
||||
@ -18,3 +19,8 @@ build/*
|
||||
.DS_Store
|
||||
dist/*
|
||||
pip-wheel-metadata/
|
||||
.pytest_cache/*
|
||||
.venv/*
|
||||
venv/*
|
||||
.vscode/*
|
||||
guide/node_modules/
|
||||
|
94
.travis.yml
94
.travis.yml
@ -1,94 +0,0 @@
|
||||
sudo: false
|
||||
language: python
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
matrix:
|
||||
include:
|
||||
- env: TOX_ENV=py37
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
name: "Python 3.7 with Extensions"
|
||||
- env: TOX_ENV=py37-no-ext
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
name: "Python 3.7 without Extensions"
|
||||
- env: TOX_ENV=py38
|
||||
python: 3.8
|
||||
dist: xenial
|
||||
sudo: true
|
||||
name: "Python 3.8 with Extensions"
|
||||
- env: TOX_ENV=py38-no-ext
|
||||
python: 3.8
|
||||
dist: xenial
|
||||
sudo: true
|
||||
name: "Python 3.8 without Extensions"
|
||||
- env: TOX_ENV=py39
|
||||
python: 3.9
|
||||
dist: bionic
|
||||
sudo: true
|
||||
name: "Python 3.9 with Extensions"
|
||||
- env: TOX_ENV=py39-no-ext
|
||||
python: 3.9
|
||||
dist: bionic
|
||||
sudo: true
|
||||
name: "Python 3.9 without Extensions"
|
||||
- env: TOX_ENV=type-checking
|
||||
python: 3.7
|
||||
name: "Python 3.7 Type checks"
|
||||
- env: TOX_ENV=type-checking
|
||||
python: 3.8
|
||||
name: "Python 3.8 Type checks"
|
||||
- env: TOX_ENV=type-checking
|
||||
python: 3.9
|
||||
dist: bionic
|
||||
name: "Python 3.9 Type checks"
|
||||
- env: TOX_ENV=security
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
name: "Python 3.7 Bandit security scan"
|
||||
- env: TOX_ENV=security
|
||||
python: 3.8
|
||||
dist: xenial
|
||||
sudo: true
|
||||
name: "Python 3.8 Bandit security scan"
|
||||
- env: TOX_ENV=security
|
||||
python: 3.9
|
||||
dist: bionic
|
||||
sudo: true
|
||||
name: "Python 3.9 Bandit security scan"
|
||||
- env: TOX_ENV=docs
|
||||
python: 3.7
|
||||
dist: xenial
|
||||
sudo: true
|
||||
name: "Python 3.7 Documentation tests"
|
||||
- env: TOX_ENV=pyNightly
|
||||
python: "nightly"
|
||||
name: "Python nightly with Extensions"
|
||||
- env: TOX_ENV=pyNightly-no-ext
|
||||
python: "nightly"
|
||||
name: "Python nightly without Extensions"
|
||||
allow_failures:
|
||||
- env: TOX_ENV=pyNightly
|
||||
python: "nightly"
|
||||
name: "Python nightly with Extensions"
|
||||
- env: TOX_ENV=pyNightly-no-ext
|
||||
python: "nightly"
|
||||
name: "Python nightly without Extensions"
|
||||
install:
|
||||
- pip install -U tox
|
||||
- pip install codecov
|
||||
script: travis_retry tox -e $TOX_ENV
|
||||
after_success:
|
||||
- codecov
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: brewmaster
|
||||
password:
|
||||
secure: "GoawLwmbtJOgKB6AJ0ZSYUUnNwIoonseHBxaAUH3zu79TS/Afrq+yB3lsVaMSG0CbyDgN4FrfD1phT1NzbvZ1VcLIOTDtCrmpQ1kLDw+zwgF40ab8sp8fPkKVHHHfCCs1mjltHIpxQa5lZTJcAs6Bpi/lbUWWwYxFzSV8pHw4W4hY09EHUd2o+evLTSVxaploetSt725DJUYKICUr2eAtCC11IDnIW4CzBJEx6krVV3uhzfTJW0Ls17x0c6sdZ9icMnV/G9xO/eQH6RIHe4xcrWJ6cmLDNKoGAkJp+BKr1CeVVg7Jw/MzPjvZKL2/ki6Beue1y6GUIy7lOS7jPVaOEhJ23b0zQwFcLMZw+Tt+E3v6QfHk+B/WBBBnM3zUZed9UI+QyW8+lqLLt39sQX0FO0P3eaDh8qTXtUuon2jTyFMMAMTFRTNpJmpAzuBH9yeMmDeALPTh0HphI+BkoUl5q1QbWFYjjnZMH2CatApxpLybt9A7rwm//PbOG0TSI93GEKNQ4w5DYryKTfwHzRBptNSephJSuxZYEfJsmUtas5es1D7Fe0PkyjxNNSU+eO+8wsTlitLUsJO4k0jAgy+cEKdU7YJ3J0GZVXocSkrNnUfd2hQPcJ3UtEJx3hLqqr8EM7EZBAasc1yGHh36NFetclzFY24YPih0G1+XurhTys="
|
||||
on:
|
||||
tags: true
|
||||
distributions: "sdist bdist_wheel"
|
286
CHANGELOG.rst
286
CHANGELOG.rst
@ -1,8 +1,113 @@
|
||||
.. 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**
|
||||
|
||||
* `#2094 <https://github.com/sanic-org/sanic/pull/2094>`_
|
||||
Add ``response.eof()`` method for closing a stream in a handler
|
||||
* `#2097 <https://github.com/sanic-org/sanic/pull/2097>`_
|
||||
Allow case-insensitive HTTP Upgrade header
|
||||
* `#2104 <https://github.com/sanic-org/sanic/pull/2104>`_
|
||||
Explicit usage of CIMultiDict getters
|
||||
* `#2109 <https://github.com/sanic-org/sanic/pull/2109>`_
|
||||
Consistent use of error loggers
|
||||
* `#2114 <https://github.com/sanic-org/sanic/pull/2114>`_
|
||||
New ``client_ip`` access of connection info instance
|
||||
* `#2119 <https://github.com/sanic-org/sanic/pull/2119>`_
|
||||
Alternatate classes on instantiation for ``Config`` and ``Sanic.ctx``
|
||||
* `#2133 <https://github.com/sanic-org/sanic/pull/2133>`_
|
||||
Implement new version of AST router
|
||||
|
||||
* Proper differentiation between ``alpha`` and ``string`` param types
|
||||
* Adds a ``slug`` param type, example: ``<foo:slug>``
|
||||
* Deprecates ``<foo:string>`` in favor of ``<foo:str>``
|
||||
* Deprecates ``<foo:number>`` in favor of ``<foo:float>``
|
||||
* Adds a ``route.uri`` accessor
|
||||
* `#2136 <https://github.com/sanic-org/sanic/pull/2136>`_
|
||||
CLI improvements with new optional params
|
||||
* `#2137 <https://github.com/sanic-org/sanic/pull/2137>`_
|
||||
Add ``version_prefix`` to URL builders
|
||||
* `#2140 <https://github.com/sanic-org/sanic/pull/2140>`_
|
||||
Event autoregistration with ``EVENT_AUTOREGISTER``
|
||||
* `#2146 <https://github.com/sanic-org/sanic/pull/2146>`_, `#2147 <https://github.com/sanic-org/sanic/pull/2147>`_
|
||||
Require stricter names on ``Sanic()`` and ``Blueprint()``
|
||||
* `#2150 <https://github.com/sanic-org/sanic/pull/2150>`_
|
||||
Infinitely reusable and nestable ``Blueprint`` and ``BlueprintGroup``
|
||||
* `#2154 <https://github.com/sanic-org/sanic/pull/2154>`_
|
||||
Upgrade ``websockets`` dependency to min version
|
||||
* `#2155 <https://github.com/sanic-org/sanic/pull/2155>`_
|
||||
Allow for maximum header sizes to be increased: ``REQUEST_MAX_HEADER_SIZE``
|
||||
* `#2157 <https://github.com/sanic-org/sanic/pull/2157>`_
|
||||
Allow app factory pattern in CLI
|
||||
* `#2165 <https://github.com/sanic-org/sanic/pull/2165>`_
|
||||
Change HTTP methods to enums
|
||||
* `#2167 <https://github.com/sanic-org/sanic/pull/2167>`_
|
||||
Allow auto-reloading on additional directories
|
||||
* `#2168 <https://github.com/sanic-org/sanic/pull/2168>`_
|
||||
Add simple HTTP server to CLI
|
||||
* `#2170 <https://github.com/sanic-org/sanic/pull/2170>`_
|
||||
Additional methods for attaching ``HTTPMethodView``
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* `#2091 <https://github.com/sanic-org/sanic/pull/2091>`_
|
||||
Fix ``UserWarning`` in ASGI mode for missing ``__slots__``
|
||||
* `#2099 <https://github.com/sanic-org/sanic/pull/2099>`_
|
||||
Fix static request handler logging exception on 404
|
||||
* `#2110 <https://github.com/sanic-org/sanic/pull/2110>`_
|
||||
Fix request.args.pop removes parameters inconsistently
|
||||
* `#2107 <https://github.com/sanic-org/sanic/pull/2107>`_
|
||||
Fix type hinting for load_env
|
||||
* `#2127 <https://github.com/sanic-org/sanic/pull/2127>`_
|
||||
Make sure ASGI ws subprotocols is a list
|
||||
* `#2128 <https://github.com/sanic-org/sanic/pull/2128>`_
|
||||
Fix issue where Blueprint exception handlers do not consistently route to proper handler
|
||||
|
||||
|
||||
**Deprecations and Removals**
|
||||
|
||||
* `#2156 <https://github.com/sanic-org/sanic/pull/2156>`_
|
||||
Remove config value ``REQUEST_BUFFER_QUEUE_SIZE``
|
||||
* `#2170 <https://github.com/sanic-org/sanic/pull/2170>`_
|
||||
``CompositionView`` deprecated and marked for removal in 21.12
|
||||
* `#2172 <https://github.com/sanic-org/sanic/pull/2170>`_
|
||||
Deprecate StreamingHTTPResponse
|
||||
|
||||
**Developer infrastructure**
|
||||
|
||||
* `#2149 <https://github.com/sanic-org/sanic/pull/2149>`_
|
||||
Remove Travis CI in favor of GitHub Actions
|
||||
|
||||
**Improved Documentation**
|
||||
|
||||
* `#2164 <https://github.com/sanic-org/sanic/pull/2164>`_
|
||||
Fix typo in documentation
|
||||
* `#2100 <https://github.com/sanic-org/sanic/pull/2100>`_
|
||||
Remove documentation for non-existent arguments
|
||||
|
||||
Version 21.3.2
|
||||
--------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
* `#2081 <https://github.com/sanic-org/sanic/pull/2081>`_
|
||||
Disable response timeout on websocket connections
|
||||
@ -13,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)
|
||||
@ -24,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>`_
|
||||
@ -78,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 ``/``
|
||||
@ -109,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>`_
|
||||
@ -129,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>`_
|
||||
@ -148,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>`_
|
||||
@ -171,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
|
||||
@ -218,8 +316,7 @@ Miscellaneous
|
||||
Version 20.12.3
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#2021 <https://github.com/sanic-org/sanic/pull/2021>`_
|
||||
@ -228,8 +325,7 @@ Bugfixes
|
||||
Version 20.12.2
|
||||
---------------
|
||||
|
||||
Dependencies
|
||||
************
|
||||
**Dependencies**
|
||||
|
||||
*
|
||||
`#2026 <https://github.com/sanic-org/sanic/pull/2026>`_
|
||||
@ -242,8 +338,7 @@ Dependencies
|
||||
Version 19.12.5
|
||||
---------------
|
||||
|
||||
Dependencies
|
||||
************
|
||||
**Dependencies**
|
||||
|
||||
*
|
||||
`#2025 <https://github.com/sanic-org/sanic/pull/2025>`_
|
||||
@ -256,19 +351,12 @@ Dependencies
|
||||
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
|
||||
@ -305,22 +393,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>`_
|
||||
@ -334,8 +419,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>`_
|
||||
@ -353,8 +437,7 @@ Improved Documentation
|
||||
Version 20.9.1
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1954 <https://github.com/sanic-org/sanic/pull/1954>`_
|
||||
@ -367,8 +450,7 @@ Bugfixes
|
||||
Version 19.12.3
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1959 <https://github.com/sanic-org/sanic/pull/1959>`_
|
||||
@ -379,8 +461,7 @@ Version 20.9.0
|
||||
---------------
|
||||
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1887 <https://github.com/sanic-org/sanic/pull/1887>`_
|
||||
@ -407,22 +488,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>`_,
|
||||
@ -437,8 +515,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>`_
|
||||
@ -448,8 +525,7 @@ Improved Documentation
|
||||
Version 20.6.3
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1884 <https://github.com/sanic-org/sanic/pull/1884>`_
|
||||
@ -459,8 +535,7 @@ Bugfixes
|
||||
Version 20.6.2
|
||||
---------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1641 <https://github.com/sanic-org/sanic/pull/1641>`_
|
||||
@ -470,8 +545,7 @@ Features
|
||||
Version 20.6.1
|
||||
---------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1760 <https://github.com/sanic-org/sanic/pull/1760>`_
|
||||
@ -485,8 +559,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>`_
|
||||
@ -508,15 +581,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>`_
|
||||
@ -531,8 +602,7 @@ Developer infrastructure
|
||||
Wrap run()'s "protocol" type annotation in Optional[]
|
||||
|
||||
|
||||
Improved Documentation
|
||||
**********************
|
||||
**Improved Documentation**
|
||||
|
||||
*
|
||||
`#1846 <https://github.com/sanic-org/sanic/pull/1846>`_
|
||||
@ -546,14 +616,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>`_
|
||||
@ -584,8 +653,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>`_
|
||||
@ -603,8 +671,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>`_
|
||||
@ -622,8 +689,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>`_
|
||||
@ -633,15 +699,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>`_
|
||||
@ -683,8 +747,7 @@ Improved Documentation
|
||||
Version 19.12.0
|
||||
---------------
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
- Fix blueprint middleware application
|
||||
|
||||
@ -703,8 +766,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
|
||||
|
||||
@ -718,8 +780,7 @@ Improved Documentation
|
||||
Version 19.6.3
|
||||
--------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
- Enable Towncrier Support
|
||||
|
||||
@ -727,8 +788,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
|
||||
|
||||
@ -741,8 +801,7 @@ Improved Documentation
|
||||
Version 19.6.2
|
||||
--------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1562 <https://github.com/sanic-org/sanic/pull/1562>`_
|
||||
@ -758,8 +817,7 @@ Features
|
||||
Add Configure support from object string
|
||||
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
*
|
||||
`#1587 <https://github.com/sanic-org/sanic/pull/1587>`_
|
||||
@ -777,8 +835,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>`_
|
||||
@ -802,8 +859,7 @@ Deprecations and Removals
|
||||
Version 19.3
|
||||
------------
|
||||
|
||||
Features
|
||||
********
|
||||
**Features**
|
||||
|
||||
*
|
||||
`#1497 <https://github.com/sanic-org/sanic/pull/1497>`_
|
||||
@ -871,8 +927,7 @@ Features
|
||||
|
||||
This is a breaking change.
|
||||
|
||||
Bugfixes
|
||||
********
|
||||
**Bugfixes**
|
||||
|
||||
|
||||
*
|
||||
@ -908,8 +963,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)
|
||||
@ -917,8 +971,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
|
||||
@ -979,21 +1032,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:
|
||||
|
||||
@ -1018,7 +1069,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)
|
||||
@ -1073,19 +1124,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
|
||||
@ -1093,23 +1141,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
|
||||
---------------
|
||||
@ -87,7 +87,7 @@ Permform ``flake8``\ , ``black`` and ``isort`` checks.
|
||||
tox -e lint
|
||||
|
||||
Run type annotation checks
|
||||
---------------
|
||||
--------------------------
|
||||
|
||||
``tox`` environment -> ``[testenv:type-checking]``
|
||||
|
||||
@ -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.
|
||||
|
||||
|
15
Makefile
15
Makefile
@ -49,6 +49,9 @@ test: clean
|
||||
test-coverage: clean
|
||||
python setup.py test --pytest-args="--cov sanic --cov-report term --cov-append "
|
||||
|
||||
view-coverage:
|
||||
sanic ./coverage --simple
|
||||
|
||||
install:
|
||||
python setup.py install
|
||||
|
||||
@ -63,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
|
||||
|
||||
@ -85,12 +88,14 @@ docs-test: docs-clean
|
||||
cd docs && make dummy
|
||||
|
||||
docs-serve:
|
||||
# python -m http.server --directory=./docs/_build/html 9999
|
||||
sphinx-autobuild docs docs/_build/html --port 9999 --watch ./sanic
|
||||
sphinx-autobuild docs docs/_build/html --port 9999 --watch ./
|
||||
|
||||
changelog:
|
||||
python scripts/changelog.py
|
||||
|
||||
guide-serve:
|
||||
cd guide && sanic server:app -r -R ./content -R ./style
|
||||
|
||||
release:
|
||||
ifdef version
|
||||
python scripts/release.py --release-version ${version} --generate-changelog
|
||||
|
47
README.rst
47
README.rst
@ -11,7 +11,7 @@ Sanic | Build fast. Run fast.
|
||||
:stub-columns: 1
|
||||
|
||||
* - Build
|
||||
- | |Build Status| |AppVeyor Build Status| |Codecov|
|
||||
- | |Tests|
|
||||
* - Docs
|
||||
- | |UserGuide| |Documentation|
|
||||
* - Package
|
||||
@ -19,7 +19,7 @@ Sanic | Build fast. Run fast.
|
||||
* - Support
|
||||
- | |Forums| |Discord| |Awesome|
|
||||
* - Stats
|
||||
- | |Downloads| |WkDownloads| |Conda downloads|
|
||||
- | |Monthly Downloads| |Weekly Downloads| |Conda downloads|
|
||||
|
||||
.. |UserGuide| image:: https://img.shields.io/badge/user%20guide-sanic-ff0068
|
||||
:target: https://sanicframework.org/
|
||||
@ -27,12 +27,8 @@ Sanic | Build fast. Run fast.
|
||||
:target: https://community.sanicframework.org/
|
||||
.. |Discord| image:: https://img.shields.io/discord/812221182594121728?logo=discord
|
||||
:target: https://discord.gg/FARQzAEMAA
|
||||
.. |Codecov| image:: https://codecov.io/gh/sanic-org/sanic/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/sanic-org/sanic
|
||||
.. |Build Status| image:: https://travis-ci.com/sanic-org/sanic.svg?branch=master
|
||||
:target: https://travis-ci.com/sanic-org/sanic
|
||||
.. |AppVeyor Build Status| image:: https://ci.appveyor.com/api/projects/status/d8pt3ids0ynexi8c/branch/master?svg=true
|
||||
:target: https://ci.appveyor.com/project/sanic-org/sanic
|
||||
.. |Tests| image:: https://github.com/sanic-org/sanic/actions/workflows/tests.yml/badge.svg?branch=main
|
||||
:target: https://github.com/sanic-org/sanic/actions/workflows/tests.yml
|
||||
.. |Documentation| image:: https://readthedocs.org/projects/sanic/badge/?version=latest
|
||||
:target: http://sanic.readthedocs.io/en/latest/?badge=latest
|
||||
.. |PyPI| image:: https://img.shields.io/pypi/v/sanic.svg
|
||||
@ -50,21 +46,25 @@ Sanic | Build fast. Run fast.
|
||||
.. |Awesome| image:: https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg
|
||||
:alt: Awesome Sanic List
|
||||
:target: https://github.com/mekicha/awesome-sanic
|
||||
.. |Downloads| image:: https://pepy.tech/badge/sanic/month
|
||||
.. |Monthly Downloads| image:: https://img.shields.io/pypi/dm/sanic.svg
|
||||
:alt: Downloads
|
||||
:target: https://pepy.tech/project/sanic
|
||||
.. |WkDownloads| image:: https://pepy.tech/badge/sanic/week
|
||||
.. |Weekly Downloads| image:: https://img.shields.io/pypi/dw/sanic.svg
|
||||
:alt: Downloads
|
||||
:target: https://pepy.tech/project/sanic
|
||||
.. |Conda downloads| image:: https://img.shields.io/conda/dn/conda-forge/sanic.svg
|
||||
:alt: Downloads
|
||||
:target: https://anaconda.org/conda-forge/sanic
|
||||
.. |Linode| image:: https://www.linode.com/wp-content/uploads/2021/01/Linode-Logo-Black.svg
|
||||
:alt: Linode
|
||||
:target: https://www.linode.com
|
||||
:width: 200px
|
||||
|
||||
.. end-badges
|
||||
|
||||
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 a **Python 3.8+** 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>`_
|
||||
|
||||
@ -75,17 +75,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
|
||||
------------
|
||||
@ -106,9 +100,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
|
||||
-------------------
|
||||
@ -118,7 +109,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):
|
||||
@ -148,17 +139,17 @@ And, we can verify it is working: ``curl localhost:8000 -i``
|
||||
|
||||
**Now, let's go build something fast!**
|
||||
|
||||
Minimum Python version is 3.7. If you need Python 3.6 support, please use v20.12LTS.
|
||||
Minimum Python version is 3.8. If you need Python 3.7 support, please use v22.12LTS.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
`User Guide <https://sanicframework.org>`__ and `API Documentation <http://sanic.readthedocs.io/>`__.
|
||||
`User Guide <https://sanic.dev>`__ and `API Documentation <http://sanic.readthedocs.io/>`__.
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
`Release Changelogs <https://github.com/sanic-org/sanic/blob/master/CHANGELOG.rst>`__.
|
||||
`Release Changelogs <https://sanic.readthedocs.io/en/stable/sanic/changelog.html>`__.
|
||||
|
||||
|
||||
Questions and Discussion
|
||||
|
53
SECURITY.md
53
SECURITY.md
@ -4,31 +4,42 @@
|
||||
|
||||
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.12 | until 2024-12 | :white_check_mark: |
|
||||
| 22.9 | | :x: |
|
||||
| 22.6 | | :x: |
|
||||
| 22.3 | | :x: |
|
||||
| 21.12 | until 2023-12 | :ballot_box_with_check: |
|
||||
| 21.9 | | :x: |
|
||||
| 21.6 | | :x: |
|
||||
| 21.3 | | :x: |
|
||||
| 20.12 | | :x: |
|
||||
| 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.
|
||||
|
32
codecov.yml
32
codecov.yml
@ -1,14 +1,28 @@
|
||||
codecov:
|
||||
require_ci_to_pass: no
|
||||
coverage:
|
||||
precision: 3
|
||||
round: nearest
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0.5%
|
||||
patch:
|
||||
default:
|
||||
target: auto
|
||||
threshold: 0.75%
|
||||
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/"
|
||||
- "sanic/pages/"
|
||||
- ".github/"
|
||||
- "changelogs/"
|
||||
- "docker/"
|
||||
- "docs/"
|
||||
- "examples/"
|
||||
- "scripts/"
|
||||
- "tests/"
|
||||
|
@ -1,28 +1,13 @@
|
||||
FROM alpine:3.7
|
||||
ARG BASE_IMAGE_ORG
|
||||
ARG BASE_IMAGE_NAME
|
||||
ARG BASE_IMAGE_TAG
|
||||
|
||||
RUN apk add --no-cache --update \
|
||||
curl \
|
||||
bash \
|
||||
build-base \
|
||||
ca-certificates \
|
||||
git \
|
||||
bzip2-dev \
|
||||
linux-headers \
|
||||
ncurses-dev \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
readline-dev \
|
||||
sqlite-dev
|
||||
FROM ${BASE_IMAGE_ORG}/${BASE_IMAGE_NAME}:${BASE_IMAGE_TAG}
|
||||
|
||||
RUN apk update
|
||||
RUN update-ca-certificates
|
||||
RUN rm -rf /var/cache/apk/*
|
||||
|
||||
ENV PYENV_ROOT="/root/.pyenv"
|
||||
ENV PATH="$PYENV_ROOT/bin:$PATH"
|
||||
ARG SANIC_PYPI_VERSION
|
||||
|
||||
ADD . /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN /app/docker/bin/install_python.sh 3.5.4 3.6.4
|
||||
|
||||
ENTRYPOINT ["./docker/bin/entrypoint.sh"]
|
||||
RUN pip install -U pip && pip install sanic==${SANIC_PYPI_VERSION}
|
||||
RUN apk del build-base
|
||||
|
9
docker/Dockerfile-base
Normal file
9
docker/Dockerfile-base
Normal file
@ -0,0 +1,9 @@
|
||||
ARG PYTHON_VERSION
|
||||
|
||||
FROM python:${PYTHON_VERSION}-alpine
|
||||
RUN apk update
|
||||
RUN apk add --no-cache --update build-base \
|
||||
ca-certificates \
|
||||
openssl
|
||||
RUN update-ca-certificates
|
||||
RUN rm -rf /var/cache/apk/*
|
@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
eval "$(pyenv init -)"
|
||||
eval "$(pyenv virtualenv-init -)"
|
||||
source /root/.pyenv/completions/pyenv.bash
|
||||
|
||||
pip install tox
|
||||
|
||||
exec $@
|
||||
|
@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
export CFLAGS='-O2'
|
||||
export EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x100000"
|
||||
|
||||
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
|
||||
eval "$(pyenv init -)"
|
||||
|
||||
for ver in $@
|
||||
do
|
||||
pyenv install $ver
|
||||
done
|
||||
|
||||
pyenv global $@
|
||||
pip install --upgrade pip
|
||||
pyenv rehash
|
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
|
||||
|
33
docs/sanic/api/app.rst
Normal file
33
docs/sanic/api/app.rst
Normal file
@ -0,0 +1,33 @@
|
||||
Application
|
||||
===========
|
||||
|
||||
sanic.app
|
||||
---------
|
||||
|
||||
.. automodule:: sanic.app
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
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
docs/sanic/api/blueprints.rst
Normal file
17
docs/sanic/api/blueprints.rst
Normal file
@ -0,0 +1,17 @@
|
||||
Blueprints
|
||||
==========
|
||||
|
||||
sanic.blueprints
|
||||
----------------
|
||||
|
||||
.. automodule:: sanic.blueprints
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
sanic.blueprint_group
|
||||
---------------------
|
||||
|
||||
.. automodule:: sanic.blueprint_group
|
||||
:members:
|
||||
:special-members:
|
48
docs/sanic/api/core.rst
Normal file
48
docs/sanic/api/core.rst
Normal file
@ -0,0 +1,48 @@
|
||||
Core
|
||||
====
|
||||
|
||||
sanic.cookies
|
||||
-------------
|
||||
|
||||
.. automodule:: sanic.cookies
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.handlers
|
||||
--------------
|
||||
|
||||
.. automodule:: sanic.handlers
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.headers
|
||||
--------------
|
||||
|
||||
.. automodule:: sanic.headers
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.request
|
||||
-------------
|
||||
|
||||
.. automodule:: sanic.request
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.response
|
||||
--------------
|
||||
|
||||
.. automodule:: sanic.response
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.views
|
||||
-----------
|
||||
|
||||
.. automodule:: sanic.views
|
||||
:members:
|
||||
:show-inheritance:
|
16
docs/sanic/api/exceptions.rst
Normal file
16
docs/sanic/api/exceptions.rst
Normal file
@ -0,0 +1,16 @@
|
||||
Exceptions
|
||||
==========
|
||||
|
||||
sanic.errorpages
|
||||
----------------
|
||||
|
||||
.. automodule:: sanic.errorpages
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.exceptions
|
||||
----------------
|
||||
|
||||
.. automodule:: sanic.exceptions
|
||||
:members:
|
||||
:show-inheritance:
|
18
docs/sanic/api/router.rst
Normal file
18
docs/sanic/api/router.rst
Normal file
@ -0,0 +1,18 @@
|
||||
Routing
|
||||
=======
|
||||
|
||||
sanic_routing models
|
||||
--------------------
|
||||
|
||||
.. autoclass:: sanic_routing.route::Route
|
||||
:members:
|
||||
|
||||
.. autoclass:: sanic_routing.group::RouteGroup
|
||||
:members:
|
||||
|
||||
sanic.router
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.router
|
||||
:members:
|
||||
:show-inheritance:
|
18
docs/sanic/api/server.rst
Normal file
18
docs/sanic/api/server.rst
Normal file
@ -0,0 +1,18 @@
|
||||
Sanic Server
|
||||
============
|
||||
|
||||
sanic.http
|
||||
----------
|
||||
|
||||
.. automodule:: sanic.http
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.server
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.server
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
16
docs/sanic/api/utility.rst
Normal file
16
docs/sanic/api/utility.rst
Normal file
@ -0,0 +1,16 @@
|
||||
Utility
|
||||
=======
|
||||
|
||||
sanic.compat
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.compat
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.log
|
||||
---------
|
||||
|
||||
.. automodule:: sanic.log
|
||||
:members:
|
||||
:show-inheritance:
|
@ -1,132 +1,13 @@
|
||||
📑 API Reference
|
||||
================
|
||||
|
||||
sanic.app
|
||||
---------
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
.. automodule:: sanic.app
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
sanic.blueprints
|
||||
----------------
|
||||
|
||||
.. automodule:: sanic.blueprints
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
sanic.blueprint_group
|
||||
---------------------
|
||||
|
||||
.. automodule:: sanic.blueprint_group
|
||||
:members:
|
||||
:special-members:
|
||||
|
||||
|
||||
sanic.compat
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.compat
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.config
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.config
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.cookies
|
||||
-------------
|
||||
|
||||
.. automodule:: sanic.cookies
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.errorpages
|
||||
----------------
|
||||
|
||||
.. automodule:: sanic.errorpages
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.exceptions
|
||||
----------------
|
||||
|
||||
.. automodule:: sanic.exceptions
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.handlers
|
||||
--------------
|
||||
|
||||
.. automodule:: sanic.handlers
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.http
|
||||
----------
|
||||
|
||||
.. automodule:: sanic.http
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.log
|
||||
---------
|
||||
|
||||
.. automodule:: sanic.log
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.request
|
||||
-------------
|
||||
|
||||
.. automodule:: sanic.request
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.response
|
||||
--------------
|
||||
|
||||
.. automodule:: sanic.response
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.router
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.router
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.server
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.server
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
sanic.views
|
||||
-----------
|
||||
|
||||
.. automodule:: sanic.views
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.websocket
|
||||
---------------
|
||||
|
||||
.. automodule:: sanic.websocket
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
sanic.worker
|
||||
------------
|
||||
|
||||
.. automodule:: sanic.worker
|
||||
:members:
|
||||
:show-inheritance:
|
||||
api/app
|
||||
api/blueprints
|
||||
api/core
|
||||
api/exceptions
|
||||
api/router
|
||||
api/server
|
||||
api/utility
|
||||
|
@ -1,4 +1,16 @@
|
||||
📜 Changelog
|
||||
============
|
||||
|
||||
| 🔶 Current release
|
||||
| 🔷 In support release
|
||||
|
|
||||
|
||||
.. mdinclude:: ./releases/23/23.6.md
|
||||
.. mdinclude:: ./releases/23/23.3.md
|
||||
.. mdinclude:: ./releases/22/22.12.md
|
||||
.. 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
|
||||
|
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
|
||||
|
||||
|
||||
|
55
docs/sanic/releases/22/22.12.md
Normal file
55
docs/sanic/releases/22/22.12.md
Normal file
@ -0,0 +1,55 @@
|
||||
## Version 22.12.0 🔷
|
||||
|
||||
_Current version_
|
||||
|
||||
### Features
|
||||
|
||||
- [#2569](https://github.com/sanic-org/sanic/pull/2569) Add `JSONResponse` class with some convenient methods when updating a response object
|
||||
- [#2598](https://github.com/sanic-org/sanic/pull/2598) Change `uvloop` requirement to `>=0.15.0`
|
||||
- [#2609](https://github.com/sanic-org/sanic/pull/2609) Add compatibility with `websockets` v11.0
|
||||
- [#2610](https://github.com/sanic-org/sanic/pull/2610) Kill server early on worker error
|
||||
- Raise deadlock timeout to 30s
|
||||
- [#2617](https://github.com/sanic-org/sanic/pull/2617) Scale number of running server workers
|
||||
- [#2621](https://github.com/sanic-org/sanic/pull/2621) [#2634](https://github.com/sanic-org/sanic/pull/2634) Send `SIGKILL` on subsequent `ctrl+c` to force worker exit
|
||||
- [#2622](https://github.com/sanic-org/sanic/pull/2622) Add API to restart all workers from the multiplexer
|
||||
- [#2624](https://github.com/sanic-org/sanic/pull/2624) Default to `spawn` for all subprocesses unless specifically set:
|
||||
```python
|
||||
from sanic import Sanic
|
||||
|
||||
Sanic.start_method = "fork"
|
||||
```
|
||||
- [#2625](https://github.com/sanic-org/sanic/pull/2625) Filename normalisation of form-data/multipart file uploads
|
||||
- [#2626](https://github.com/sanic-org/sanic/pull/2626) Move to HTTP Inspector:
|
||||
- Remote access to inspect running Sanic instances
|
||||
- TLS support for encrypted calls to Inspector
|
||||
- Authentication to Inspector with API key
|
||||
- Ability to extend Inspector with custom commands
|
||||
- [#2632](https://github.com/sanic-org/sanic/pull/2632) Control order of restart operations
|
||||
- [#2633](https://github.com/sanic-org/sanic/pull/2633) Move reload interval to class variable
|
||||
- [#2636](https://github.com/sanic-org/sanic/pull/2636) Add `priority` to `register_middleware` method
|
||||
- [#2639](https://github.com/sanic-org/sanic/pull/2639) Add `unquote` to `add_route` method
|
||||
- [#2640](https://github.com/sanic-org/sanic/pull/2640) ASGI websockets to receive `text` or `bytes`
|
||||
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- [#2607](https://github.com/sanic-org/sanic/pull/2607) Force socket shutdown before close to allow rebinding
|
||||
- [#2590](https://github.com/sanic-org/sanic/pull/2590) Use actual `StrEnum` in Python 3.11+
|
||||
- [#2615](https://github.com/sanic-org/sanic/pull/2615) Ensure middleware executes only once per request timeout
|
||||
- [#2627](https://github.com/sanic-org/sanic/pull/2627) Crash ASGI application on lifespan failure
|
||||
- [#2635](https://github.com/sanic-org/sanic/pull/2635) Resolve error with low-level server creation on Windows
|
||||
|
||||
|
||||
### Deprecations and Removals
|
||||
|
||||
- [#2608](https://github.com/sanic-org/sanic/pull/2608) [#2630](https://github.com/sanic-org/sanic/pull/2630) Signal conditions and triggers saved on `signal.extra`
|
||||
- [#2626](https://github.com/sanic-org/sanic/pull/2626) Move to HTTP Inspector
|
||||
- 🚨 *BREAKING CHANGE*: Moves the Inspector to a Sanic app from a simple TCP socket with a custom protocol
|
||||
- *DEPRECATE*: The `--inspect*` commands have been deprecated in favor of `inspect ...` commands
|
||||
- [#2628](https://github.com/sanic-org/sanic/pull/2628) Replace deprecated `distutils.strtobool`
|
||||
|
||||
|
||||
### Developer infrastructure
|
||||
|
||||
- [#2612](https://github.com/sanic-org/sanic/pull/2612) Add CI testing for Python 3.11
|
||||
|
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
|
||||
|
74
docs/sanic/releases/22/22.9.md
Normal file
74
docs/sanic/releases/22/22.9.md
Normal file
@ -0,0 +1,74 @@
|
||||
## Version 22.9.1
|
||||
|
||||
### Features
|
||||
|
||||
- [#2585](https://github.com/sanic-org/sanic/pull/2585) Improved error message when no applications have been registered
|
||||
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- [#2578](https://github.com/sanic-org/sanic/pull/2578) Add certificate loader for in process certificate creation
|
||||
- [#2591](https://github.com/sanic-org/sanic/pull/2591) Do not use sentinel identity for `spawn` compatibility
|
||||
- [#2592](https://github.com/sanic-org/sanic/pull/2592) Fix properties in nested blueprint groups
|
||||
- [#2595](https://github.com/sanic-org/sanic/pull/2595) Introduce sleep interval on new worker reloader
|
||||
|
||||
|
||||
### Deprecations and Removals
|
||||
|
||||
|
||||
### Developer infrastructure
|
||||
|
||||
- [#2588](https://github.com/sanic-org/sanic/pull/2588) Markdown templates on issue forms
|
||||
|
||||
|
||||
### Improved Documentation
|
||||
|
||||
- [#2556](https://github.com/sanic-org/sanic/pull/2556) v22.9 documentation
|
||||
- [#2582](https://github.com/sanic-org/sanic/pull/2582) Cleanup documentation on Windows support
|
||||
|
||||
|
||||
## Version 22.9.0
|
||||
|
||||
### 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
|
53
docs/sanic/releases/23/23.3.md
Normal file
53
docs/sanic/releases/23/23.3.md
Normal file
@ -0,0 +1,53 @@
|
||||
## Version 23.3.0
|
||||
|
||||
### Features
|
||||
- [#2545](https://github.com/sanic-org/sanic/pull/2545) Standardize init of exceptions for more consistent control of HTTP responses using exceptions
|
||||
- [#2606](https://github.com/sanic-org/sanic/pull/2606) Decode headers as UTF-8 also in ASGI
|
||||
- [#2646](https://github.com/sanic-org/sanic/pull/2646) Separate ASGI request and lifespan callables
|
||||
- [#2659](https://github.com/sanic-org/sanic/pull/2659) Use ``FALLBACK_ERROR_FORMAT`` for handlers that return ``empty()``
|
||||
- [#2662](https://github.com/sanic-org/sanic/pull/2662) Add basic file browser (HTML page) and auto-index serving
|
||||
- [#2667](https://github.com/sanic-org/sanic/pull/2667) Nicer traceback formatting (HTML page)
|
||||
- [#2668](https://github.com/sanic-org/sanic/pull/2668) Smarter error page rendering format selection; more reliant upon header and "common sense" defaults
|
||||
- [#2680](https://github.com/sanic-org/sanic/pull/2680) Check the status of socket before shutting down with ``SHUT_RDWR``
|
||||
- [#2687](https://github.com/sanic-org/sanic/pull/2687) Refresh ``Request.accept`` functionality to be more performant and spec-compliant
|
||||
- [#2696](https://github.com/sanic-org/sanic/pull/2696) Add header accessors as properties
|
||||
```
|
||||
Example-Field: Foo, Bar
|
||||
Example-Field: Baz
|
||||
```
|
||||
```python
|
||||
request.headers.example_field == "Foo, Bar,Baz"
|
||||
```
|
||||
- [#2700](https://github.com/sanic-org/sanic/pull/2700) Simpler CLI targets
|
||||
|
||||
```sh
|
||||
$ sanic path.to.module:app # global app instance
|
||||
$ sanic path.to.module:create_app # factory pattern
|
||||
$ sanic ./path/to/directory/ # simple serve
|
||||
```
|
||||
- [#2701](https://github.com/sanic-org/sanic/pull/2701) API to define a number of workers in managed processes
|
||||
- [#2704](https://github.com/sanic-org/sanic/pull/2704) Add convenience for dynamic changes to routing
|
||||
- [#2706](https://github.com/sanic-org/sanic/pull/2706) Add convenience methods for cookie creation and deletion
|
||||
|
||||
```python
|
||||
response = text("...")
|
||||
response.add_cookie("test", "It worked!", domain=".yummy-yummy-cookie.com")
|
||||
```
|
||||
- [#2707](https://github.com/sanic-org/sanic/pull/2707) Simplified ``parse_content_header`` escaping to be RFC-compliant and remove outdated FF hack
|
||||
- [#2710](https://github.com/sanic-org/sanic/pull/2710) Stricter charset handling and escaping of request URLs
|
||||
- [#2711](https://github.com/sanic-org/sanic/pull/2711) Consume body on ``DELETE`` by default
|
||||
- [#2719](https://github.com/sanic-org/sanic/pull/2719) Allow ``password`` to be passed to TLS context
|
||||
- [#2720](https://github.com/sanic-org/sanic/pull/2720) Skip middleware on ``RequestCancelled``
|
||||
- [#2721](https://github.com/sanic-org/sanic/pull/2721) Change access logging format to ``%s``
|
||||
- [#2722](https://github.com/sanic-org/sanic/pull/2722) Add ``CertLoader`` as application option for directly controlling ``SSLContext`` objects
|
||||
- [#2725](https://github.com/sanic-org/sanic/pull/2725) Worker sync state tolerance on race condition
|
||||
|
||||
### Bugfixes
|
||||
- [#2651](https://github.com/sanic-org/sanic/pull/2651) ASGI websocket to pass thru bytes as is
|
||||
- [#2697](https://github.com/sanic-org/sanic/pull/2697) Fix comparison between datetime aware and naive in ``file`` when using ``If-Modified-Since``
|
||||
|
||||
### Deprecations and Removals
|
||||
- [#2666](https://github.com/sanic-org/sanic/pull/2666) Remove deprecated ``__blueprintname__`` property
|
||||
|
||||
### Improved Documentation
|
||||
- [#2712](https://github.com/sanic-org/sanic/pull/2712) Improved example using ``'https'`` to create the redirect
|
33
docs/sanic/releases/23/23.6.md
Normal file
33
docs/sanic/releases/23/23.6.md
Normal file
@ -0,0 +1,33 @@
|
||||
## Version 23.6.0 🔶
|
||||
|
||||
### Features
|
||||
- [#2670](https://github.com/sanic-org/sanic/pull/2670) Increase `KEEP_ALIVE_TIMEOUT` default to 120 seconds
|
||||
- [#2716](https://github.com/sanic-org/sanic/pull/2716) Adding allow route overwrite option in blueprint
|
||||
- [#2724](https://github.com/sanic-org/sanic/pull/2724) and [#2792](https://github.com/sanic-org/sanic/pull/2792) Add a new exception signal for ALL exceptions raised anywhere in application
|
||||
- [#2727](https://github.com/sanic-org/sanic/pull/2727) Add name prefixing to BP groups
|
||||
- [#2754](https://github.com/sanic-org/sanic/pull/2754) Update request type on middleware types
|
||||
- [#2770](https://github.com/sanic-org/sanic/pull/2770) Better exception message on startup time application induced import error
|
||||
- [#2776](https://github.com/sanic-org/sanic/pull/2776) Set multiprocessing start method early
|
||||
- [#2785](https://github.com/sanic-org/sanic/pull/2785) Add custom typing to config and ctx objects
|
||||
- [#2790](https://github.com/sanic-org/sanic/pull/2790) Add `request.client_ip`
|
||||
|
||||
### Bugfixes
|
||||
- [#2728](https://github.com/sanic-org/sanic/pull/2728) Fix traversals for intended results
|
||||
- [#2729](https://github.com/sanic-org/sanic/pull/2729) Handle case when headers argument of ResponseStream constructor is None
|
||||
- [#2737](https://github.com/sanic-org/sanic/pull/2737) Fix type annotation for `JSONREsponse` default content type
|
||||
- [#2740](https://github.com/sanic-org/sanic/pull/2740) Use Sanic's serializer for JSON responses in the Inspector
|
||||
- [#2760](https://github.com/sanic-org/sanic/pull/2760) Support for `Request.get_current` in ASGI mode
|
||||
- [#2773](https://github.com/sanic-org/sanic/pull/2773) Alow Blueprint routes to explicitly define error_format
|
||||
- [#2774](https://github.com/sanic-org/sanic/pull/2774) Resolve headers on different renderers
|
||||
- [#2782](https://github.com/sanic-org/sanic/pull/2782) Resolve pypy compatibility issues
|
||||
|
||||
### Deprecations and Removals
|
||||
- [#2777](https://github.com/sanic-org/sanic/pull/2777) Remove Python 3.7 support
|
||||
|
||||
### Developer infrastructure
|
||||
- [#2766](https://github.com/sanic-org/sanic/pull/2766) Unpin setuptools version
|
||||
- [#2779](https://github.com/sanic-org/sanic/pull/2779) Run keep alive tests in loop to get available port
|
||||
|
||||
### Improved Documentation
|
||||
- [#2741](https://github.com/sanic-org/sanic/pull/2741) Better documentation examples about running Sanic
|
||||
From that list, the items to highlight in the release notes:
|
@ -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)
|
||||
if __name__ == "__main__":
|
||||
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,54 @@
|
||||
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.run(host="0.0.0.0", port=8000, debug=True, auto_reload=False)
|
||||
app.blueprint(bp, url_prefix="/bp")
|
||||
|
||||
if __name__ == "__main__":
|
||||
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")
|
||||
@ -36,4 +37,5 @@ app.blueprint(blueprint)
|
||||
app.blueprint(blueprint2)
|
||||
app.blueprint(blueprint3)
|
||||
|
||||
app.run(host="0.0.0.0", port=9999, debug=True)
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=9999, debug=True)
|
||||
|
@ -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
|
||||
@ -27,25 +29,45 @@ def proxy(request, path):
|
||||
path=path,
|
||||
_server=https.config.SERVER_NAME,
|
||||
_external=True,
|
||||
_scheme="http",
|
||||
_scheme="https",
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
https.run(port=HTTPS_PORT, debug=True)
|
||||
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
|
||||
|
||||
if __name__ == "__main__":
|
||||
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,11 +32,12 @@ 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)
|
||||
|
||||
|
||||
app.run(host="0.0.0.0", port=8000, workers=2)
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000, workers=2)
|
||||
|
@ -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)
|
||||
|
@ -20,4 +20,5 @@ def test(request):
|
||||
return text("hey")
|
||||
|
||||
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=8000)
|
||||
|
@ -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)
|
||||
|
@ -6,5 +6,5 @@ data = ""
|
||||
for i in range(1, 250000):
|
||||
data += str(i)
|
||||
|
||||
r = requests.post('http://0.0.0.0:8000/stream', data=data)
|
||||
r = requests.post("http://0.0.0.0:8000/stream", data=data)
|
||||
print(r.text)
|
||||
|
@ -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,24 @@
|
||||
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)
|
||||
|
||||
if __name__ == "__main__":
|
||||
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,38 +1,68 @@
|
||||
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)
|
||||
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()
|
||||
try:
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt as e:
|
||||
loop.stop()
|
||||
finally:
|
||||
server.before_stop()
|
||||
if __name__ == "__main__":
|
||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||
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: AsyncioServer = loop.run_until_complete(serv_task)
|
||||
loop.run_until_complete(server.startup())
|
||||
|
||||
# Wait for server to close
|
||||
close_task = server.close()
|
||||
loop.run_until_complete(close_task)
|
||||
# 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:
|
||||
loop.stop()
|
||||
finally:
|
||||
loop.run_until_complete(server.before_stop())
|
||||
|
||||
# Complete all tasks on the loop
|
||||
for connection in server.connections:
|
||||
connection.close_if_idle()
|
||||
server.after_stop()
|
||||
# Wait for server to close
|
||||
close_task = server.close()
|
||||
loop.run_until_complete(close_task)
|
||||
|
||||
# Complete all tasks on the loop
|
||||
for connection in server.connections:
|
||||
connection.close_if_idle()
|
||||
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
guide/Procfile
Normal file
1
guide/Procfile
Normal file
@ -0,0 +1 @@
|
||||
web: sanic --port=${PORT} --host=0.0.0.0 --workers=1 server:app
|
1
guide/config/en/general.yaml
Normal file
1
guide/config/en/general.yaml
Normal file
@ -0,0 +1 @@
|
||||
current_version: "23.6"
|
15
guide/config/en/navbar.yaml
Normal file
15
guide/config/en/navbar.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
root:
|
||||
- label: Home
|
||||
path: index.html
|
||||
- label: Community
|
||||
items:
|
||||
- label: Forums
|
||||
href: https://community.sanicframework.org
|
||||
- label: Discord
|
||||
href: https://discord.gg/FARQzAEMAA
|
||||
- label: Twitter
|
||||
href: https://twitter.com/sanicframework
|
||||
- label: Help
|
||||
path: ./help.html
|
||||
- label: GitHub
|
||||
href: https://github.com/sanic-org/sanic
|
257
guide/config/en/sidebar.yaml
Normal file
257
guide/config/en/sidebar.yaml
Normal file
@ -0,0 +1,257 @@
|
||||
root:
|
||||
- label: User Guide
|
||||
items:
|
||||
- label: General
|
||||
items:
|
||||
- label: Introduction
|
||||
path: guide/introduction.html
|
||||
- label: Getting Started
|
||||
path: guide/getting-started.html
|
||||
- label: Basics
|
||||
items:
|
||||
- label: Sanic Application
|
||||
path: guide/basics/app.html
|
||||
- label: Handlers
|
||||
path: guide/basics/handlers.html
|
||||
- label: Request
|
||||
path: guide/basics/request.html
|
||||
- label: Response
|
||||
path: guide/basics/response.html
|
||||
- label: Routing
|
||||
path: guide/basics/routing.html
|
||||
- label: Listeners
|
||||
path: guide/basics/listeners.html
|
||||
- label: Middleware
|
||||
path: guide/basics/middleware.html
|
||||
- label: Headers
|
||||
path: guide/basics/headers.html
|
||||
- label: Cookies
|
||||
path: guide/basics/cookies.html
|
||||
- label: Background Tasks
|
||||
path: guide/basics/tasks.html
|
||||
- label: Advanced
|
||||
items:
|
||||
- label: Class Based Views
|
||||
path: guide/advanced/class-based-views.html
|
||||
- label: Proxy Configuration
|
||||
path: guide/advanced/proxy-headers.html
|
||||
- label: Streaming
|
||||
path: guide/advanced/streaming.html
|
||||
- label: Websockets
|
||||
path: guide/advanced/websockets.html
|
||||
- label: Versioning
|
||||
path: guide/advanced/versioning.html
|
||||
- label: Signals
|
||||
path: guide/advanced/signals.html
|
||||
- label: Best Practices
|
||||
items:
|
||||
- label: Blueprints
|
||||
path: guide/best-practices/blueprints.html
|
||||
- label: Exceptions
|
||||
path: guide/best-practices/exceptions.html
|
||||
- label: Decorators
|
||||
path: guide/best-practices/decorators.html
|
||||
- label: Logging
|
||||
path: guide/best-practices/logging.html
|
||||
- label: Testing
|
||||
path: guide/best-practices/testing.html
|
||||
- label: Running Sanic
|
||||
items:
|
||||
- label: Configuration
|
||||
path: guide/running/configuration.html
|
||||
- label: Development
|
||||
path: guide/running/development.html
|
||||
- label: Server
|
||||
path: guide/running/running.html
|
||||
- label: Worker Manager
|
||||
path: guide/running/manager.html
|
||||
- label: Dynamic Applications
|
||||
path: guide/running/app-loader.html
|
||||
- label: Inspector
|
||||
path: guide/running/inspector.html
|
||||
- label: Deployment
|
||||
items:
|
||||
- label: Caddy
|
||||
path: guide/deployment/caddy.html
|
||||
- label: Nginx
|
||||
path: guide/deployment/nginx.html
|
||||
- label: Docker
|
||||
path: guide/deployment/docker.html
|
||||
- label: How to ...
|
||||
items:
|
||||
- label: Table of Contents
|
||||
path: guide/how-to/table-of-contents.html
|
||||
- label: Application Mounting
|
||||
path: guide/how-to/mounting.html
|
||||
- label: Authentication
|
||||
path: guide/how-to/authentication.html
|
||||
- label: Autodiscovery
|
||||
path: guide/how-to/autodiscovery.html
|
||||
- label: CORS
|
||||
path: guide/how-to/cors.html
|
||||
- label: ORM
|
||||
path: guide/how-to/orm.html
|
||||
- label: Static Redirects
|
||||
path: guide/how-to/static-redirects.html
|
||||
- label: TLS/SSL/HTTPS
|
||||
path: guide/how-to/tls.html
|
||||
- label: Plugins
|
||||
items:
|
||||
- label: Sanic Extensions
|
||||
items:
|
||||
- label: Getting Started
|
||||
path: plugins/sanic-ext/getting-started.html
|
||||
- label: HTTP - Methods
|
||||
path: plugins/sanic-ext/http/methods.html
|
||||
- label: HTTP - CORS Protection
|
||||
path: plugins/sanic-ext/http/cors.html
|
||||
- label: OpenAPI - Basics
|
||||
path: plugins/sanic-ext/openapi/basics.html
|
||||
- label: OpenAPI - UI
|
||||
path: plugins/sanic-ext/openapi/ui.html
|
||||
- label: OpenAPI - Decorators
|
||||
path: plugins/sanic-ext/openapi/decorators.html
|
||||
# - label: OpenAPI - Advanced
|
||||
# path: plugins/sanic-ext/openapi/advanced.html
|
||||
- label: OpenAPI - Auto Documentation
|
||||
path: plugins/sanic-ext/openapi/autodoc.html
|
||||
- label: OpenAPI - Security
|
||||
path: plugins/sanic-ext/openapi/security.html
|
||||
- label: Convenience
|
||||
path: plugins/sanic-ext/convenience.html
|
||||
- label: Templating - Jinja
|
||||
path: plugins/sanic-ext/templating/jinja.html
|
||||
- label: Templating - html5tagger
|
||||
path: plugins/sanic-ext/templating/html5tagger.html
|
||||
- label: Dependency Injection
|
||||
path: plugins/sanic-ext/injection.html
|
||||
- label: Validation
|
||||
path: plugins/sanic-ext/validation.html
|
||||
- label: Health Monitor
|
||||
path: plugins/sanic-ext/health-monitor.html
|
||||
- label: Background Logger
|
||||
path: plugins/sanic-ext/logger.html
|
||||
- label: Configuration
|
||||
path: plugins/sanic-ext/configuration.html
|
||||
- label: Custom Extensions
|
||||
path: plugins/sanic-ext/custom.html
|
||||
- label: Sanic Testing
|
||||
items:
|
||||
- label: Getting Started
|
||||
path: plugins/sanic-testing/getting-started.html
|
||||
- label: Test Clients
|
||||
path: plugins/sanic-testing/clients.html
|
||||
- label: Release Notes
|
||||
items:
|
||||
- label: "2023"
|
||||
items:
|
||||
- label: Sanic 23.6
|
||||
path: release-notes/2023/v23.6.html
|
||||
- label: Sanic 23.3
|
||||
path: release-notes/2023/v23.3.html
|
||||
- label: "2022"
|
||||
items:
|
||||
- label: Sanic 22.12
|
||||
path: release-notes/2022/v22.12.html
|
||||
- label: Sanic 22.9
|
||||
path: release-notes/2022/v22.9.html
|
||||
- label: Sanic 22.6
|
||||
path: release-notes/2022/v22.6.html
|
||||
- label: Sanic 22.3
|
||||
path: release-notes/2022/v22.3.html
|
||||
- label: "2021"
|
||||
items:
|
||||
- label: Sanic 21.12
|
||||
path: release-notes/2021/v21.12.html
|
||||
- label: Sanic 21.9
|
||||
path: release-notes/2021/v21.9.html
|
||||
- label: Sanic 21.6
|
||||
path: release-notes/2021/v21.6.html
|
||||
- label: Sanic 21.3
|
||||
path: release-notes/2021/v21.3.html
|
||||
- label: Organization
|
||||
items:
|
||||
- label: Contributing
|
||||
path: organization/contributing.html
|
||||
- label: Code of Conduct
|
||||
path: organization/code-of-conduct.html
|
||||
- label: S.C.O.P.E. (Governance)
|
||||
path: organization/scope.html
|
||||
- label: Policies
|
||||
path: organization/policies.html
|
||||
- label: API Reference
|
||||
items:
|
||||
- label: Application
|
||||
items:
|
||||
- label: sanic.app
|
||||
path: /api/sanic.app.html
|
||||
- label: sanic.config
|
||||
path: /api/sanic.config.html
|
||||
- label: sanic.application
|
||||
path: /api/sanic.application.html
|
||||
- label: Blueprint
|
||||
items:
|
||||
- label: sanic.blueprints
|
||||
path: /api/sanic.blueprints.html
|
||||
- label: sanic.blueprint_group
|
||||
path: /api/sanic.blueprint_group.html
|
||||
- label: Constant
|
||||
items:
|
||||
- label: sanic.constants
|
||||
path: /api/sanic.constants.html
|
||||
- label: Core
|
||||
items:
|
||||
- label: sanic.cookies
|
||||
path: /api/sanic.cookies.html
|
||||
- label: sanic.handlers
|
||||
path: /api/sanic.handlers.html
|
||||
- label: sanic.headers
|
||||
path: /api/sanic.headers.html
|
||||
- label: sanic.middleware
|
||||
path: /api/sanic.middleware.html
|
||||
- label: sanic.mixins
|
||||
path: /api/sanic.mixins.html
|
||||
- label: sanic.request
|
||||
path: /api/sanic.request.html
|
||||
- label: sanic.response
|
||||
path: /api/sanic.response.html
|
||||
- label: sanic.views
|
||||
path: /api/sanic.views.html
|
||||
- label: Display
|
||||
items:
|
||||
- label: sanic.pages
|
||||
path: /api/sanic.pages.html
|
||||
- label: Exception
|
||||
items:
|
||||
- label: sanic.errorpages
|
||||
path: /api/sanic.errorpages.html
|
||||
- label: sanic.exceptions
|
||||
path: /api/sanic.exceptions.html
|
||||
- label: Model
|
||||
items:
|
||||
- label: sanic.models
|
||||
path: /api/sanic.models.html
|
||||
- label: Routing
|
||||
items:
|
||||
- label: sanic.router
|
||||
path: /api/sanic.router.html
|
||||
- label: sanic.signals
|
||||
path: /api/sanic.signals.html
|
||||
- label: Server
|
||||
items:
|
||||
- label: sanic.http
|
||||
path: /api/sanic.http.html
|
||||
- label: sanic.server
|
||||
path: /api/sanic.server.html
|
||||
- label: sanic.worker
|
||||
path: /api/sanic.worker.html
|
||||
- label: Utility
|
||||
items:
|
||||
- label: sanic.compat
|
||||
path: /api/sanic.compat.html
|
||||
- label: sanic.helpers
|
||||
path: /api/sanic.helpers.html
|
||||
- label: sanic.log
|
||||
path: /api/sanic.log.html
|
||||
- label: sanic.utils
|
||||
path: /api/sanic.utils.html
|
3668
guide/content/en/emoji.py
Normal file
3668
guide/content/en/emoji.py
Normal file
File diff suppressed because it is too large
Load Diff
202
guide/content/en/guide/advanced/class-based-views.md
Normal file
202
guide/content/en/guide/advanced/class-based-views.md
Normal file
@ -0,0 +1,202 @@
|
||||
# Class Based Views
|
||||
|
||||
## Why use them?
|
||||
|
||||
.. column::
|
||||
|
||||
### The problem
|
||||
|
||||
A common pattern when designing an API is to have multiple functionality on the same endpoint that depends upon the HTTP method.
|
||||
|
||||
While both of these options work, they are not good design practices and may be hard to maintain over time as your project grows.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.get("/foo")
|
||||
async def foo_get(request):
|
||||
...
|
||||
|
||||
@app.post("/foo")
|
||||
async def foo_post(request):
|
||||
...
|
||||
|
||||
@app.put("/foo")
|
||||
async def foo_put(request):
|
||||
...
|
||||
|
||||
@app.route("/bar", methods=["GET", "POST", "PATCH"])
|
||||
async def bar(request):
|
||||
if request.method == "GET":
|
||||
...
|
||||
|
||||
elif request.method == "POST":
|
||||
...
|
||||
|
||||
elif request.method == "PATCH":
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
### The solution
|
||||
|
||||
Class-based views are simply classes that implement response behavior to requests. They provide a way to compartmentalize handling of different HTTP request types at the same endpoint.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic.views import HTTPMethodView
|
||||
|
||||
class FooBar(HTTPMethodView):
|
||||
async def get(self, request):
|
||||
...
|
||||
|
||||
async def post(self, request):
|
||||
...
|
||||
|
||||
async def put(self, request):
|
||||
...
|
||||
|
||||
app.add_route(FooBar.as_view(), "/foobar")
|
||||
```
|
||||
|
||||
## Defining a view
|
||||
|
||||
A class-based view should subclass `HTTPMethodView`. You can then implement class methods with the name of the corresponding HTTP method. If a request is received that has no defined method, a `405: Method not allowed` response will be generated.
|
||||
|
||||
.. column::
|
||||
|
||||
To register a class-based view on an endpoint, the `app.add_route` method is used. The first argument should be the defined class with the method `as_view` invoked, and the second should be the URL endpoint.
|
||||
|
||||
The available methods are:
|
||||
|
||||
- get
|
||||
- post
|
||||
- put
|
||||
- patch
|
||||
- delete
|
||||
- head
|
||||
- options
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic.views import HTTPMethodView
|
||||
from sanic.response import text
|
||||
|
||||
class SimpleView(HTTPMethodView):
|
||||
|
||||
def get(self, request):
|
||||
return text("I am get method")
|
||||
|
||||
# You can also use async syntax
|
||||
async def post(self, request):
|
||||
return text("I am post method")
|
||||
|
||||
def put(self, request):
|
||||
return text("I am put method")
|
||||
|
||||
def patch(self, request):
|
||||
return text("I am patch method")
|
||||
|
||||
def delete(self, request):
|
||||
return text("I am delete method")
|
||||
|
||||
app.add_route(SimpleView.as_view(), "/")
|
||||
```
|
||||
|
||||
## Path parameters
|
||||
|
||||
.. column::
|
||||
|
||||
You can use path parameters exactly as discussed in [the routing section](/guide/basics/routing.md).
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
class NameView(HTTPMethodView):
|
||||
|
||||
def get(self, request, name):
|
||||
return text("Hello {}".format(name))
|
||||
|
||||
app.add_route(NameView.as_view(), "/<name>")
|
||||
```
|
||||
|
||||
## Decorators
|
||||
|
||||
As discussed in [the decorators section](/guide/best-practices/decorators.md), often you will need to add functionality to endpoints with the use of decorators. You have two options with CBV:
|
||||
|
||||
1. Apply to _all_ HTTP methods in the view
|
||||
2. Apply individually to HTTP methods in the view
|
||||
|
||||
Let's see what the options look like:
|
||||
|
||||
.. column::
|
||||
|
||||
### Apply to all methods
|
||||
|
||||
If you want to add any decorators to the class, you can set the `decorators` class variable. These will be applied to the class when `as_view` is called.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
class ViewWithDecorator(HTTPMethodView):
|
||||
decorators = [some_decorator_here]
|
||||
|
||||
def get(self, request, name):
|
||||
return text("Hello I have a decorator")
|
||||
|
||||
def post(self, request, name):
|
||||
return text("Hello I also have a decorator")
|
||||
|
||||
app.add_route(ViewWithDecorator.as_view(), "/url")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
### Apply to individual methods
|
||||
|
||||
But if you just want to decorate some methods and not all methods, you can as shown here.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
class ViewWithSomeDecorator(HTTPMethodView):
|
||||
|
||||
@staticmethod
|
||||
@some_decorator_here
|
||||
def get(request, name):
|
||||
return text("Hello I have a decorator")
|
||||
|
||||
def post(self, request, name):
|
||||
return text("Hello I do not have any decorators")
|
||||
|
||||
@some_decorator_here
|
||||
def patch(self, request, name):
|
||||
return text("Hello I have a decorator")
|
||||
```
|
||||
|
||||
## Generating a URL
|
||||
|
||||
.. column::
|
||||
|
||||
This works just like [generating any other URL](/guide/basics/routing.md#generating-a-url), except that the class name is a part of the endpoint.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
def index(request):
|
||||
url = app.url_for("SpecialClassView")
|
||||
return redirect(url)
|
||||
|
||||
class SpecialClassView(HTTPMethodView):
|
||||
def get(self, request):
|
||||
return text("Hello from the Special Class View!")
|
||||
|
||||
app.add_route(SpecialClassView.as_view(), "/special_class_view")
|
||||
```
|
||||
|
477
guide/content/en/guide/advanced/proxy-headers.md
Normal file
477
guide/content/en/guide/advanced/proxy-headers.md
Normal file
@ -0,0 +1,477 @@
|
||||
# Proxy configuration
|
||||
|
||||
When you use a reverse proxy server (e.g. nginx), the value of `request.ip` will contain the IP of a proxy, typically `127.0.0.1`. Almost always, this is **not** what you will want.
|
||||
|
||||
Sanic may be configured to use proxy headers for determining the true client IP, available as `request.remote_addr`. The full external URL is also constructed from header fields _if available_.
|
||||
|
||||
|
||||
.. tip:: Heads up
|
||||
|
||||
Without proper precautions, a malicious client may use proxy headers to spoof its own IP. To avoid such issues, Sanic does not use any proxy headers unless explicitly enabled.
|
||||
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Services behind reverse proxies must configure one or more of the following [configuration values](/guide/deployment/configuration.md):
|
||||
|
||||
- `FORWARDED_SECRET`
|
||||
- `REAL_IP_HEADER`
|
||||
- `PROXIES_COUNT`
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app.config.FORWARDED_SECRET = "super-duper-secret"
|
||||
app.config.REAL_IP_HEADER = "CF-Connecting-IP"
|
||||
app.config.PROXIES_COUNT = 2
|
||||
```
|
||||
|
||||
## Forwarded header
|
||||
|
||||
In order to use the `Forwarded` header, you should set `app.config.FORWARDED_SECRET` to a value known to the trusted proxy server. The secret is used to securely identify a specific proxy server.
|
||||
|
||||
Sanic ignores any elements without the secret key, and will not even parse the header if no secret is set.
|
||||
|
||||
All other proxy headers are ignored once a trusted forwarded element is found, as it already carries complete information about the client.
|
||||
|
||||
To learn more about the `Forwarded` header, read the related [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded) and [Nginx](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/) articles.
|
||||
|
||||
## Traditional proxy headers
|
||||
|
||||
### IP Headers
|
||||
|
||||
When your proxy forwards you the IP address in a known header, you can tell Sanic what that is with the `REAL_IP_HEADER` config value.
|
||||
|
||||
### X-Forwarded-For
|
||||
|
||||
This header typically contains a chain of IP addresses through each layer of a proxy. Setting `PROXIES_COUNT` tells Sanic how deep to look to get an actual IP address for the client. This value should equal the _expected_ number of IP addresses in the chain.
|
||||
|
||||
### Other X-headers
|
||||
|
||||
If a client IP is found by one of these methods, Sanic uses the following headers for URL parts:
|
||||
|
||||
- x-forwarded-proto
|
||||
- x-forwarded-host
|
||||
- x-forwarded-port
|
||||
- x-forwarded-path
|
||||
- x-scheme
|
||||
|
||||
## Examples
|
||||
|
||||
In the following examples, all requests will assume that the endpoint looks like this:
|
||||
```python
|
||||
@app.route("/fwd")
|
||||
async def forwarded(request):
|
||||
return json(
|
||||
{
|
||||
"remote_addr": request.remote_addr,
|
||||
"scheme": request.scheme,
|
||||
"server_name": request.server_name,
|
||||
"server_port": request.server_port,
|
||||
"forwarded": request.forwarded,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
---
|
||||
|
||||
##### Example 1
|
||||
Without configured FORWARDED_SECRET, x-headers should be respected
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \
|
||||
-H "X-Real-IP: 127.0.0.2" \
|
||||
-H "X-Forwarded-For: 127.0.1.1" \
|
||||
-H "X-Scheme: ws" \
|
||||
-H "Host: local.site" | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "127.0.0.2",
|
||||
"scheme": "ws",
|
||||
"server_name": "local.site",
|
||||
"server_port": 80,
|
||||
"forwarded": {
|
||||
"for": "127.0.0.2",
|
||||
"proto": "ws"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 2
|
||||
FORWARDED_SECRET now configured
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H 'Forwarded: for=1.1.1.1, for=injected;host=", for="[::2]";proto=https;host=me.tld;path="/app/";secret=mySecret,for=broken;;secret=b0rked, for=127.0.0.3;scheme=http;port=1234' \
|
||||
-H "X-Real-IP: 127.0.0.2" \
|
||||
-H "X-Forwarded-For: 127.0.1.1" \
|
||||
-H "X-Scheme: ws" \
|
||||
-H "Host: local.site" | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "[::2]",
|
||||
"scheme": "https",
|
||||
"server_name": "me.tld",
|
||||
"server_port": 443,
|
||||
"forwarded": {
|
||||
"for": "[::2]",
|
||||
"proto": "https",
|
||||
"host": "me.tld",
|
||||
"path": "/app/",
|
||||
"secret": "mySecret"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 3
|
||||
Empty Forwarded header -> use X-headers
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H "X-Real-IP: 127.0.0.2" \
|
||||
-H "X-Forwarded-For: 127.0.1.1" \
|
||||
-H "X-Scheme: ws" \
|
||||
-H "Host: local.site" | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "127.0.0.2",
|
||||
"scheme": "ws",
|
||||
"server_name": "local.site",
|
||||
"server_port": 80,
|
||||
"forwarded": {
|
||||
"for": "127.0.0.2",
|
||||
"proto": "ws"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 4
|
||||
Header present but not matching anything
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H "Forwarded: nomatch" | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "",
|
||||
"scheme": "http",
|
||||
"server_name": "localhost",
|
||||
"server_port": 8000,
|
||||
"forwarded": {}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 5
|
||||
Forwarded header present but no matching secret -> use X-headers
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H "Forwarded: for=1.1.1.1;secret=x, for=127.0.0.1" \
|
||||
-H "X-Real-IP: 127.0.0.2" | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "127.0.0.2",
|
||||
"scheme": "http",
|
||||
"server_name": "localhost",
|
||||
"server_port": 8000,
|
||||
"forwarded": {
|
||||
"for": "127.0.0.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 6
|
||||
Different formatting and hitting both ends of the header
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H 'Forwarded: Secret="mySecret";For=127.0.0.4;Port=1234' | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "127.0.0.4",
|
||||
"scheme": "http",
|
||||
"server_name": "localhost",
|
||||
"server_port": 1234,
|
||||
"forwarded": {
|
||||
"secret": "mySecret",
|
||||
"for": "127.0.0.4",
|
||||
"port": 1234
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 7
|
||||
Test escapes (modify this if you see anyone implementing quoted-pairs)
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H 'Forwarded: for=test;quoted="\,x=x;y=\";secret=mySecret' | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "test",
|
||||
"scheme": "http",
|
||||
"server_name": "localhost",
|
||||
"server_port": 8000,
|
||||
"forwarded": {
|
||||
"for": "test",
|
||||
"quoted": "\\,x=x;y=\\",
|
||||
"secret": "mySecret"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 8
|
||||
Secret insulated by malformed field #1
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H 'Forwarded: for=test;secret=mySecret;b0rked;proto=wss;' | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "test",
|
||||
"scheme": "http",
|
||||
"server_name": "localhost",
|
||||
"server_port": 8000,
|
||||
"forwarded": {
|
||||
"for": "test",
|
||||
"secret": "mySecret"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 9
|
||||
Secret insulated by malformed field #2
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H 'Forwarded: for=test;b0rked;secret=mySecret;proto=wss' | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "",
|
||||
"scheme": "wss",
|
||||
"server_name": "localhost",
|
||||
"server_port": 8000,
|
||||
"forwarded": {
|
||||
"secret": "mySecret",
|
||||
"proto": "wss"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 10
|
||||
Unexpected termination should not lose existing acceptable values
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H 'Forwarded: b0rked;secret=mySecret;proto=wss' | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "",
|
||||
"scheme": "wss",
|
||||
"server_name": "localhost",
|
||||
"server_port": 8000,
|
||||
"forwarded": {
|
||||
"secret": "mySecret",
|
||||
"proto": "wss"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 11
|
||||
Field normalization
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "mySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H 'Forwarded: PROTO=WSS;BY="CAFE::8000";FOR=unknown;PORT=X;HOST="A:2";PATH="/With%20Spaces%22Quoted%22/sanicApp?key=val";SECRET=mySecret' | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "",
|
||||
"scheme": "wss",
|
||||
"server_name": "a",
|
||||
"server_port": 2,
|
||||
"forwarded": {
|
||||
"proto": "wss",
|
||||
"by": "[cafe::8000]",
|
||||
"host": "a:2",
|
||||
"path": "/With Spaces\"Quoted\"/sanicApp?key=val",
|
||||
"secret": "mySecret"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
.. column::
|
||||
|
||||
##### Example 12
|
||||
Using "by" field as secret
|
||||
```python
|
||||
app.config.PROXIES_COUNT = 1
|
||||
app.config.REAL_IP_HEADER = "x-real-ip"
|
||||
app.config.FORWARDED_SECRET = "_proxySecret"
|
||||
```
|
||||
```bash
|
||||
$ curl localhost:8000/fwd \
|
||||
-H 'Forwarded: for=1.2.3.4; by=_proxySecret' | jq
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
# curl response
|
||||
{
|
||||
"remote_addr": "1.2.3.4",
|
||||
"scheme": "http",
|
||||
"server_name": "localhost",
|
||||
"server_port": 8000,
|
||||
"forwarded": {
|
||||
"for": "1.2.3.4",
|
||||
"by": "_proxySecret"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
346
guide/content/en/guide/advanced/signals.md
Normal file
346
guide/content/en/guide/advanced/signals.md
Normal file
@ -0,0 +1,346 @@
|
||||
# Signals
|
||||
|
||||
Signals provide a way for one part of your application to tell another part that something happened.
|
||||
|
||||
```python
|
||||
@app.signal("user.registration.created")
|
||||
async def send_registration_email(**context):
|
||||
await send_email(context["email"], template="registration")
|
||||
|
||||
@app.post("/register")
|
||||
async def handle_registration(request):
|
||||
await do_registration(request)
|
||||
await request.app.dispatch(
|
||||
"user.registration.created",
|
||||
context={"email": request.json.email}
|
||||
})
|
||||
```
|
||||
|
||||
## Adding a signal
|
||||
|
||||
.. column::
|
||||
|
||||
The API for adding a signal is very similar to adding a route.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
async def my_signal_handler():
|
||||
print("something happened")
|
||||
|
||||
app.add_signal(my_signal_handler, "something.happened.ohmy")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
But, perhaps a slightly more convenient method is to use the built-in decorators.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.signal("something.happened.ohmy")
|
||||
async def my_signal_handler():
|
||||
print("something happened")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
If the signal requires conditions, make sure to add them while adding the handler.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
async def my_signal_handler1():
|
||||
print("something happened")
|
||||
|
||||
app.add_signal(
|
||||
my_signal_handler,
|
||||
"something.happened.ohmy1",
|
||||
conditions={"some_condition": "value"}
|
||||
)
|
||||
|
||||
@app.signal("something.happened.ohmy2", conditions={"some_condition": "value"})
|
||||
async def my_signal_handler2():
|
||||
print("something happened")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Signals can also be declared on blueprints
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
bp = Blueprint("foo")
|
||||
|
||||
@bp.signal("something.happened.ohmy")
|
||||
async def my_signal_handler():
|
||||
print("something happened")
|
||||
```
|
||||
|
||||
## Built-in signals
|
||||
|
||||
In addition to creating a new signal, there are a number of built-in signals that are dispatched from Sanic itself. These signals exist to provide developers with more opportunities to add functionality into the request and server lifecycles.
|
||||
|
||||
*Added in v21.9*
|
||||
|
||||
.. column::
|
||||
|
||||
You can attach them just like any other signal to an application or blueprint instance.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.signal("http.lifecycle.complete")
|
||||
async def my_signal_handler(conn_info):
|
||||
print("Connection has been closed")
|
||||
```
|
||||
|
||||
These signals are the signals that are available, along with the arguments that the handlers take, and the conditions that attach (if any).
|
||||
|
||||
| Event name | Arguments | Conditions |
|
||||
| -------------------------- | ------------------------------- | --------------------------------------------------------- |
|
||||
| `http.routing.before` | request | |
|
||||
| `http.routing.after` | request, route, kwargs, handler | |
|
||||
| `http.handler.before` | request | |
|
||||
| `http.handler.after` | request | |
|
||||
| `http.lifecycle.begin` | conn_info | |
|
||||
| `http.lifecycle.read_head` | head | |
|
||||
| `http.lifecycle.request` | request | |
|
||||
| `http.lifecycle.handle` | request | |
|
||||
| `http.lifecycle.read_body` | body | |
|
||||
| `http.lifecycle.exception` | request, exception | |
|
||||
| `http.lifecycle.response` | request, response | |
|
||||
| `http.lifecycle.send` | data | |
|
||||
| `http.lifecycle.complete` | conn_info | |
|
||||
| `http.middleware.before` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` |
|
||||
| `http.middleware.after` | request, response | `{"attach_to": "request"}` or `{"attach_to": "response"}` |
|
||||
| `server.exception.report` | app, exception | |
|
||||
| `server.init.before` | app, loop | |
|
||||
| `server.init.after` | app, loop | |
|
||||
| `server.shutdown.before` | app, loop | |
|
||||
| `server.shutdown.after` | app, loop | |
|
||||
|
||||
Version 22.9 added `http.handler.before` and `http.handler.after`.
|
||||
|
||||
Version 23.6 added `server.exception.report`.
|
||||
|
||||
.. column::
|
||||
|
||||
To make using the built-in signals easier, there is an `Enum` object that contains all of the allowed built-ins. With a modern IDE this will help so that you do not need to remember the full list of event names as strings.
|
||||
|
||||
*Added in v21.12*
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic.signals import Event
|
||||
|
||||
@app.signal(Event.HTTP_LIFECYCLE_COMPLETE)
|
||||
async def my_signal_handler(conn_info):
|
||||
print("Connection has been closed")
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
.. column::
|
||||
|
||||
Signals are based off of an _event_. An event, is simply a string in the following pattern:
|
||||
|
||||
.. column::
|
||||
|
||||
```
|
||||
namespace.reference.action
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. tip:: Events must have three parts. If you do not know what to use, try these patterns:
|
||||
|
||||
- `my_app.something.happened`
|
||||
- `sanic.notice.hello`
|
||||
|
||||
|
||||
### Event parameters
|
||||
|
||||
.. column::
|
||||
|
||||
An event can be "dynamic" and declared using the same syntax as [path parameters](../basics/routing.md#path-parameters). This allows matching based upon arbitrary values.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.signal("foo.bar.<thing>")
|
||||
async def signal_handler(thing):
|
||||
print(f"[signal_handler] {thing=}")
|
||||
|
||||
@app.get("/")
|
||||
async def trigger(request):
|
||||
await app.dispatch("foo.bar.baz")
|
||||
return response.text("Done.")
|
||||
```
|
||||
|
||||
Checkout [path parameters](../basics/routing.md#path-parameters) for more information on allowed type definitions.
|
||||
|
||||
|
||||
.. warning:: Only the third part of an event (the action) may be dynamic:
|
||||
|
||||
- `foo.bar.<thing>` 🆗
|
||||
- `foo.<bar>.baz` ❌
|
||||
|
||||
|
||||
### Waiting
|
||||
|
||||
.. column::
|
||||
|
||||
In addition to executing a signal handler, your application can wait for an event to be triggered.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
await app.event("foo.bar.baz")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
**IMPORTANT**: waiting is a blocking function. Therefore, you likely will want this to run in a [background task](../basics/tasks.md).
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
async def wait_for_event(app):
|
||||
while True:
|
||||
print("> waiting")
|
||||
await app.event("foo.bar.baz")
|
||||
print("> event found\n")
|
||||
|
||||
@app.after_server_start
|
||||
async def after_server_start(app, loop):
|
||||
app.add_task(wait_for_event(app))
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
If your event was defined with a dynamic path, you can use `*` to catch any action.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.signal("foo.bar.<thing>")
|
||||
|
||||
...
|
||||
|
||||
await app.event("foo.bar.*")
|
||||
```
|
||||
|
||||
## Dispatching
|
||||
|
||||
*In the future, Sanic will dispatch some events automatically to assist developers to hook into life cycle events.*
|
||||
|
||||
.. column::
|
||||
|
||||
Dispatching an event will do two things:
|
||||
|
||||
1. execute any signal handlers defined on the event, and
|
||||
2. resolve anything that is "waiting" for the event to complete.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.signal("foo.bar.<thing>")
|
||||
async def foo_bar(thing):
|
||||
print(f"{thing=}")
|
||||
|
||||
await app.dispatch("foo.bar.baz")
|
||||
```
|
||||
```
|
||||
thing=baz
|
||||
```
|
||||
|
||||
### Context
|
||||
|
||||
.. column::
|
||||
|
||||
Sometimes you may find the need to pass extra information into the signal handler. In our first example above, we wanted our email registration process to have the email address for the user.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.signal("user.registration.created")
|
||||
async def send_registration_email(**context):
|
||||
print(context)
|
||||
|
||||
await app.dispatch(
|
||||
"user.registration.created",
|
||||
context={"hello": "world"}
|
||||
)
|
||||
```
|
||||
```
|
||||
{'hello': 'world'}
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
Signals are dispatched in a background task.
|
||||
|
||||
|
||||
### Blueprints
|
||||
|
||||
Dispatching blueprint signals works similar in concept to [middleware](../basics/middleware.md). Anything that is done from the app level, will trickle down to the blueprints. However, dispatching on a blueprint, will only execute the signals that are defined on that blueprint.
|
||||
|
||||
.. column::
|
||||
|
||||
Perhaps an example is easier to explain:
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
bp = Blueprint("bp")
|
||||
|
||||
app_counter = 0
|
||||
bp_counter = 0
|
||||
|
||||
@app.signal("foo.bar.baz")
|
||||
def app_signal():
|
||||
nonlocal app_counter
|
||||
app_counter += 1
|
||||
|
||||
@bp.signal("foo.bar.baz")
|
||||
def bp_signal():
|
||||
nonlocal bp_counter
|
||||
bp_counter += 1
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Running `app.dispatch("foo.bar.baz")` will execute both signals.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
await app.dispatch("foo.bar.baz")
|
||||
assert app_counter == 1
|
||||
assert bp_counter == 1
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Running `bp.dispatch("foo.bar.baz")` will execute only the blueprint signal.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
await bp.dispatch("foo.bar.baz")
|
||||
assert app_counter == 1
|
||||
assert bp_counter == 2
|
||||
```
|
||||
|
151
guide/content/en/guide/advanced/streaming.md
Normal file
151
guide/content/en/guide/advanced/streaming.md
Normal file
@ -0,0 +1,151 @@
|
||||
# Streaming
|
||||
|
||||
## Request streaming
|
||||
|
||||
Sanic allows you to stream data sent by the client to begin processing data as the bytes arrive.
|
||||
|
||||
.. column::
|
||||
|
||||
When enabled on an endpoint, you can stream the request body using `await request.stream.read()`.
|
||||
|
||||
That method will return `None` when the body is completed.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic.views import stream
|
||||
|
||||
class SimpleView(HTTPMethodView):
|
||||
@stream
|
||||
async def post(self, request):
|
||||
result = ""
|
||||
while True:
|
||||
body = await request.stream.read()
|
||||
if body is None:
|
||||
break
|
||||
result += body.decode("utf-8")
|
||||
return text(result)
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
It also can be enabled with a keyword argument in the decorator...
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.post("/stream", stream=True)
|
||||
async def handler(request):
|
||||
...
|
||||
body = await request.stream.read()
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
... or the `add_route()` method.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
bp.add_route(
|
||||
bp_handler,
|
||||
"/bp_stream",
|
||||
methods=["POST"],
|
||||
stream=True,
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
Only post, put and patch decorators have stream argument.
|
||||
|
||||
|
||||
## Response streaming
|
||||
|
||||
.. column::
|
||||
|
||||
Sanic allows you to stream content to the client.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
async def test(request):
|
||||
response = await request.respond(content_type="text/csv")
|
||||
await response.send("foo,")
|
||||
await response.send("bar")
|
||||
|
||||
# Optionally, you can explicitly end the stream by calling:
|
||||
await response.eof()
|
||||
```
|
||||
|
||||
This is useful in situations where you want to stream content to the client that originates in an external service, like a database. For example, you can stream database records to the client with the asynchronous cursor that `asyncpg` provides.
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
async def index(request):
|
||||
response = await request.respond()
|
||||
conn = await asyncpg.connect(database='test')
|
||||
async with conn.transaction():
|
||||
async for record in conn.cursor('SELECT generate_series(0, 10)'):
|
||||
await response.send(record[0])
|
||||
```
|
||||
|
||||
|
||||
You can explicitly end a stream by calling `await response.eof()`. It a convenience method to replace `await response.send("", True)`. It should be called **one time** *after* your handler has determined that it has nothing left to send back to the client. While it is *optional* to use with Sanic server, if you are running Sanic in ASGI mode, then you **must** explicitly terminate the stream.
|
||||
|
||||
*Calling `eof` became optional in v21.6*
|
||||
|
||||
## File streaming
|
||||
|
||||
.. column::
|
||||
|
||||
Sanic provides `sanic.response.file_stream` function that is useful when you want to send a large file. It returns a `StreamingHTTPResponse` object and will use chunked transfer encoding by default; for this reason Sanic doesn’t add `Content-Length` HTTP header in the response.
|
||||
|
||||
A typical use case might be streaming an video file.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/mp4")
|
||||
async def handler_file_stream(request):
|
||||
return await response.file_stream(
|
||||
"/path/to/sample.mp4",
|
||||
chunk_size=1024,
|
||||
mime_type="application/metalink4+xml",
|
||||
headers={
|
||||
"Content-Disposition": 'Attachment; filename="nicer_name.meta4"',
|
||||
"Content-Type": "application/metalink4+xml",
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
If you want to use the `Content-Length` header, you can disable chunked transfer encoding and add it manually simply by adding the `Content-Length` header.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from aiofiles import os as async_os
|
||||
from sanic.response import file_stream
|
||||
|
||||
@app.route("/")
|
||||
async def index(request):
|
||||
file_path = "/srv/www/whatever.png"
|
||||
|
||||
file_stat = await async_os.stat(file_path)
|
||||
headers = {"Content-Length": str(file_stat.st_size)}
|
||||
|
||||
return await file_stream(
|
||||
file_path,
|
||||
headers=headers,
|
||||
)
|
||||
```
|
||||
|
170
guide/content/en/guide/advanced/versioning.md
Normal file
170
guide/content/en/guide/advanced/versioning.md
Normal file
@ -0,0 +1,170 @@
|
||||
# Versioning
|
||||
|
||||
It is standard practice in API building to add versions to your endpoints. This allows you to easily differentiate incompatible endpoints when you try and change your API down the road in a breaking manner.
|
||||
|
||||
Adding a version will add a `/v{version}` url prefix to your endpoints.
|
||||
|
||||
The version can be a `int`, `float`, or `str`. Acceptable values:
|
||||
|
||||
- `1`, `2`, `3`
|
||||
- `1.1`, `2.25`, `3.0`
|
||||
- `"1"`, `"v1"`, `"v1.1"`
|
||||
|
||||
## Per route
|
||||
|
||||
.. column::
|
||||
|
||||
You can pass a version number to the routes directly.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
# /v1/text
|
||||
@app.route("/text", version=1)
|
||||
def handle_request(request):
|
||||
return response.text("Hello world! Version 1")
|
||||
|
||||
# /v2/text
|
||||
@app.route("/text", version=2)
|
||||
def handle_request(request):
|
||||
return response.text("Hello world! Version 2")
|
||||
```
|
||||
|
||||
## Per Blueprint
|
||||
|
||||
.. column::
|
||||
|
||||
You can also pass a version number to the blueprint, which will apply to all routes in that blueprint.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
bp = Blueprint("test", url_prefix="/foo", version=1)
|
||||
|
||||
# /v1/foo/html
|
||||
@bp.route("/html")
|
||||
def handle_request(request):
|
||||
return response.html("<p>Hello world!</p>")
|
||||
```
|
||||
|
||||
## Per Blueprint Group
|
||||
|
||||
.. column::
|
||||
|
||||
In order to simplify the management of the versioned blueprints, you can provide a version number in the blueprint
|
||||
group. The same will be inherited to all the blueprint grouped under it if the blueprints don't already override the
|
||||
same information with a value specified while creating a blueprint instance.
|
||||
|
||||
When using blueprint groups for managing the versions, the following order is followed to apply the Version prefix to
|
||||
the routes being registered.
|
||||
|
||||
1. Route Level configuration
|
||||
2. Blueprint level configuration
|
||||
3. Blueprint Group level configuration
|
||||
|
||||
If we find a more pointed versioning specification, we will pick that over the more generic versioning specification
|
||||
provided under the Blueprint or Blueprint Group
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic.blueprints import Blueprint
|
||||
from sanic.response import json
|
||||
|
||||
bp1 = Blueprint(
|
||||
name="blueprint-1",
|
||||
url_prefix="/bp1",
|
||||
version=1.25,
|
||||
)
|
||||
bp2 = Blueprint(
|
||||
name="blueprint-2",
|
||||
url_prefix="/bp2",
|
||||
)
|
||||
|
||||
group = Blueprint.group(
|
||||
[bp1, bp2],
|
||||
url_prefix="/bp-group",
|
||||
version="v2",
|
||||
)
|
||||
|
||||
# GET /v1.25/bp-group/bp1/endpoint-1
|
||||
@bp1.get("/endpoint-1")
|
||||
async def handle_endpoint_1_bp1(request):
|
||||
return json({"Source": "blueprint-1/endpoint-1"})
|
||||
|
||||
# GET /v2/bp-group/bp2/endpoint-2
|
||||
@bp2.get("/endpoint-1")
|
||||
async def handle_endpoint_1_bp2(request):
|
||||
return json({"Source": "blueprint-2/endpoint-1"})
|
||||
|
||||
# GET /v1/bp-group/bp2/endpoint-2
|
||||
@bp2.get("/endpoint-2", version=1)
|
||||
async def handle_endpoint_2_bp2(request):
|
||||
return json({"Source": "blueprint-2/endpoint-2"})
|
||||
```
|
||||
|
||||
## Version prefix
|
||||
|
||||
As seen above, the `version` that is applied to a route is **always** the first segment in the generated URI path. Therefore, to make it possible to add path segments before the version, every place that a `version` argument is passed, you can also pass `version_prefix`.
|
||||
|
||||
The `version_prefix` argument can be defined in:
|
||||
|
||||
- `app.route` and `bp.route` decorators (and all the convenience decorators also)
|
||||
- `Blueprint` instantiation
|
||||
- `Blueprint.group` constructor
|
||||
- `BlueprintGroup` instantiation
|
||||
- `app.blueprint` registration
|
||||
|
||||
If there are definitions in multiple places, a more specific definition overrides a more general. This list provides that hierarchy.
|
||||
|
||||
The default value of `version_prefix` is `/v`.
|
||||
|
||||
.. column::
|
||||
|
||||
An often requested feature is to be able to mount versioned routes on `/api`. This can easily be accomplished with `version_prefix`.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
# /v1/my/path
|
||||
app.route("/my/path", version=1, version_prefix="/api/v")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Perhaps a more compelling usage is to load all `/api` routes into a single `BlueprintGroup`.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
# /v1/my/path
|
||||
app = Sanic(__name__)
|
||||
v2ip = Blueprint("v2ip", url_prefix="/ip", version=2)
|
||||
api = Blueprint.group(v2ip, version_prefix="/api/version")
|
||||
|
||||
# /api/version2/ip
|
||||
@v2ip.get("/")
|
||||
async def handler(request):
|
||||
return text(request.ip)
|
||||
|
||||
app.blueprint(api)
|
||||
```
|
||||
|
||||
We can therefore learn that a route's URI is:
|
||||
|
||||
```
|
||||
version_prefix + version + url_prefix + URI definition
|
||||
```
|
||||
|
||||
|
||||
.. tip::
|
||||
|
||||
Just like with `url_prefix`, it is possible to define path parameters inside a `version_prefix`. It is perfectly legitimate to do this. Just remember that every route will have that parameter injected into the handler.
|
||||
|
||||
```python
|
||||
version_prefix="/<foo:str>/v"
|
||||
```
|
||||
|
||||
|
||||
*Added in v21.6*
|
82
guide/content/en/guide/advanced/websockets.md
Normal file
82
guide/content/en/guide/advanced/websockets.md
Normal file
@ -0,0 +1,82 @@
|
||||
# Websockets
|
||||
|
||||
Sanic provides an easy to use abstraction on top of [websockets](https://websockets.readthedocs.io/en/stable/).
|
||||
|
||||
## Routing
|
||||
|
||||
.. column::
|
||||
|
||||
Websocket handlers can be hooked up to the router similar to regular handlers.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Request, Websocket
|
||||
|
||||
async def feed(request: Request, ws: Websocket):
|
||||
pass
|
||||
|
||||
app.add_websocket_route(feed, "/feed")
|
||||
```
|
||||
```python
|
||||
from sanic import Request, Websocket
|
||||
|
||||
@app.websocket("/feed")
|
||||
async def feed(request: Request, ws: Websocket):
|
||||
pass
|
||||
```
|
||||
|
||||
## Handler
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Typically, a websocket handler will want to hold open a loop.
|
||||
|
||||
It can then use the `send()` and `recv()` methods on the second object injected into the handler.
|
||||
|
||||
This example is a simple endpoint that echos back to the client messages that it receives.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Request, Websocket
|
||||
|
||||
@app.websocket("/feed")
|
||||
async def feed(request: Request, ws: Websocket):
|
||||
while True:
|
||||
data = "hello!"
|
||||
print("Sending: " + data)
|
||||
await ws.send(data)
|
||||
data = await ws.recv()
|
||||
print("Received: " + data)
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
You can simplify your loop by just iterating over the `Websocket` object in a for loop.
|
||||
|
||||
*Added in v22.9*
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Request, Websocket
|
||||
|
||||
@app.websocket("/feed")
|
||||
async def feed(request: Request, ws: Websocket):
|
||||
async for msg in ws:
|
||||
await ws.send(msg)
|
||||
```
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
See [configuration section](/guide/deployment/configuration.md) for more details, however the defaults are shown below.
|
||||
|
||||
```python
|
||||
app.config.WEBSOCKET_MAX_SIZE = 2 ** 20
|
||||
app.config.WEBSOCKET_PING_INTERVAL = 20
|
||||
app.config.WEBSOCKET_PING_TIMEOUT = 20
|
||||
```
|
1
guide/content/en/guide/basics/README.md
Normal file
1
guide/content/en/guide/basics/README.md
Normal file
@ -0,0 +1 @@
|
||||
# Basics
|
517
guide/content/en/guide/basics/app.md
Normal file
517
guide/content/en/guide/basics/app.md
Normal file
@ -0,0 +1,517 @@
|
||||
# Sanic Application
|
||||
|
||||
## Instance
|
||||
|
||||
.. column::
|
||||
|
||||
The most basic building block is the `Sanic()` instance. It is not required, but the custom is to instantiate this in a file called `server.py`.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
# /path/to/server.py
|
||||
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic("MyHelloWorldApp")
|
||||
```
|
||||
|
||||
## Application context
|
||||
|
||||
Most applications will have the need to share/reuse data or objects across different parts of the code base. The most common example is DB connections.
|
||||
|
||||
.. column::
|
||||
|
||||
In versions of Sanic prior to v21.3, this was commonly done by attaching an attribute to the application instance
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
# Raises a warning as deprecated feature in 21.3
|
||||
app = Sanic("MyApp")
|
||||
app.db = Database()
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Because this can create potential problems with name conflicts, and to be consistent with [request context](./request.md#context) objects, v21.3 introduces application level context object.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
# Correct way to attach objects to the application
|
||||
app = Sanic("MyApp")
|
||||
app.ctx.db = Database()
|
||||
```
|
||||
|
||||
## App Registry
|
||||
|
||||
.. column::
|
||||
|
||||
When you instantiate a Sanic instance, that can be retrieved at a later time from the Sanic app registry. This can be useful, for example, if you need to access your Sanic instance from a location where it is not otherwise accessible.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
# ./path/to/server.py
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic("my_awesome_server")
|
||||
|
||||
# ./path/to/somewhere_else.py
|
||||
from sanic import Sanic
|
||||
|
||||
app = Sanic.get_app("my_awesome_server")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
If you call `Sanic.get_app("non-existing")` on an app that does not exist, it will raise `SanicException` by default. You can, instead, force the method to return a new instance of Sanic with that name.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app = Sanic.get_app(
|
||||
"non-existing",
|
||||
force_create=True,
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
If there is **only one** Sanic instance registered, then calling `Sanic.get_app()` with no arguments will return that instance
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
Sanic("My only app")
|
||||
|
||||
app = Sanic.get_app()
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
.. column::
|
||||
|
||||
Sanic holds the configuration in the `config` attribute of the `Sanic` instance. Configuration can be modified **either** using dot-notation **OR** like a dictionary.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app = Sanic('myapp')
|
||||
|
||||
app.config.DB_NAME = 'appdb'
|
||||
app.config['DB_USER'] = 'appuser'
|
||||
|
||||
db_settings = {
|
||||
'DB_HOST': 'localhost',
|
||||
'DB_NAME': 'appdb',
|
||||
'DB_USER': 'appuser'
|
||||
}
|
||||
app.config.update(db_settings)
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. note:: Heads up
|
||||
|
||||
Config keys _should_ be uppercase. But, this is mainly by convention, and lowercase will work most of the time.
|
||||
```
|
||||
app.config.GOOD = "yay!"
|
||||
app.config.bad = "boo"
|
||||
```
|
||||
|
||||
|
||||
There is much [more detail about configuration](/guide/deployment/configuration.md) later on.
|
||||
|
||||
## Customization
|
||||
|
||||
The Sanic application instance can be customized for your application needs in a variety of ways at instantiation.
|
||||
|
||||
### Custom configuration
|
||||
|
||||
.. column::
|
||||
|
||||
This simplest form of custom configuration would be to pass your own object directly into that Sanic application instance
|
||||
|
||||
If you create a custom configuration object, it is *highly* recommended that you subclass the Sanic `Config` option to inherit its behavior. You could use this option for adding properties, or your own set of custom logic.
|
||||
|
||||
*Added in v21.6*
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic.config import Config
|
||||
|
||||
class MyConfig(Config):
|
||||
FOO = "bar"
|
||||
|
||||
app = Sanic(..., config=MyConfig())
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
A useful example of this feature would be if you wanted to use a config file in a form that differs from what is [supported](../deployment/configuration.md#using-sanic-update-config).
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Sanic, text
|
||||
from sanic.config import Config
|
||||
|
||||
class TomlConfig(Config):
|
||||
def __init__(self, *args, path: str, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
with open(path, "r") as f:
|
||||
self.apply(toml.load(f))
|
||||
|
||||
def apply(self, config):
|
||||
self.update(self._to_uppercase(config))
|
||||
|
||||
def _to_uppercase(self, obj: Dict[str, Any]) -> Dict[str, Any]:
|
||||
retval: Dict[str, Any] = {}
|
||||
for key, value in obj.items():
|
||||
upper_key = key.upper()
|
||||
if isinstance(value, list):
|
||||
retval[upper_key] = [
|
||||
self._to_uppercase(item) for item in value
|
||||
]
|
||||
elif isinstance(value, dict):
|
||||
retval[upper_key] = self._to_uppercase(value)
|
||||
else:
|
||||
retval[upper_key] = value
|
||||
return retval
|
||||
|
||||
toml_config = TomlConfig(path="/path/to/config.toml")
|
||||
app = Sanic(toml_config.APP_NAME, config=toml_config)
|
||||
```
|
||||
|
||||
### Custom context
|
||||
|
||||
.. column::
|
||||
|
||||
By default, the application context is a [`SimpleNamespace()`](https://docs.python.org/3/library/types.html#types.SimpleNamespace) that allows you to set any properties you want on it. However, you also have the option of passing any object whatsoever instead.
|
||||
|
||||
*Added in v21.6*
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app = Sanic(..., ctx=1)
|
||||
```
|
||||
|
||||
```python
|
||||
app = Sanic(..., ctx={})
|
||||
```
|
||||
|
||||
```python
|
||||
class MyContext:
|
||||
...
|
||||
|
||||
app = Sanic(..., ctx=MyContext())
|
||||
```
|
||||
|
||||
### Custom requests
|
||||
|
||||
.. column::
|
||||
|
||||
It is sometimes helpful to have your own `Request` class, and tell Sanic to use that instead of the default. One example is if you wanted to modify the default `request.id` generator.
|
||||
|
||||
|
||||
|
||||
.. note:: Important
|
||||
|
||||
It is important to remember that you are passing the *class* not an instance of the class.
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
import time
|
||||
|
||||
from sanic import Request, Sanic, text
|
||||
|
||||
class NanoSecondRequest(Request):
|
||||
@classmethod
|
||||
def generate_id(*_):
|
||||
return time.time_ns()
|
||||
|
||||
app = Sanic(..., request_class=NanoSecondRequest)
|
||||
|
||||
@app.get("/")
|
||||
async def handler(request):
|
||||
return text(str(request.id))
|
||||
```
|
||||
|
||||
### Custom error handler
|
||||
|
||||
.. column::
|
||||
|
||||
See [exception handling](../best-practices/exceptions.md#custom-error-handling) for more
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic.handlers import ErrorHandler
|
||||
|
||||
class CustomErrorHandler(ErrorHandler):
|
||||
def default(self, request, exception):
|
||||
''' handles errors that have no error handlers assigned '''
|
||||
# You custom error handling logic...
|
||||
return super().default(request, exception)
|
||||
|
||||
app = Sanic(..., error_handler=CustomErrorHandler())
|
||||
```
|
||||
|
||||
### Custom dumps function
|
||||
|
||||
.. column::
|
||||
|
||||
It may sometimes be necessary or desirable to provide a custom function that serializes an object to JSON data.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
import ujson
|
||||
|
||||
dumps = partial(ujson.dumps, escape_forward_slashes=False)
|
||||
app = Sanic(__name__, dumps=dumps)
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Or, perhaps use another library or create your own.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from orjson import dumps
|
||||
|
||||
app = Sanic(__name__, dumps=dumps)
|
||||
```
|
||||
|
||||
### Custom loads function
|
||||
|
||||
.. column::
|
||||
|
||||
Similar to `dumps`, you can also provide a custom function for deserializing data.
|
||||
|
||||
*Added in v22.9*
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from orjson import loads
|
||||
|
||||
app = Sanic(__name__, loads=loads)
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. new:: NEW in v23.6
|
||||
|
||||
### Custom typed application
|
||||
|
||||
The correct, default type of a Sanic application instance is:
|
||||
|
||||
```python
|
||||
sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]
|
||||
```
|
||||
|
||||
It refers to two generic types:
|
||||
|
||||
1. The first is the type of the configuration object. It defaults to `sanic.config.Config`, but can be any subclass of that.
|
||||
2. The second is the type of the application context. It defaults to `types.SimpleNamespace`, but can be **any object** as show above.
|
||||
|
||||
Let's look at some examples of how the type will change.
|
||||
|
||||
.. column::
|
||||
|
||||
Consider this example where we pass a custom subclass of `Config` and a custom context object.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Sanic
|
||||
from sanic.config import Config
|
||||
|
||||
class CustomConfig(Config):
|
||||
pass
|
||||
|
||||
app = Sanic("test", config=CustomConfig())
|
||||
reveal_type(app) # N: Revealed type is "sanic.app.Sanic[main.CustomConfig, types.SimpleNamespace]"
|
||||
```
|
||||
```
|
||||
sanic.app.Sanic[main.CustomConfig, types.SimpleNamespace]
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Similarly, when passing a custom context object, the type will change to reflect that.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Sanic
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
app = Sanic("test", ctx=Foo())
|
||||
reveal_type(app) # N: Revealed type is "sanic.app.Sanic[sanic.config.Config, main.Foo]"
|
||||
```
|
||||
```
|
||||
sanic.app.Sanic[sanic.config.Config, main.Foo]
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Of course, you can set both the config and context to custom types.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Sanic
|
||||
from sanic.config import Config
|
||||
|
||||
class CustomConfig(Config):
|
||||
pass
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
app = Sanic("test", config=CustomConfig(), ctx=Foo())
|
||||
reveal_type(app) # N: Revealed type is "sanic.app.Sanic[main.CustomConfig, main.Foo]"
|
||||
```
|
||||
```
|
||||
sanic.app.Sanic[main.CustomConfig, main.Foo]
|
||||
```
|
||||
|
||||
This pattern is particularly useful if you create a custom type alias for your application instance so that you can use it to annotate listeners and handlers.
|
||||
|
||||
```python
|
||||
# ./path/to/types.py
|
||||
from sanic.app import Sanic
|
||||
from sanic.config import Config
|
||||
from myapp.context import MyContext
|
||||
from typing import TypeAlias
|
||||
|
||||
MyApp = TypeAlias("MyApp", Sanic[Config, MyContext])
|
||||
```
|
||||
|
||||
```python
|
||||
# ./path/to/listeners.py
|
||||
from myapp.types import MyApp
|
||||
|
||||
def add_listeners(app: MyApp):
|
||||
@app.before_server_start
|
||||
async def before_server_start(app: MyApp):
|
||||
# do something with your fully typed app instance
|
||||
await app.ctx.db.connect()
|
||||
```
|
||||
|
||||
```python
|
||||
# ./path/to/server.py
|
||||
from myapp.types import MyApp
|
||||
from myapp.context import MyContext
|
||||
from myapp.config import MyConfig
|
||||
from myapp.listeners import add_listeners
|
||||
|
||||
app = Sanic("myapp", config=MyConfig(), ctx=MyContext())
|
||||
add_listeners(app)
|
||||
```
|
||||
|
||||
*Added in v23.6*
|
||||
|
||||
### Custom typed request
|
||||
|
||||
Sanic also allows you to customize the type of the request object. This is useful if you want to add custom properties to the request object, or be able to access your custom properties of a typed application instance.
|
||||
|
||||
The correct, default type of a Sanic request instance is:
|
||||
|
||||
```python
|
||||
sanic.request.Request[
|
||||
sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace],
|
||||
types.SimpleNamespace
|
||||
]
|
||||
```
|
||||
|
||||
It refers to two generic types:
|
||||
|
||||
1. The first is the type of the application instance. It defaults to `sanic.app.Sanic[sanic.config.Config, types.SimpleNamespace]`, but can be any subclass of that.
|
||||
2. The second is the type of the request context. It defaults to `types.SimpleNamespace`, but can be **any object** as show above in [custom requests](#custom-requests).
|
||||
|
||||
Let's look at some examples of how the type will change.
|
||||
|
||||
.. column::
|
||||
|
||||
Expanding upon the full example above where there is a type alias for a customized application instance, we can also create a custom request type so that we can access those same type annotations.
|
||||
|
||||
Of course, you do not need type aliases for this to work. We are only showing them here to cut down on the amount of code shown.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Request
|
||||
from myapp.types import MyApp
|
||||
from types import SimpleNamespace
|
||||
|
||||
def add_routes(app: MyApp):
|
||||
@app.get("/")
|
||||
async def handler(request: Request[MyApp, SimpleNamespace]):
|
||||
# do something with your fully typed app instance
|
||||
results = await request.app.ctx.db.query("SELECT * FROM foo")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Perhaps you have a custom request object that generates a custom context object. You can type annotate it to properly access those properties with your IDE as shown here.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Request, Sanic
|
||||
from sanic.config import Config
|
||||
|
||||
class CustomConfig(Config):
|
||||
pass
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
|
||||
class RequestContext:
|
||||
foo: Foo
|
||||
|
||||
class CustomRequest(Request[Sanic[CustomConfig, Foo], RequestContext]):
|
||||
@staticmethod
|
||||
def make_context() -> RequestContext:
|
||||
ctx = RequestContext()
|
||||
ctx.foo = Foo()
|
||||
return ctx
|
||||
|
||||
app = Sanic(
|
||||
"test", config=CustomConfig(), ctx=Foo(), request_class=CustomRequest
|
||||
)
|
||||
|
||||
@app.get("/")
|
||||
async def handler(request: CustomRequest):
|
||||
# Full access to typed:
|
||||
# - custom application configuration object
|
||||
# - custom application context object
|
||||
# - custom request context object
|
||||
pass
|
||||
```
|
||||
|
||||
See more information in the [custom request context](./request.md#custom-request-context) section.
|
||||
|
||||
*Added in v23.6*
|
||||
|
108
guide/content/en/guide/basics/cookies.md
Normal file
108
guide/content/en/guide/basics/cookies.md
Normal file
@ -0,0 +1,108 @@
|
||||
# Cookies
|
||||
|
||||
## Reading
|
||||
|
||||
.. column::
|
||||
|
||||
Cookies can be accessed via the `Request` object’s `cookies` dictionary.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/cookie")
|
||||
async def test(request):
|
||||
test_cookie = request.cookies.get("test")
|
||||
return text(f"Test cookie: {test_cookie}")
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
💡 The `request.cookies` object is one of a few types that is a dictionary with each value being a `list`. This is because HTTP allows a single key to be reused to send multiple values.
|
||||
|
||||
Most of the time you will want to use the `.get()` method to access the first element and not a `list`. If you do want a `list` of all items, you can use `.getlist()`.
|
||||
|
||||
*Added in v23.3*
|
||||
|
||||
|
||||
|
||||
## Writing
|
||||
|
||||
.. column::
|
||||
|
||||
When returning a response, cookies can be set on the `Response` object: `response.cookies`. This object is an instance of `CookieJar` which is a special sort of dictionary that automatically will write the response headers for you.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/cookie")
|
||||
async def test(request):
|
||||
response = text("There's a cookie up in this response")
|
||||
response.add_cookie(
|
||||
"test",
|
||||
"It worked!",
|
||||
domain=".yummy-yummy-cookie.com",
|
||||
httponly=True
|
||||
)
|
||||
return response
|
||||
```
|
||||
|
||||
Response cookies can be set like dictionary values and have the following parameters available:
|
||||
|
||||
- `path: str` - The subset of URLs to which this cookie applies. Defaults to `/`.
|
||||
- `domain: str` - Specifies the domain for which the cookie is valid. An explicitly specified domain must always start with a dot.
|
||||
- `max_age: int` - Number of seconds the cookie should live for.
|
||||
- `expires: datetime` - The time for the cookie to expire on the client’s browser. Usually it is better to use max-age instead.
|
||||
- `secure: bool` - Specifies whether the cookie will only be sent via HTTPS. Defaults to `True`.
|
||||
- `httponly: bool` - Specifies whether the cookie cannot be read by JavaScript.
|
||||
- `samesite: str` - Available values: Lax, Strict, and None. Defaults to `Lax`.
|
||||
- `comment: str` - A comment (metadata).
|
||||
- `host_prefix: bool` - Whether to add the `__Host-` prefix to the cookie.
|
||||
- `secure_prefix: bool` - Whether to add the `__Secure-` prefix to the cookie.
|
||||
- `partitioned: bool` - Whether to mark the cookie as partitioned.
|
||||
|
||||
To better understand the implications and usage of these values, it might be helpful to read the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies) on [setting cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie).
|
||||
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
By default, Sanic will set the `secure` flag to `True` to ensure that cookies are only sent over HTTPS as a sensible default. This should not be impactful for local development since secure cookies over HTTP should still be sent to `localhost`. For more information, you should read the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) on [secure cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Secure).
|
||||
|
||||
|
||||
## Deleting
|
||||
|
||||
.. column::
|
||||
|
||||
Cookies can be removed semantically or explicitly.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/cookie")
|
||||
async def test(request):
|
||||
response = text("Time to eat some cookies muahaha")
|
||||
|
||||
# This cookie will be set to expire in 0 seconds
|
||||
response.delete_cookie("eat_me")
|
||||
|
||||
# This cookie will self destruct in 5 seconds
|
||||
response.add_cookie("fast_bake", "Be quick!", max_age=5)
|
||||
|
||||
return response
|
||||
```
|
||||
|
||||
*Don't forget to add `path` or `domain` if needed!*
|
||||
|
||||
## Eating
|
||||
|
||||
.. column::
|
||||
|
||||
Sanic likes cookies
|
||||
|
||||
.. column::
|
||||
|
||||
.. attrs::
|
||||
:class: is-size-1 has-text-centered
|
||||
|
||||
🍪
|
131
guide/content/en/guide/basics/handlers.md
Normal file
131
guide/content/en/guide/basics/handlers.md
Normal file
@ -0,0 +1,131 @@
|
||||
# Handlers
|
||||
|
||||
The next important building block are your _handlers_. These are also sometimes called "views".
|
||||
|
||||
In Sanic, a handler is any callable that takes at least a `Request` instance as an argument, and returns either an `HTTPResponse` instance, or a coroutine that does the same.
|
||||
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Huh? 😕
|
||||
|
||||
It is a **function**; either synchronous or asynchronous.
|
||||
|
||||
The job of the handler is to respond to an endpoint and do something. This is where the majority of your business logic will go.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
def i_am_a_handler(request):
|
||||
return HTTPResponse()
|
||||
|
||||
async def i_am_ALSO_a_handler(request):
|
||||
return HTTPResponse()
|
||||
```
|
||||
|
||||
.. tip:: Heads up
|
||||
|
||||
If you want to learn more about encapsulating your logic, checkout [class based views](/guide/advanced/class-based-views.md).
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Then, all you need to do is wire it up to an endpoint. We'll learn more about [routing soon](./routing.md).
|
||||
|
||||
Let's look at a practical example.
|
||||
|
||||
- We use a convenience decorator on our app instance: `@app.get()`
|
||||
- And a handy convenience method for generating out response object: `text()`
|
||||
|
||||
Mission accomplished :muscle:
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic.response import text
|
||||
|
||||
@app.get("/foo")
|
||||
async def foo_handler(request):
|
||||
return text("I said foo!")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## A word about _async_...
|
||||
|
||||
.. column::
|
||||
|
||||
It is entirely possible to write handlers that are synchronous.
|
||||
|
||||
In this example, we are using the _blocking_ `time.sleep()` to simulate 100ms of processing time. Perhaps this represents fetching data from a DB, or a 3rd-party website.
|
||||
|
||||
Using four (4) worker processes and a common benchmarking tool:
|
||||
|
||||
- **956** requests in 30.10s
|
||||
- Or, about **31.76** requests/second
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.get("/sync")
|
||||
def sync_handler(request):
|
||||
time.sleep(0.1)
|
||||
return text("Done.")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Just by changing to the asynchronous alternative `asyncio.sleep()`, we see an incredible change in performance. 🚀
|
||||
|
||||
Using the same four (4) worker processes:
|
||||
|
||||
- **115,590** requests in 30.08s
|
||||
- Or, about **3,843.17** requests/second
|
||||
|
||||
.. attrs::
|
||||
:class: is-size-3
|
||||
|
||||
🤯
|
||||
|
||||
Okay... this is a ridiculously overdramatic result. And any benchmark you see is inherently very biased. This example is meant to over-the-top show the benefit of `async/await` in the web world. Results will certainly vary. Tools like Sanic and other async Python libraries are not magic bullets that make things faster. They make them _more efficient_.
|
||||
|
||||
In our example, the asynchronous version is so much better because while one request is sleeping, it is able to start another one, and another one, and another one, and another one...
|
||||
|
||||
But, this is the point! Sanic is fast because it takes the available resources and squeezes performance out of them. It can handle many requests concurrently, which means more requests per second.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.get("/async")
|
||||
async def async_handler(request):
|
||||
await asyncio.sleep(0.1)
|
||||
return text("Done.")
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. warning:: A common mistake!
|
||||
|
||||
Don't do this! You need to ping a website. What do you use? `pip install your-fav-request-library` 🙈
|
||||
|
||||
Instead, try using a client that is `async/await` capable. Your server will thank you. Avoid using blocking tools, and favor those that play well in the asynchronous ecosystem. If you need recommendations, check out [Awesome Sanic](https://github.com/mekicha/awesome-sanic).
|
||||
|
||||
Sanic uses [httpx](https://www.python-httpx.org/) inside of its testing package (sanic-testing) :wink:.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## A fully annotated handler
|
||||
|
||||
For those that are using type annotations...
|
||||
|
||||
```python
|
||||
from sanic.response import HTTPResponse, text
|
||||
from sanic.request import Request
|
||||
|
||||
@app.get("/typed")
|
||||
async def typed_handler(request: Request) -> HTTPResponse:
|
||||
return text("Done.")
|
||||
```
|
229
guide/content/en/guide/basics/headers.md
Normal file
229
guide/content/en/guide/basics/headers.md
Normal file
@ -0,0 +1,229 @@
|
||||
# Headers
|
||||
|
||||
Request and response headers are available in the `Request` and `HTTPResponse` objects, respectively. They make use of the [`multidict` package](https://multidict.readthedocs.io/en/stable/multidict.html#cimultidict) that allows a single key to have multiple values.
|
||||
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
Header keys are converted to *lowercase* when parsed. Capitalization is not considered for headers.
|
||||
|
||||
|
||||
## Request
|
||||
|
||||
Sanic does attempt to do some normalization on request headers before presenting them to the developer, and also make some potentially meaningful extractions for common use cases.
|
||||
|
||||
.. column::
|
||||
|
||||
#### Tokens
|
||||
|
||||
Authorization tokens in the form `Token <token>` or `Bearer <token>` are extracted to the request object: `request.token`.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return text(request.token)
|
||||
```
|
||||
|
||||
```bash
|
||||
$ curl localhost:8000 \
|
||||
-H "Authorization: Token ABCDEF12345679"
|
||||
ABCDEF12345679
|
||||
```
|
||||
|
||||
```bash
|
||||
$ curl localhost:8000 \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|
||||
```
|
||||
|
||||
### Proxy headers
|
||||
|
||||
Sanic has special handling for proxy headers. See the [proxy headers](/guide/advanced/proxy-headers.md) section for more details.
|
||||
|
||||
### Host header and dynamic URL construction
|
||||
|
||||
.. column::
|
||||
|
||||
The *effective host* is available via `request.host`. This is not necessarily the same as the host header, as it prefers proxy-forwarded host and can be forced by the server name setting.
|
||||
|
||||
Webapps should generally use this accessor so that they can function the same no matter how they are deployed. The actual host header, if needed, can be found via `request.headers`
|
||||
|
||||
The effective host is also used in dynamic URL construction via `request.url_for`, which uses the request to determine the external address of a handler.
|
||||
|
||||
.. tip:: Be wary of malicious clients
|
||||
|
||||
These URLs can be manipulated by sending misleading host headers. `app.url_for` should be used instead if this is a concern.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app.config.SERVER_NAME = "https://example.com"
|
||||
|
||||
@app.route("/hosts", name="foo")
|
||||
async def handler(request):
|
||||
return json(
|
||||
{
|
||||
"effective host": request.host,
|
||||
"host header": request.headers.get("host"),
|
||||
"forwarded host": request.forwarded.get("host"),
|
||||
"you are here": request.url_for("foo"),
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
```bash
|
||||
$ curl localhost:8000/hosts
|
||||
{
|
||||
"effective host": "example.com",
|
||||
"host header": "localhost:8000",
|
||||
"forwarded host": null,
|
||||
"you are here": "https://example.com/hosts"
|
||||
}
|
||||
```
|
||||
|
||||
### Other headers
|
||||
|
||||
.. column::
|
||||
|
||||
|
||||
All request headers are available on `request.headers`, and can be accessed in dictionary form. Capitalization is not considered for headers, and can be accessed using either uppercase or lowercase keys.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return json(
|
||||
{
|
||||
"foo_weakref": request.headers["foo"],
|
||||
"foo_get": request.headers.get("Foo"),
|
||||
"foo_getone": request.headers.getone("FOO"),
|
||||
"foo_getall": request.headers.getall("fOo"),
|
||||
"all": list(request.headers.items()),
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
```bash
|
||||
$ curl localhost:9999/headers -H "Foo: one" -H "FOO: two"|jq
|
||||
{
|
||||
"foo_weakref": "one",
|
||||
"foo_get": "one",
|
||||
"foo_getone": "one",
|
||||
"foo_getall": [
|
||||
"one",
|
||||
"two"
|
||||
],
|
||||
"all": [
|
||||
[
|
||||
"host",
|
||||
"localhost:9999"
|
||||
],
|
||||
[
|
||||
"user-agent",
|
||||
"curl/7.76.1"
|
||||
],
|
||||
[
|
||||
"accept",
|
||||
"*/*"
|
||||
],
|
||||
[
|
||||
"foo",
|
||||
"one"
|
||||
],
|
||||
[
|
||||
"foo",
|
||||
"two"
|
||||
]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
💡 The request.headers object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.
|
||||
|
||||
Most of the time you will want to use the .get() or .getone() methods to access the first element and not a list. If you do want a list of all items, you can use .getall().
|
||||
|
||||
|
||||
### Request ID
|
||||
|
||||
.. column::
|
||||
|
||||
Often it is convenient or necessary to track a request by its `X-Request-ID` header. You can easily access that as: `request.id`.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return text(request.id)
|
||||
```
|
||||
|
||||
```bash
|
||||
$ curl localhost:8000 \
|
||||
-H "X-Request-ID: ABCDEF12345679"
|
||||
ABCDEF12345679
|
||||
```
|
||||
|
||||
## Response
|
||||
|
||||
Sanic will automatically set the following response headers (when appropriate) for you:
|
||||
|
||||
- `content-length`
|
||||
- `content-type`
|
||||
- `connection`
|
||||
- `transfer-encoding`
|
||||
|
||||
In most circumstances, you should never need to worry about setting these headers.
|
||||
|
||||
.. column::
|
||||
|
||||
Any other header that you would like to set can be done either in the route handler, or a response middleware.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return text("Done.", headers={"content-language": "en-US"})
|
||||
|
||||
@app.middleware("response")
|
||||
async def add_csp(request, response):
|
||||
response.headers["content-security-policy"] = "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';base-uri 'self';form-action 'self'"
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
A common [middleware](middleware.md) you might want is to add a `X-Request-ID` header to every response. As stated above: `request.id` will provide the ID from the incoming request. But, even if no ID was supplied in the request headers, one will be automatically supplied for you.
|
||||
|
||||
[See API docs for more details](https://sanic.readthedocs.io/en/latest/sanic/api_reference.html#sanic.request.Request.id)
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return text(str(request.id))
|
||||
|
||||
@app.on_response
|
||||
async def add_request_id_header(request, response):
|
||||
response.headers["X-Request-ID"] = request.id
|
||||
```
|
||||
|
||||
```bash
|
||||
$ curl localhost:8000 -i
|
||||
HTTP/1.1 200 OK
|
||||
X-Request-ID: 805a958e-9906-4e7a-8fe0-cbe83590431b
|
||||
content-length: 36
|
||||
connection: keep-alive
|
||||
content-type: text/plain; charset=utf-8
|
||||
|
||||
805a958e-9906-4e7a-8fe0-cbe83590431b
|
||||
```
|
||||
|
243
guide/content/en/guide/basics/listeners.md
Normal file
243
guide/content/en/guide/basics/listeners.md
Normal file
@ -0,0 +1,243 @@
|
||||
# Listeners
|
||||
|
||||
Sanic provides you with eight (8) opportunities to inject an operation into the life cycle of your application server. This does not include the [signals](../advanced/signals.md), which allow further injection customization.
|
||||
|
||||
There are two (2) that run **only** on your main Sanic process (ie, once per call to `sanic server.app`.)
|
||||
|
||||
- `main_process_start`
|
||||
- `main_process_stop`
|
||||
|
||||
There are also two (2) that run **only** in a reloader process if auto-reload has been turned on.
|
||||
|
||||
- `reload_process_start`
|
||||
- `reload_process_stop`
|
||||
|
||||
*Added `reload_process_start` and `reload_process_stop` in v22.3*
|
||||
|
||||
There are four (4) that enable you to execute startup/teardown code as your server starts or closes.
|
||||
|
||||
- `before_server_start`
|
||||
- `after_server_start`
|
||||
- `before_server_stop`
|
||||
- `after_server_stop`
|
||||
|
||||
The life cycle of a worker process looks like this:
|
||||
|
||||
.. mermaid::
|
||||
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant Process
|
||||
participant Worker
|
||||
participant Listener
|
||||
participant Handler
|
||||
Note over Process: sanic server.app
|
||||
loop
|
||||
Process->>Listener: @app.main_process_start
|
||||
Listener->>Handler: Invoke event handler
|
||||
end
|
||||
Process->>Worker: Run workers
|
||||
loop Start each worker
|
||||
loop
|
||||
Worker->>Listener: @app.before_server_start
|
||||
Listener->>Handler: Invoke event handler
|
||||
end
|
||||
Note over Worker: Server status: started
|
||||
loop
|
||||
Worker->>Listener: @app.after_server_start
|
||||
Listener->>Handler: Invoke event handler
|
||||
end
|
||||
Note over Worker: Server status: ready
|
||||
end
|
||||
Process->>Worker: Graceful shutdown
|
||||
loop Stop each worker
|
||||
loop
|
||||
Worker->>Listener: @app.before_server_stop
|
||||
Listener->>Handler: Invoke event handler
|
||||
end
|
||||
Note over Worker: Server status: stopped
|
||||
loop
|
||||
Worker->>Listener: @app.after_server_stop
|
||||
Listener->>Handler: Invoke event handler
|
||||
end
|
||||
Note over Worker: Server status: closed
|
||||
end
|
||||
loop
|
||||
Process->>Listener: @app.main_process_stop
|
||||
Listener->>Handler: Invoke event handler
|
||||
end
|
||||
Note over Process: exit
|
||||
|
||||
|
||||
The reloader process live outside of this worker process inside of a process that is responsible for starting and stopping the Sanic processes. Consider the following example:
|
||||
|
||||
```python
|
||||
@app.reload_process_start
|
||||
async def reload_start(*_):
|
||||
print(">>>>>> reload_start <<<<<<")
|
||||
|
||||
@app.main_process_start
|
||||
async def main_start(*_):
|
||||
print(">>>>>> main_start <<<<<<")
|
||||
```
|
||||
|
||||
If this application were run with auto-reload turned on, the `reload_start` function would be called once. This is contrasted with `main_start`, which would be run every time a file is save and the reloader restarts the applicaition process.
|
||||
|
||||
## Attaching a listener
|
||||
|
||||
.. column::
|
||||
|
||||
The process to setup a function as a listener is similar to declaring a route.
|
||||
|
||||
The currently running `Sanic()` instance is injected into the listener.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
async def setup_db(app):
|
||||
app.ctx.db = await db_setup()
|
||||
|
||||
app.register_listener(setup_db, "before_server_start")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
The `Sanic` app instance also has a convenience decorator.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.listener("before_server_start")
|
||||
async def setup_db(app):
|
||||
app.ctx.db = await db_setup()
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Prior to v22.3, both the application instance and the current event loop were injected into the function. However, only the application instance is injected by default. If your function signature will accept both, then both the application and the loop will be injected as shown here.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.listener("before_server_start")
|
||||
async def setup_db(app, loop):
|
||||
app.ctx.db = await db_setup()
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
You can shorten the decorator even further. This is helpful if you have an IDE with autocomplete.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.before_server_start
|
||||
async def setup_db(app):
|
||||
app.ctx.db = await db_setup()
|
||||
```
|
||||
|
||||
## Order of execution
|
||||
|
||||
Listeners are executed in the order they are declared during startup, and reverse order of declaration during teardown
|
||||
|
||||
| | Phase | Order |
|
||||
|-----------------------|-----------------|---------|
|
||||
| `main_process_start` | main startup | regular 🙂 ⬇️ |
|
||||
| `before_server_start` | worker startup | regular 🙂 ⬇️ |
|
||||
| `after_server_start` | worker startup | regular 🙂 ⬇️ |
|
||||
| `before_server_stop` | worker shutdown | 🙃 ⬆️ reverse |
|
||||
| `after_server_stop` | worker shutdown | 🙃 ⬆️ reverse |
|
||||
| `main_process_stop` | main shutdown | 🙃 ⬆️ reverse |
|
||||
|
||||
Given the following setup, we should expect to see this in the console if we run two workers.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.listener("before_server_start")
|
||||
async def listener_1(app, loop):
|
||||
print("listener_1")
|
||||
|
||||
@app.before_server_start
|
||||
async def listener_2(app, loop):
|
||||
print("listener_2")
|
||||
|
||||
@app.listener("after_server_start")
|
||||
async def listener_3(app, loop):
|
||||
print("listener_3")
|
||||
|
||||
@app.after_server_start
|
||||
async def listener_4(app, loop):
|
||||
print("listener_4")
|
||||
|
||||
@app.listener("before_server_stop")
|
||||
async def listener_5(app, loop):
|
||||
print("listener_5")
|
||||
|
||||
@app.before_server_stop
|
||||
async def listener_6(app, loop):
|
||||
print("listener_6")
|
||||
|
||||
@app.listener("after_server_stop")
|
||||
async def listener_7(app, loop):
|
||||
print("listener_7")
|
||||
|
||||
@app.after_server_stop
|
||||
async def listener_8(app, loop):
|
||||
print("listener_8")
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
[pid: 1000000] [INFO] Goin' Fast @ http://127.0.0.1:9999
|
||||
[pid: 1000000] [INFO] listener_0
|
||||
[pid: 1111111] [INFO] listener_1
|
||||
[pid: 1111111] [INFO] listener_2
|
||||
[pid: 1111111] [INFO] listener_3
|
||||
[pid: 1111111] [INFO] listener_4
|
||||
[pid: 1111111] [INFO] Starting worker [1111111]
|
||||
[pid: 1222222] [INFO] listener_1
|
||||
[pid: 1222222] [INFO] listener_2
|
||||
[pid: 1222222] [INFO] listener_3
|
||||
[pid: 1222222] [INFO] listener_4
|
||||
[pid: 1222222] [INFO] Starting worker [1222222]
|
||||
[pid: 1111111] [INFO] Stopping worker [1111111]
|
||||
[pid: 1222222] [INFO] Stopping worker [1222222]
|
||||
[pid: 1222222] [INFO] listener_6
|
||||
[pid: 1222222] [INFO] listener_5
|
||||
[pid: 1222222] [INFO] listener_8
|
||||
[pid: 1222222] [INFO] listener_7
|
||||
[pid: 1111111] [INFO] listener_6
|
||||
[pid: 1111111] [INFO] listener_5
|
||||
[pid: 1111111] [INFO] listener_8
|
||||
[pid: 1111111] [INFO] listener_7
|
||||
[pid: 1000000] [INFO] listener_9
|
||||
[pid: 1000000] [INFO] Server Stopped
|
||||
```
|
||||
In the above example, notice how there are three processes running:
|
||||
|
||||
- `pid: 1000000` - The *main* process
|
||||
- `pid: 1111111` - Worker 1
|
||||
- `pid: 1222222` - Worker 2
|
||||
|
||||
*Just because our example groups all of one worker and then all of another, in reality since these are running on separate processes, the ordering between processes is not guaranteed. But, you can be sure that a single worker will **always** maintain its order.*
|
||||
|
||||
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
The practical result of this is that if the first listener in `before_server_start` handler setups a database connection, listeners that are registered after it can rely upon that connection being alive both when they are started and stopped.
|
||||
|
||||
|
||||
## ASGI Mode
|
||||
|
||||
If you are running your application with an ASGI server, then make note of the following changes:
|
||||
|
||||
- `reload_process_start` and `reload_process_stop` will be **ignored**
|
||||
- `main_process_start` and `main_process_stop` will be **ignored**
|
||||
- `before_server_start` will run as early as it can, and will be before `after_server_start`, but technically, the server is already running at that point
|
||||
- `after_server_stop` will run as late as it can, and will be after `before_server_stop`, but technically, the server is still running at that point
|
229
guide/content/en/guide/basics/middleware.md
Normal file
229
guide/content/en/guide/basics/middleware.md
Normal file
@ -0,0 +1,229 @@
|
||||
# Middleware
|
||||
|
||||
Whereas listeners allow you to attach functionality to the lifecycle of a worker process, middleware allows you to attach functionality to the lifecycle of an HTTP stream.
|
||||
|
||||
You can execute middleware either _before_ the handler is executed, or _after_.
|
||||
|
||||
.. mermaid::
|
||||
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant Worker
|
||||
participant Middleware
|
||||
participant MiddlewareHandler
|
||||
participant RouteHandler
|
||||
Note over Worker: Incoming HTTP request
|
||||
loop
|
||||
Worker->>Middleware: @app.on_request
|
||||
Middleware->>MiddlewareHandler: Invoke middleware handler
|
||||
MiddlewareHandler-->>Worker: Return response (optional)
|
||||
end
|
||||
rect rgba(255, 13, 104, .1)
|
||||
Worker->>RouteHandler: Invoke route handler
|
||||
RouteHandler->>Worker: Return response
|
||||
end
|
||||
loop
|
||||
Worker->>Middleware: @app.on_response
|
||||
Middleware->>MiddlewareHandler: Invoke middleware handler
|
||||
MiddlewareHandler-->>Worker: Return response (optional)
|
||||
end
|
||||
Note over Worker: Deliver response
|
||||
|
||||
## Attaching middleware
|
||||
|
||||
.. column::
|
||||
|
||||
This should probably look familiar by now. All you need to do is declare when you would like the middleware to execute: on the `request` or on the `response`.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
async def extract_user(request):
|
||||
request.ctx.user = await extract_user_from_request(request)
|
||||
|
||||
app.register_middleware(extract_user, "request")
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
Again, the `Sanic` app instance also has a convenience decorator.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.middleware("request")
|
||||
async def extract_user(request):
|
||||
request.ctx.user = await extract_user_from_request(request)
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
Response middleware receives both the `request` and `response` arguments.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.middleware('response')
|
||||
async def prevent_xss(request, response):
|
||||
response.headers["x-xss-protection"] = "1; mode=block"
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
You can shorten the decorator even further. This is helpful if you have an IDE with autocomplete.
|
||||
|
||||
This is the preferred usage, and is what we will use going forward.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.on_request
|
||||
async def extract_user(request):
|
||||
...
|
||||
|
||||
@app.on_response
|
||||
async def prevent_xss(request, response):
|
||||
...
|
||||
```
|
||||
|
||||
## Modification
|
||||
|
||||
Middleware can modify the request or response parameter it is given, _as long as it does not return it_.
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
#### Order of execution
|
||||
|
||||
1. Request middleware: `add_key`
|
||||
2. Route handler: `index`
|
||||
3. Response middleware: `prevent_xss`
|
||||
4. Response middleware: `custom_banner`
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.on_request
|
||||
async def add_key(request):
|
||||
# Arbitrary data may be stored in request context:
|
||||
request.ctx.foo = "bar"
|
||||
|
||||
@app.on_response
|
||||
async def custom_banner(request, response):
|
||||
response.headers["Server"] = "Fake-Server"
|
||||
|
||||
@app.on_response
|
||||
async def prevent_xss(request, response):
|
||||
response.headers["x-xss-protection"] = "1; mode=block"
|
||||
|
||||
@app.get("/")
|
||||
async def index(request):
|
||||
return text(request.ctx.foo)
|
||||
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
You can modify the `request.match_info`. A useful feature that could be used, for example, in middleware to convert `a-slug` to `a_slug`.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.on_request
|
||||
def convert_slug_to_underscore(request: Request):
|
||||
request.match_info["slug"] = request.match_info["slug"].replace("-", "_")
|
||||
|
||||
@app.get("/<slug:slug>")
|
||||
async def handler(request, slug):
|
||||
return text(slug)
|
||||
```
|
||||
```
|
||||
$ curl localhost:9999/foo-bar-baz
|
||||
foo_bar_baz
|
||||
```
|
||||
|
||||
## Responding early
|
||||
|
||||
.. column::
|
||||
|
||||
If middleware returns a `HTTPResponse` object, the request will stop processing and the response will be returned. If this occurs to a request before the route handler is reached, the handler will **not** be called. Returning a response will also prevent any further middleware from running.
|
||||
|
||||
|
||||
|
||||
.. tip::
|
||||
|
||||
You can return a `None` value to stop the execution of the middleware handler to allow the request to process as normal. This can be useful when using early return to avoid processing requests inside of that middleware handler.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.on_request
|
||||
async def halt_request(request):
|
||||
return text("I halted the request")
|
||||
|
||||
@app.on_response
|
||||
async def halt_response(request, response):
|
||||
return text("I halted the response")
|
||||
```
|
||||
|
||||
## Order of execution
|
||||
|
||||
Request middleware is executed in the order declared. Response middleware is executed in **reverse order**.
|
||||
|
||||
Given the following setup, we should expect to see this in the console.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.on_request
|
||||
async def middleware_1(request):
|
||||
print("middleware_1")
|
||||
|
||||
@app.on_request
|
||||
async def middleware_2(request):
|
||||
print("middleware_2")
|
||||
|
||||
@app.on_response
|
||||
async def middleware_3(request, response):
|
||||
print("middleware_3")
|
||||
|
||||
@app.on_response
|
||||
async def middleware_4(request, response):
|
||||
print("middleware_4")
|
||||
|
||||
@app.get("/handler")
|
||||
async def handler(request):
|
||||
print("~ handler ~")
|
||||
return text("Done.")
|
||||
```
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
middleware_1
|
||||
middleware_2
|
||||
~ handler ~
|
||||
middleware_4
|
||||
middleware_3
|
||||
[INFO][127.0.0.1:44788]: GET http://localhost:8000/handler 200 5
|
||||
```
|
||||
|
||||
### Middleware priority
|
||||
|
||||
.. column::
|
||||
|
||||
You can modify the order of execution of middleware by assigning it a higher priority. This happens inside of the middleware definition. The higher the value, the earlier it will execute relative to other middleware. The default priority for middleware is `0`.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.on_request
|
||||
async def low_priority(request):
|
||||
...
|
||||
|
||||
@app.on_request(priority=99)
|
||||
async def high_priority(request):
|
||||
...
|
||||
```
|
||||
|
||||
*Added in v22.9*
|
327
guide/content/en/guide/basics/request.md
Normal file
327
guide/content/en/guide/basics/request.md
Normal file
@ -0,0 +1,327 @@
|
||||
# Request
|
||||
|
||||
The `Request` instance contains **a lot** of helpful information available on its parameters. Refer to the [API documentation](https://sanic.readthedocs.io/) for full details.
|
||||
|
||||
## Body
|
||||
|
||||
The `Request` object allows you to access the content of the request body in a few different ways.
|
||||
|
||||
### JSON
|
||||
|
||||
.. column::
|
||||
|
||||
**Parameter**: `request.json`
|
||||
**Description**: The parsed JSON object
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
$ curl localhost:8000 -d '{"foo": "bar"}'
|
||||
```
|
||||
|
||||
```python
|
||||
>>> print(request.json)
|
||||
{'foo': 'bar'}
|
||||
```
|
||||
|
||||
### Raw
|
||||
|
||||
.. column::
|
||||
|
||||
**Parameter**: `request.body`
|
||||
**Description**: The raw bytes from the request body
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
$ curl localhost:8000 -d '{"foo": "bar"}'
|
||||
```
|
||||
|
||||
```python
|
||||
>>> print(request.body)
|
||||
b'{"foo": "bar"}'
|
||||
```
|
||||
|
||||
### Form
|
||||
|
||||
.. column::
|
||||
|
||||
**Parameter**: `request.form`
|
||||
**Description**: The form data
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
The `request.form` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.
|
||||
|
||||
Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`.
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
$ curl localhost:8000 -d 'foo=bar'
|
||||
```
|
||||
|
||||
```python
|
||||
>>> print(request.body)
|
||||
b'foo=bar'
|
||||
|
||||
>>> print(request.form)
|
||||
{'foo': ['bar']}
|
||||
|
||||
>>> print(request.form.get("foo"))
|
||||
bar
|
||||
|
||||
>>> print(request.form.getlist("foo"))
|
||||
['bar']
|
||||
```
|
||||
|
||||
### Uploaded
|
||||
|
||||
.. column::
|
||||
|
||||
**Parameter**: `request.files`
|
||||
**Description**: The files uploaded to the server
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
The `request.files` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.
|
||||
|
||||
Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`.
|
||||
|
||||
.. column::
|
||||
|
||||
```bash
|
||||
$ curl -F 'my_file=@/path/to/TEST' http://localhost:8000
|
||||
```
|
||||
|
||||
```python
|
||||
>>> print(request.body)
|
||||
b'--------------------------cb566ad845ad02d3\r\nContent-Disposition: form-data; name="my_file"; filename="TEST"\r\nContent-Type: application/octet-stream\r\n\r\nhello\n\r\n--------------------------cb566ad845ad02d3--\r\n'
|
||||
|
||||
>>> print(request.files)
|
||||
{'my_file': [File(type='application/octet-stream', body=b'hello\n', name='TEST')]}
|
||||
|
||||
>>> print(request.files.get("my_file"))
|
||||
File(type='application/octet-stream', body=b'hello\n', name='TEST')
|
||||
|
||||
>>> print(request.files.getlist("my_file"))
|
||||
[File(type='application/octet-stream', body=b'hello\n', name='TEST')]
|
||||
```
|
||||
|
||||
## Context
|
||||
|
||||
### Request context
|
||||
|
||||
The `request.ctx` object is your playground to store whatever information you need to about the request.
|
||||
|
||||
This is often used to store items like authenticated user details. We will get more into [middleware](./middleware.md) later, but here is a simple example.
|
||||
|
||||
```python
|
||||
@app.on_request
|
||||
async def run_before_handler(request):
|
||||
request.ctx.user = await fetch_user_by_token(request.token)
|
||||
|
||||
@app.route('/hi')
|
||||
async def hi_my_name_is(request):
|
||||
return text("Hi, my name is {}".format(request.ctx.user.name))
|
||||
```
|
||||
|
||||
A typical use case would be to store the user object acquired from database in an authentication middleware. Keys added are accessible to all later middleware as well as the handler over the duration of the request.
|
||||
|
||||
Custom context is reserved for applications and extensions. Sanic itself makes no use of it.
|
||||
|
||||
### Connection context
|
||||
|
||||
.. column::
|
||||
|
||||
Often times your API will need to serve multiple concurrent (or consecutive) requests to the same client. This happens, for example, very often with progressive web apps that need to query multiple endpoints to get data.
|
||||
|
||||
The HTTP protocol calls for an easing of overhead time caused by the connection with the use of [keep alive headers](../deployment/configuration.md#keep-alive-timeout).
|
||||
|
||||
When multiple requests share a single connection, Sanic provides a context object to allow those requests to share state.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.on_request
|
||||
async def increment_foo(request):
|
||||
if not hasattr(request.conn_info.ctx, "foo"):
|
||||
request.conn_info.ctx.foo = 0
|
||||
request.conn_info.ctx.foo += 1
|
||||
|
||||
@app.get("/")
|
||||
async def count_foo(request):
|
||||
return text(f"request.conn_info.ctx.foo={request.conn_info.ctx.foo}")
|
||||
```
|
||||
|
||||
```bash
|
||||
$ curl localhost:8000 localhost:8000 localhost:8000
|
||||
request.conn_info.ctx.foo=1
|
||||
request.conn_info.ctx.foo=2
|
||||
request.conn_info.ctx.foo=3
|
||||
```
|
||||
|
||||
### Custom Request Objects
|
||||
|
||||
As dicussed in [application customization](./app.md#custom-requests), you can create a subclass of `sanic.Request` to add additional functionality to the request object. This is useful for adding additional attributes or methods that are specific to your application.
|
||||
|
||||
.. column::
|
||||
|
||||
For example, imagine your application sends a custom header that contains a user ID. You can create a custom request object that will parse that header and store the user ID for you.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Sanic, Request
|
||||
|
||||
class CustomRequest(Request):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.user_id = self.headers.get("X-User-ID")
|
||||
|
||||
app = Sanic("Example", request_class=CustomRequest)
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Now, in your handlers, you can access the `user_id` attribute.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/")
|
||||
async def handler(request: CustomRequest):
|
||||
return text(f"User ID: {request.user_id}")
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. new:: NEW in v23.6
|
||||
|
||||
### Custom Request Context
|
||||
|
||||
By default, the request context (`request.ctx`) is a `SimpleNamespace` object allowing you to set arbitrary attributes on it. While this is super helpful to reuse logic across your application, it can be difficult in the development experience since the IDE will not know what attributes are available.
|
||||
|
||||
To help with this, you can create a custom request context object that will be used instead of the default `SimpleNamespace`. This allows you to add type hints to the context object and have them be available in your IDE.
|
||||
|
||||
.. column::
|
||||
|
||||
Start by subclassing the `sanic.Request` class to create a custom request type. Then, you will need to add a `make_context()` method that returns an instance of your custom context object. *NOTE: the `make_context` method should be a static method.*
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import Sanic, Request
|
||||
from types import SimpleNamespace
|
||||
|
||||
class CustomRequest(Request):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.ctx.user_id = self.headers.get("X-User-ID")
|
||||
|
||||
@staticmethod
|
||||
def make_context() -> CustomContext:
|
||||
return CustomContext()
|
||||
|
||||
@dataclass
|
||||
class CustomContext:
|
||||
user_id: str = None
|
||||
```
|
||||
|
||||
*Added in v23.6*
|
||||
|
||||
|
||||
## Parameters
|
||||
|
||||
.. column::
|
||||
|
||||
Values that are extracted from the path are injected into the handler as parameters, or more specifically as keyword arguments. There is much more detail about this in the [Routing section](./routing.md).
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route('/tag/<tag>')
|
||||
async def tag_handler(request, tag):
|
||||
return text("Tag - {}".format(tag))
|
||||
```
|
||||
|
||||
|
||||
## Arguments
|
||||
|
||||
There are two attributes on the `request` instance to get query parameters:
|
||||
|
||||
- `request.args`
|
||||
- `request.query_args`
|
||||
|
||||
```bash
|
||||
$ curl http://localhost:8000\?key1\=val1\&key2\=val2\&key1\=val3
|
||||
```
|
||||
|
||||
```python
|
||||
>>> print(request.args)
|
||||
{'key1': ['val1', 'val3'], 'key2': ['val2']}
|
||||
|
||||
>>> print(request.args.get("key1"))
|
||||
val1
|
||||
|
||||
>>> print(request.args.getlist("key1"))
|
||||
['val1', 'val3']
|
||||
|
||||
>>> print(request.query_args)
|
||||
[('key1', 'val1'), ('key2', 'val2'), ('key1', 'val3')]
|
||||
|
||||
>>> print(request.query_string)
|
||||
key1=val1&key2=val2&key1=val3
|
||||
|
||||
```
|
||||
|
||||
|
||||
.. tip:: FYI
|
||||
|
||||
The `request.args` object is one of a few types that is a dictionary with each value being a list. This is because HTTP allows a single key to be reused to send multiple values.
|
||||
|
||||
Most of the time you will want to use the `.get()` method to access the first element and not a list. If you do want a list of all items, you can use `.getlist()`.
|
||||
|
||||
|
||||
## Current request getter
|
||||
|
||||
Sometimes you may find that you need access to the current request in your application in a location where it is not accessible. A typical example might be in a `logging` format. You can use `Request.get_current()` to fetch the current request (if any).
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
from sanic import Request, Sanic, json
|
||||
from sanic.exceptions import SanicException
|
||||
from sanic.log import LOGGING_CONFIG_DEFAULTS
|
||||
|
||||
LOGGING_FORMAT = (
|
||||
"%(asctime)s - (%(name)s)[%(levelname)s][%(host)s]: "
|
||||
"%(request_id)s %(request)s %(message)s %(status)d %(byte)d"
|
||||
)
|
||||
|
||||
old_factory = logging.getLogRecordFactory()
|
||||
|
||||
def record_factory(*args, **kwargs):
|
||||
record = old_factory(*args, **kwargs)
|
||||
record.request_id = ""
|
||||
|
||||
try:
|
||||
request = Request.get_current()
|
||||
except SanicException:
|
||||
...
|
||||
else:
|
||||
record.request_id = str(request.id)
|
||||
|
||||
return record
|
||||
|
||||
logging.setLogRecordFactory(record_factory)
|
||||
|
||||
LOGGING_CONFIG_DEFAULTS["formatters"]["access"]["format"] = LOGGING_FORMAT
|
||||
|
||||
app = Sanic("Example", log_config=LOGGING_CONFIG_DEFAULTS)
|
||||
```
|
||||
|
||||
In this example, we are adding the `request.id` to every access log message.
|
||||
|
||||
*Added in v22.6*
|
265
guide/content/en/guide/basics/response.md
Normal file
265
guide/content/en/guide/basics/response.md
Normal file
@ -0,0 +1,265 @@
|
||||
# Response
|
||||
|
||||
All [handlers](./handlers.md) *usually* return a response object, and [middleware](./middleware.md) may optionally return a response object.
|
||||
|
||||
To clarify that statement:
|
||||
- unless the handler is a streaming endpoint handling its own pattern for sending bytes to the client, the return value must be an instance of `sanic.HTTPResponse` (to learn more about this exception see [streaming responses](../advanced/streaming.md#response-streaming))
|
||||
- if a middleware returns a response object, that will be used instead of whatever the handler would do (see [middleware](./middleware.md) to learn more)
|
||||
|
||||
A most basic handler would look like the following. The `HTTPResponse` object will allow you to set the status, body, and headers to be returned to the client.
|
||||
|
||||
```python
|
||||
from sanic import HTTPResponse, Sanic
|
||||
|
||||
app = Sanic("TestApp")
|
||||
|
||||
@app.route("")
|
||||
def handler(_):
|
||||
return HTTPResponse()
|
||||
```
|
||||
|
||||
However, usually it is easier to use one of the convenience methods discussed below.
|
||||
|
||||
## Methods
|
||||
|
||||
The easiest way to generate a response object is to use one of the nine (9) convenience methods.
|
||||
|
||||
### Text
|
||||
|
||||
.. column::
|
||||
|
||||
**Default Content-Type**: `text/plain; charset=utf-8`
|
||||
**Description**: Returns plain text
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import text
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return text("Hi 😎")
|
||||
```
|
||||
|
||||
### HTML
|
||||
|
||||
.. column::
|
||||
|
||||
**Default Content-Type**: `text/html; charset=utf-8`
|
||||
**Description**: Returns an HTML document
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import html
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return html('<!DOCTYPE html><html lang="en"><meta charset="UTF-8"><div>Hi 😎</div>')
|
||||
```
|
||||
|
||||
### JSON
|
||||
|
||||
.. column::
|
||||
|
||||
**Default Content-Type**: `application/json`
|
||||
**Description**: Returns a JSON document
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import json
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return json({"foo": "bar"})
|
||||
```
|
||||
|
||||
By default, Sanic ships with [`ujson`](https://github.com/ultrajson/ultrajson) as its JSON encoder of choice. It is super simple to change this if you want.
|
||||
|
||||
```python
|
||||
from orjson import dumps
|
||||
|
||||
json({"foo": "bar"}, dumps=dumps)
|
||||
```
|
||||
|
||||
If `ujson` is not installed, it will fall back to the standard library `json` module.
|
||||
|
||||
You may additionally declare which implementation to use globally across your application at initialization:
|
||||
|
||||
```python
|
||||
from orjson import dumps
|
||||
|
||||
app = Sanic(..., dumps=dumps)
|
||||
```
|
||||
|
||||
### File
|
||||
|
||||
.. column::
|
||||
|
||||
**Default Content-Type**: N/A
|
||||
**Description**: Returns a file
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import file
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return await file("/path/to/whatever.png")
|
||||
```
|
||||
|
||||
Sanic will examine the file, and try and guess its mime type and use an appropriate value for the content type. You could be explicit, if you would like:
|
||||
|
||||
```python
|
||||
file("/path/to/whatever.png", mime_type="image/png")
|
||||
```
|
||||
|
||||
You can also choose to override the file name:
|
||||
|
||||
```python
|
||||
file("/path/to/whatever.png", filename="super-awesome-incredible.png")
|
||||
```
|
||||
|
||||
### File Streaming
|
||||
|
||||
.. column::
|
||||
|
||||
**Default Content-Type**: N/A
|
||||
**Description**: Streams a file to a client, useful when streaming large files, like a video
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic.response import file_stream
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return await file_stream("/path/to/whatever.mp4")
|
||||
```
|
||||
|
||||
Like the `file()` method, `file_stream()` will attempt to determine the mime type of the file.
|
||||
|
||||
|
||||
|
||||
### Raw
|
||||
|
||||
.. column::
|
||||
|
||||
**Default Content-Type**: `application/octet-stream`
|
||||
**Description**: Send raw bytes without encoding the body
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import raw
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return raw(b"raw bytes")
|
||||
```
|
||||
|
||||
### Redirect
|
||||
|
||||
.. column::
|
||||
|
||||
**Default Content-Type**: `text/html; charset=utf-8`
|
||||
**Description**: Send a `302` response to redirect the client to a different path
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import redirect
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return redirect("/login")
|
||||
```
|
||||
|
||||
### Empty
|
||||
|
||||
.. column::
|
||||
|
||||
**Default Content-Type**: N/A
|
||||
**Description**: For responding with an empty message as defined by [RFC 2616](https://tools.ietf.org/search/rfc2616#section-7.2.1)
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
from sanic import empty
|
||||
|
||||
@app.route("/")
|
||||
async def handler(request):
|
||||
return empty()
|
||||
```
|
||||
|
||||
Defaults to a `204` status.
|
||||
|
||||
## Default status
|
||||
|
||||
The default HTTP status code for the response is `200`. If you need to change it, it can be done by the response method.
|
||||
|
||||
```python
|
||||
@app.post("/")
|
||||
async def create_new(request):
|
||||
new_thing = await do_create(request)
|
||||
return json({"created": True, "id": new_thing.thing_id}, status=201)
|
||||
```
|
||||
|
||||
## Returning JSON data
|
||||
|
||||
Starting in v22.12, When you use the `sanic.json` convenience method, it will return a subclass of `HTTPResponse` called `JSONResponse`. This object will
|
||||
have several convenient methods available to modify common JSON body.
|
||||
|
||||
```python
|
||||
from sanic import json
|
||||
|
||||
resp = json(...)
|
||||
```
|
||||
|
||||
- `resp.set_body(<raw_body>)` - Set the body of the JSON object to the value passed
|
||||
- `resp.append(<value>)` - Append a value to the body like `list.append` (only works if the root JSON is an array)
|
||||
- `resp.extend(<value>)` - Extend a value to the body like `list.extend` (only works if the root JSON is an array)
|
||||
- `resp.update(<value>)` - Update the body with a value like `dict.update` (only works if the root JSON is an object)
|
||||
- `resp.pop()` - Pop a value like `list.pop` or `dict.pop` (only works if the root JSON is an array or an object)
|
||||
|
||||
.. warning::
|
||||
|
||||
The raw Python object is stored on the `JSONResponse` object as `raw_body`. While it is safe to overwrite this value with a new one, you should **not** attempt to mutate it. You should instead use the methods listed above.
|
||||
|
||||
```python
|
||||
resp = json({"foo": "bar"})
|
||||
|
||||
# This is OKAY
|
||||
resp.raw_body = {"foo": "bar", "something": "else"}
|
||||
|
||||
# This is better
|
||||
resp.set_body({"foo": "bar", "something": "else"})
|
||||
|
||||
# This is also works well
|
||||
resp.update({"something": "else"})
|
||||
|
||||
# This is NOT OKAY
|
||||
resp.raw_body.update({"something": "else"})
|
||||
```
|
||||
|
||||
```python
|
||||
# Or, even treat it like a list
|
||||
resp = json(["foo", "bar"])
|
||||
|
||||
# This is OKAY
|
||||
resp.raw_body = ["foo", "bar", "something", "else"]
|
||||
|
||||
# This is better
|
||||
resp.extend(["something", "else"])
|
||||
|
||||
# This is also works well
|
||||
resp.append("something")
|
||||
resp.append("else")
|
||||
|
||||
# This is NOT OKAY
|
||||
resp.raw_body.append("something")
|
||||
```
|
||||
|
||||
*Added in v22.9*
|
798
guide/content/en/guide/basics/routing.md
Normal file
798
guide/content/en/guide/basics/routing.md
Normal file
@ -0,0 +1,798 @@
|
||||
# Routing
|
||||
|
||||
.. column::
|
||||
|
||||
So far we have seen a lot of this decorator in different forms.
|
||||
|
||||
But what is it? And how do we use it?
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/stairway")
|
||||
...
|
||||
|
||||
@app.get("/to")
|
||||
...
|
||||
|
||||
@app.post("/heaven")
|
||||
...
|
||||
```
|
||||
|
||||
## Adding a route
|
||||
|
||||
.. column::
|
||||
|
||||
The most basic way to wire up a handler to an endpoint is with `app.add_route()`.
|
||||
|
||||
See [API docs](https://sanic.readthedocs.io/en/stable/sanic/api_reference.html#sanic.app.Sanic.url_for) for more details.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
async def handler(request):
|
||||
return text("OK")
|
||||
|
||||
app.add_route(handler, "/test")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
By default, routes are available as an HTTP `GET` call. You can change a handler to respond to one or more HTTP methods.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app.add_route(
|
||||
handler,
|
||||
'/test',
|
||||
methods=["POST", "PUT"],
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Using the decorator syntax, the previous example is identical to this.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route('/test', methods=["POST", "PUT"])
|
||||
async def handler(request):
|
||||
return text('OK')
|
||||
```
|
||||
|
||||
## HTTP methods
|
||||
|
||||
Each of the standard HTTP methods has a convenience decorator.
|
||||
|
||||
### GET
|
||||
|
||||
```python
|
||||
@app.get('/test')
|
||||
async def handler(request):
|
||||
return text('OK')
|
||||
```
|
||||
|
||||
[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET)
|
||||
|
||||
### POST
|
||||
|
||||
```python
|
||||
@app.post('/test')
|
||||
async def handler(request):
|
||||
return text('OK')
|
||||
```
|
||||
|
||||
[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST)
|
||||
|
||||
### PUT
|
||||
|
||||
```python
|
||||
@app.put('/test')
|
||||
async def handler(request):
|
||||
return text('OK')
|
||||
```
|
||||
|
||||
[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT)
|
||||
|
||||
### PATCH
|
||||
|
||||
```python
|
||||
@app.patch('/test')
|
||||
async def handler(request):
|
||||
return text('OK')
|
||||
```
|
||||
|
||||
[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH)
|
||||
|
||||
### DELETE
|
||||
|
||||
```python
|
||||
@app.delete('/test')
|
||||
async def handler(request):
|
||||
return text('OK')
|
||||
```
|
||||
|
||||
[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE)
|
||||
|
||||
### HEAD
|
||||
|
||||
```python
|
||||
@app.head('/test')
|
||||
async def handler(request):
|
||||
return empty()
|
||||
```
|
||||
|
||||
[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD)
|
||||
|
||||
### OPTIONS
|
||||
|
||||
```python
|
||||
@app.options('/test')
|
||||
async def handler(request):
|
||||
return empty()
|
||||
```
|
||||
|
||||
[MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS)
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
By default, Sanic will **only** consume the incoming request body on non-safe HTTP methods (`POST`, `PUT`, `PATCH`, `DELETE`). If you want to receive data in the HTTP request on any other method, you will need to do one of the following two options:
|
||||
|
||||
**Option #1 - Tell Sanic to consume the body using `ignore_body`**
|
||||
```python
|
||||
@app.request("/path", ignore_body=False)
|
||||
async def handler(_):
|
||||
...
|
||||
```
|
||||
|
||||
**Option #2 - Manually consume the body in the handler using `receive_body`**
|
||||
```python
|
||||
@app.get("/path")
|
||||
async def handler(request: Request):
|
||||
await request.receive_body()
|
||||
```
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
.. column::
|
||||
|
||||
Sanic allows for pattern matching, and for extracting values from URL paths. These parameters are then injected as keyword arguments in the route handler.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.get("/tag/<tag>")
|
||||
async def tag_handler(request, tag):
|
||||
return text("Tag - {}".format(tag))
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
You can declare a type for the parameter. This will be enforced when matching, and also will type cast the variable.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.get("/foo/<foo_id:uuid>")
|
||||
async def uuid_handler(request, foo_id: UUID):
|
||||
return text("UUID - {}".format(foo_id))
|
||||
```
|
||||
|
||||
### Supported types
|
||||
|
||||
### `str`
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: `r"[^/]+"`
|
||||
**Cast type**: `str`
|
||||
**Example matches**:
|
||||
|
||||
- `/path/to/Bob`
|
||||
- `/path/to/Python%203`
|
||||
|
||||
Beginning in v22.3 `str` will *not* match on empty strings. See `strorempty` for this behavior.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<foo:str>")
|
||||
async def handler(request, foo: str):
|
||||
...
|
||||
```
|
||||
|
||||
### `strorempty`
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: `r"[^/]*"`
|
||||
**Cast type**: `str`
|
||||
**Example matches**:
|
||||
|
||||
- `/path/to/Bob`
|
||||
- `/path/to/Python%203`
|
||||
- `/path/to/`
|
||||
|
||||
Unlike the `str` path parameter type, `strorempty` can also match on an empty string path segment.
|
||||
|
||||
*Added in v22.3*
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<foo:strorempty>")
|
||||
async def handler(request, foo: str):
|
||||
...
|
||||
```
|
||||
|
||||
### `int`
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: `r"-?\d+"`
|
||||
**Cast type**: `int`
|
||||
**Example matches**:
|
||||
|
||||
- `/path/to/10`
|
||||
- `/path/to/-10`
|
||||
|
||||
_Does not match float, hex, octal, etc_
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<foo:int>")
|
||||
async def handler(request, foo: int):
|
||||
...
|
||||
```
|
||||
|
||||
### `float`
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: `r"-?(?:\d+(?:\.\d*)?|\.\d+)"`
|
||||
**Cast type**: `float`
|
||||
**Example matches**:
|
||||
|
||||
- `/path/to/10`
|
||||
- `/path/to/-10`
|
||||
- `/path/to/1.5`
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<foo:float>")
|
||||
async def handler(request, foo: float):
|
||||
...
|
||||
```
|
||||
|
||||
### `alpha`
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: `r"[A-Za-z]+"`
|
||||
**Cast type**: `str`
|
||||
**Example matches**:
|
||||
|
||||
- `/path/to/Bob`
|
||||
- `/path/to/Python`
|
||||
|
||||
_Does not match a digit, or a space or other special character_
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<foo:alpha>")
|
||||
async def handler(request, foo: str):
|
||||
...
|
||||
```
|
||||
|
||||
### `slug`
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: `r"[a-z0-9]+(?:-[a-z0-9]+)*"`
|
||||
**Cast type**: `str`
|
||||
**Example matches**:
|
||||
|
||||
- `/path/to/some-news-story`
|
||||
- `/path/to/or-has-digits-123`
|
||||
|
||||
*Added in v21.6*
|
||||
|
||||
.. column::
|
||||
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<article:slug>")
|
||||
async def handler(request, article: str):
|
||||
...
|
||||
```
|
||||
|
||||
### `path`
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: `r"[^/].*?"`
|
||||
**Cast type**: `str`
|
||||
**Example matches**:
|
||||
- `/path/to/hello`
|
||||
- `/path/to/hello.txt`
|
||||
- `/path/to/hello/world.txt`
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<foo:path>")
|
||||
async def handler(request, foo: str):
|
||||
...
|
||||
```
|
||||
.. warning::
|
||||
|
||||
Because this will match on `/`, you should be careful and thoroughly test your patterns that use `path` so they do not capture traffic intended for another endpoint. Additionally, depending on how you use this type, you may be creating a path traversal vulnerability in your application. It is your job to protect your endpoint against this, but feel free to ask in our community channels for help if you need it :)
|
||||
|
||||
### `ymd`
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: `r"^([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"`
|
||||
**Cast type**: `datetime.date`
|
||||
**Example matches**:
|
||||
|
||||
- `/path/to/2021-03-28`
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<foo:ymd>")
|
||||
async def handler(request, foo: datetime.date):
|
||||
...
|
||||
```
|
||||
|
||||
### `uuid`
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: `r"[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"`
|
||||
**Cast type**: `UUID`
|
||||
**Example matches**:
|
||||
|
||||
- `/path/to/123a123a-a12a-1a1a-a1a1-1a12a1a12345`
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<foo:uuid>")
|
||||
async def handler(request, foo: UUID):
|
||||
...
|
||||
```
|
||||
|
||||
### ext
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: n/a
|
||||
**Cast type**: *varies*
|
||||
**Example matches**:
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route("/path/to/<foo:ext>")
|
||||
async def handler(request, foo: str, ext: str):
|
||||
...
|
||||
```
|
||||
|
||||
| definition | example | filename | extension |
|
||||
| --------------------------------- | ----------- | ----------- | ---------- |
|
||||
| \<file:ext> | page.txt | `"page"` | `"txt"` |
|
||||
| \<file:ext=jpg> | cat.jpg | `"cat"` | `"jpg"` |
|
||||
| \<file:ext=jpg\|png\|gif\|svg> | cat.jpg | `"cat"` | `"jpg"` |
|
||||
| <file=int:ext> | 123.txt | `123` | `"txt"` |
|
||||
| <file=int:ext=jpg\|png\|gif\|svg> | 123.svg | `123` | `"svg"` |
|
||||
| <file=float:ext=tar.gz> | 3.14.tar.gz | `3.14` | `"tar.gz"` |
|
||||
|
||||
File extensions can be matched using the special `ext` parameter type. It uses a special format that allows you to specify other types of parameter types as the file name, and one or more specific extensions as shown in the example table above.
|
||||
|
||||
It does *not* support the `path` parameter type.
|
||||
|
||||
*Added in v22.3*
|
||||
|
||||
### regex
|
||||
|
||||
.. column::
|
||||
|
||||
**Regular expression applied**: _whatever you insert_
|
||||
**Cast type**: `str`
|
||||
**Example matches**:
|
||||
|
||||
- `/path/to/2021-01-01`
|
||||
|
||||
This gives you the freedom to define specific matching patterns for your use case.
|
||||
|
||||
In the example shown, we are looking for a date that is in `YYYY-MM-DD` format.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route(r"/path/to/<foo:([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))>")
|
||||
async def handler(request, foo: str):
|
||||
...
|
||||
```
|
||||
|
||||
### Regex Matching
|
||||
|
||||
|
||||
More often than not, compared with complex routing, the above example is too simple, and we use a completely different routing matching pattern, so here we will explain the advanced usage of regex matching in detail.
|
||||
|
||||
Sometimes, you want to match a part of a route:
|
||||
|
||||
```text
|
||||
/image/123456789.jpg
|
||||
```
|
||||
|
||||
If you wanted to match the file pattern, but only capture the numeric portion, you need to do some regex fun 😄:
|
||||
|
||||
```python
|
||||
app.route(r"/image/<img_id:(?P<img_id>\d+)\.jpg>")
|
||||
```
|
||||
|
||||
Further, these should all be acceptable:
|
||||
|
||||
```python
|
||||
@app.get(r"/<foo:[a-z]{3}.txt>") # matching on the full pattern
|
||||
@app.get(r"/<foo:([a-z]{3}).txt>") # defining a single matching group
|
||||
@app.get(r"/<foo:(?P<foo>[a-z]{3}).txt>") # defining a single named matching group
|
||||
@app.get(r"/<foo:(?P<foo>[a-z]{3}).(?:txt)>") # defining a single named matching group, with one or more non-matching groups
|
||||
```
|
||||
|
||||
Also, if using a named matching group, it must be the same as the segment label.
|
||||
|
||||
```python
|
||||
@app.get(r"/<foo:(?P<foo>\d+).jpg>") # OK
|
||||
@app.get(r"/<foo:(?P<bar>\d+).jpg>") # NOT OK
|
||||
```
|
||||
|
||||
For more regular usage methods, please refer to [Regular expression operations](https://docs.python.org/3/library/re.html)
|
||||
|
||||
## Generating a URL
|
||||
|
||||
.. column::
|
||||
|
||||
Sanic provides a method to generate URLs based on the handler method name: `app.url_for()`. This is useful if you want to avoid hardcoding url paths into your app; instead, you can just reference the handler name.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.route('/')
|
||||
async def index(request):
|
||||
# generate a URL for the endpoint `post_handler`
|
||||
url = app.url_for('post_handler', post_id=5)
|
||||
|
||||
# Redirect to `/posts/5`
|
||||
return redirect(url)
|
||||
|
||||
@app.route('/posts/<post_id>')
|
||||
async def post_handler(request, post_id):
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
You can pass any arbitrary number of keyword arguments. Anything that is _not_ a request parameter will be implemented as a part of the query string.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
assert app.url_for(
|
||||
"post_handler",
|
||||
post_id=5,
|
||||
arg_one="one",
|
||||
arg_two="two",
|
||||
) == "/posts/5?arg_one=one&arg_two=two"
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Also supported is passing multiple values for a single query key.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
assert app.url_for(
|
||||
"post_handler",
|
||||
post_id=5,
|
||||
arg_one=["one", "two"],
|
||||
) == "/posts/5?arg_one=one&arg_one=two"
|
||||
```
|
||||
|
||||
### Special keyword arguments
|
||||
|
||||
See [API Docs]() for more details.
|
||||
|
||||
```python
|
||||
app.url_for("post_handler", post_id=5, arg_one="one", _anchor="anchor")
|
||||
# '/posts/5?arg_one=one#anchor'
|
||||
|
||||
# _external requires you to pass an argument _server or set SERVER_NAME in app.config if not url will be same as no _external
|
||||
app.url_for("post_handler", post_id=5, arg_one="one", _external=True)
|
||||
# '//server/posts/5?arg_one=one'
|
||||
|
||||
# when specifying _scheme, _external must be True
|
||||
app.url_for("post_handler", post_id=5, arg_one="one", _scheme="http", _external=True)
|
||||
# 'http://server/posts/5?arg_one=one'
|
||||
|
||||
# you can pass all special arguments at once
|
||||
app.url_for("post_handler", post_id=5, arg_one=["one", "two"], arg_two=2, _anchor="anchor", _scheme="http", _external=True, _server="another_server:8888")
|
||||
# 'http://another_server:8888/posts/5?arg_one=one&arg_one=two&arg_two=2#anchor'
|
||||
```
|
||||
|
||||
### Customizing a route name
|
||||
|
||||
.. column::
|
||||
|
||||
A custom route name can be used by passing a `name` argument while registering the route.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.get("/get", name="get_handler")
|
||||
def handler(request):
|
||||
return text("OK")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Now, use this custom name to retrieve the URL
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
assert app.url_for("get_handler", foo="bar") == "/get?foo=bar"
|
||||
```
|
||||
|
||||
## Websockets routes
|
||||
|
||||
.. column::
|
||||
|
||||
Websocket routing works similar to HTTP methods.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
async def handler(request, ws):
|
||||
message = "Start"
|
||||
while True:
|
||||
await ws.send(message)
|
||||
message = await ws.recv()
|
||||
|
||||
app.add_websocket_route(handler, "/test")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
It also has a convenience decorator.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.websocket("/test")
|
||||
async def handler(request, ws):
|
||||
message = "Start"
|
||||
while True:
|
||||
await ws.send(message)
|
||||
message = await ws.recv()
|
||||
```
|
||||
|
||||
Read the [websockets section](/guide/advanced/websockets.md) to learn more about how they work.
|
||||
|
||||
## Strict slashes
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Sanic routes can be configured to strictly match on whether or not there is a trailing slash: `/`. This can be configured at a few levels and follows this order of precedence:
|
||||
|
||||
1. Route
|
||||
2. Blueprint
|
||||
3. BlueprintGroup
|
||||
4. Application
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
# provide default strict_slashes value for all routes
|
||||
app = Sanic(__file__, strict_slashes=True)
|
||||
```
|
||||
|
||||
```python
|
||||
# overwrite strict_slashes value for specific route
|
||||
@app.get("/get", strict_slashes=False)
|
||||
def handler(request):
|
||||
return text("OK")
|
||||
```
|
||||
|
||||
```python
|
||||
# it also works for blueprints
|
||||
bp = Blueprint(__file__, strict_slashes=True)
|
||||
|
||||
@bp.get("/bp/get", strict_slashes=False)
|
||||
def handler(request):
|
||||
return text("OK")
|
||||
```
|
||||
|
||||
```python
|
||||
bp1 = Blueprint(name="bp1", url_prefix="/bp1")
|
||||
bp2 = Blueprint(
|
||||
name="bp2",
|
||||
url_prefix="/bp2",
|
||||
strict_slashes=False,
|
||||
)
|
||||
|
||||
# This will enforce strict slashes check on the routes
|
||||
# under bp1 but ignore bp2 as that has an explicitly
|
||||
# set the strict slashes check to false
|
||||
group = Blueprint.group([bp1, bp2], strict_slashes=True)
|
||||
```
|
||||
|
||||
## Static files
|
||||
|
||||
.. column::
|
||||
|
||||
In order to serve static files from Sanic, use `app.static()`.
|
||||
|
||||
The order of arguments is important:
|
||||
|
||||
1. Route the files will be served from
|
||||
2. Path to the files on the server
|
||||
|
||||
See [API docs](https://sanic.readthedocs.io/en/stable/sanic/api/app.html#sanic.app.Sanic.static) for more details.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app.static("/static/", "/path/to/directory/")
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. tip::
|
||||
|
||||
It is generally best practice to end your directory paths with a trailing slash (`/this/is/a/directory/`). This removes ambiguity by being more explicit.
|
||||
|
||||
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
You can also serve individual files.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app.static("/", "/path/to/index.html")
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
It is also sometimes helpful to name your endpoint
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app.static(
|
||||
"/user/uploads/",
|
||||
"/path/to/uploads/",
|
||||
name="uploads",
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
Retrieving the URLs works similar to handlers. But, we can also add the `filename` argument when we need a specific file inside a directory.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
assert app.url_for(
|
||||
"static",
|
||||
name="static",
|
||||
filename="file.txt",
|
||||
) == "/static/file.txt"
|
||||
```
|
||||
```python
|
||||
assert app.url_for(
|
||||
"static",
|
||||
name="uploads",
|
||||
filename="image.png",
|
||||
) == "/user/uploads/image.png"
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
.. tip::
|
||||
|
||||
If you are going to have multiple `static()` routes, then it is *highly* suggested that you manually name them. This will almost certainly alleviate potential hard to discover bugs.
|
||||
|
||||
```python
|
||||
app.static("/user/uploads/", "/path/to/uploads/", name="uploads")
|
||||
app.static("/user/profile/", "/path/to/profile/", name="profile_pics")
|
||||
```
|
||||
|
||||
|
||||
#### Auto index serving
|
||||
|
||||
.. column::
|
||||
|
||||
If you have a directory of static files that should be served by an index page, you can provide the filename of the index. Now, when reaching that directory URL, the index page will be served.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app.static("/foo/", "/path/to/foo/", index="index.html")
|
||||
```
|
||||
|
||||
*Added in v23.3*
|
||||
|
||||
#### File browser
|
||||
|
||||
.. column::
|
||||
|
||||
When serving a directory from a static handler, Sanic can be configured to show a basic file browser instead using `directory_view=True`.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
app.static("/uploads/", "/path/to/dir", directory_view=True)
|
||||
```
|
||||
|
||||
You now have a browsable directory in your web browser:
|
||||
|
||||

|
||||
|
||||
*Added in v23.3*
|
||||
|
||||
## Route context
|
||||
|
||||
|
||||
.. column::
|
||||
|
||||
When a route is defined, you can add any number of keyword arguments with a `ctx_` prefix. These values will be injected into the route `ctx` object.
|
||||
|
||||
.. column::
|
||||
|
||||
```python
|
||||
@app.get("/1", ctx_label="something")
|
||||
async def handler1(request):
|
||||
...
|
||||
|
||||
@app.get("/2", ctx_label="something")
|
||||
async def handler2(request):
|
||||
...
|
||||
|
||||
@app.get("/99")
|
||||
async def handler99(request):
|
||||
...
|
||||
|
||||
@app.on_request
|
||||
async def do_something(request):
|
||||
if request.route.ctx.label == "something":
|
||||
...
|
||||
```
|
||||
|
||||
*Added in v21.12*
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user