Merge branch 'main' into middleware-revamp
This commit is contained in:
commit
b59131504b
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
13
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -5,21 +5,22 @@ about: Create a report to help us improve
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is, make sure to paste any exceptions and tracebacks.
|
<!-- A clear and concise description of what the bug is, make sure to paste any exceptions and tracebacks. -->
|
||||||
|
|
||||||
|
|
||||||
**Code snippet**
|
**Code snippet**
|
||||||
Relevant source code, make sure to remove what is not necessary.
|
<!-- Relevant source code, make sure to remove what is not necessary. -->
|
||||||
|
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
A clear and concise description of what you expected to happen.
|
<!-- A clear and concise description of what you expected to happen. -->
|
||||||
|
|
||||||
|
|
||||||
**Environment (please complete the following information):**
|
**Environment (please complete the following information):**
|
||||||
- OS: [e.g. iOS]
|
<!-- Please provide the information below. Instead, you can copy and paste the message that Sanic shows on startup. If you do, please remember to format it with ``` -->
|
||||||
- Version [e.g. 0.8.3]
|
- OS:
|
||||||
|
- Sanic Version:
|
||||||
|
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
<!-- Add any other context about the problem here. -->
|
||||||
|
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -3,3 +3,6 @@ contact_links:
|
||||||
- name: Questions and Help
|
- name: Questions and Help
|
||||||
url: https://community.sanicframework.org/c/questions-and-help
|
url: https://community.sanicframework.org/c/questions-and-help
|
||||||
about: Do you need help with Sanic? Ask your questions here.
|
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.
|
||||||
|
|
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
6
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -5,12 +5,12 @@ about: Suggest an idea for Sanic
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**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 [...]
|
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
**Describe the solution you'd like**
|
||||||
A clear and concise description of what you want to happen.
|
<!-- A clear and concise description of what you want to happen. -->
|
||||||
|
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context or sample code about the feature request here.
|
<!-- Add any other context or sample code about the feature request here. -->
|
||||||
|
|
51
SECURITY.md
51
SECURITY.md
|
@ -4,31 +4,40 @@
|
||||||
|
|
||||||
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.
|
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
|
| Version | LTS | Supported |
|
||||||
:heavy_check_mark: = full support
|
| ------- | ------------- | ----------------------- |
|
||||||
|
| 22.6 | | :white_check_mark: |
|
||||||
|
| 22.3 | | :x: |
|
||||||
|
| 21.12 | until 2023-12 | :white_check_mark: |
|
||||||
|
| 21.9 | | :x: |
|
||||||
|
| 21.6 | | :x: |
|
||||||
|
| 21.3 | | :x: |
|
||||||
|
| 20.12 | until 2022-12 | :ballot_box_with_check: |
|
||||||
|
| 20.9 | | :x: |
|
||||||
|
| 20.6 | | :x: |
|
||||||
|
| 20.3 | | :x: |
|
||||||
|
| 19.12 | | :x: |
|
||||||
|
| 19.9 | | :x: |
|
||||||
|
| 19.6 | | :x: |
|
||||||
|
| 19.3 | | :x: |
|
||||||
|
| 18.12 | | :x: |
|
||||||
|
| 0.8.3 | | :x: |
|
||||||
|
| 0.7.0 | | :x: |
|
||||||
|
| 0.6.0 | | :x: |
|
||||||
|
| 0.5.4 | | :x: |
|
||||||
|
| 0.4.1 | | :x: |
|
||||||
|
| 0.3.1 | | :x: |
|
||||||
|
| 0.2.0 | | :x: |
|
||||||
|
| 0.1.9 | | :x: |
|
||||||
|
|
||||||
|
:ballot_box_with_check: = security/bug fixes
|
||||||
|
:white_check_mark: = full support
|
||||||
|
|
||||||
## Reporting a Vulnerability
|
## 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.
|
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.
|
This will help to not publicize the issue until the team can address it and resolve it.
|
||||||
|
|
14
sanic/app.py
14
sanic/app.py
|
@ -1308,7 +1308,7 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
||||||
self.config.update_config(config)
|
self.config.update_config(config)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def asgi(self):
|
def asgi(self) -> bool:
|
||||||
return self.state.asgi
|
return self.state.asgi
|
||||||
|
|
||||||
@asgi.setter
|
@asgi.setter
|
||||||
|
@ -1515,6 +1515,18 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
||||||
self.signalize(self.config.TOUCHUP)
|
self.signalize(self.config.TOUCHUP)
|
||||||
self.finalize()
|
self.finalize()
|
||||||
|
|
||||||
|
route_names = [route.name for route in self.router.routes]
|
||||||
|
duplicates = {
|
||||||
|
name for name in route_names if route_names.count(name) > 1
|
||||||
|
}
|
||||||
|
if duplicates:
|
||||||
|
names = ", ".join(duplicates)
|
||||||
|
deprecation(
|
||||||
|
f"Duplicate route names detected: {names}. In the future, "
|
||||||
|
"Sanic will enforce uniqueness in route naming.",
|
||||||
|
23.3,
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: Replace in v22.6 to check against apps in app registry
|
# TODO: Replace in v22.6 to check against apps in app registry
|
||||||
if (
|
if (
|
||||||
self.__class__._uvloop_setting is not None
|
self.__class__._uvloop_setting is not None
|
||||||
|
|
|
@ -34,6 +34,15 @@ class LocalCertCreator(str, Enum):
|
||||||
|
|
||||||
|
|
||||||
HTTP_METHODS = tuple(HTTPMethod.__members__.values())
|
HTTP_METHODS = tuple(HTTPMethod.__members__.values())
|
||||||
|
SAFE_HTTP_METHODS = (HTTPMethod.GET, HTTPMethod.HEAD, HTTPMethod.OPTIONS)
|
||||||
|
IDEMPOTENT_HTTP_METHODS = (
|
||||||
|
HTTPMethod.GET,
|
||||||
|
HTTPMethod.HEAD,
|
||||||
|
HTTPMethod.PUT,
|
||||||
|
HTTPMethod.DELETE,
|
||||||
|
HTTPMethod.OPTIONS,
|
||||||
|
)
|
||||||
|
CACHEABLE_HTTP_METHODS = (HTTPMethod.GET, HTTPMethod.HEAD)
|
||||||
DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream"
|
DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream"
|
||||||
DEFAULT_LOCAL_TLS_KEY = "key.pem"
|
DEFAULT_LOCAL_TLS_KEY = "key.pem"
|
||||||
DEFAULT_LOCAL_TLS_CERT = "cert.pem"
|
DEFAULT_LOCAL_TLS_CERT = "cert.pem"
|
||||||
|
|
|
@ -525,7 +525,7 @@ class RunnerMixin(metaclass=SanicMeta):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
server = ""
|
server = "ASGI" if self.asgi else "unknown" # type: ignore
|
||||||
|
|
||||||
display = {
|
display = {
|
||||||
"mode": " ".join(mode),
|
"mode": " ".join(mode),
|
||||||
|
@ -571,8 +571,12 @@ class RunnerMixin(metaclass=SanicMeta):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def serve_location(self) -> str:
|
def serve_location(self) -> str:
|
||||||
server_settings = self.state.server_info[0].settings
|
try:
|
||||||
return self.get_server_location(server_settings)
|
server_settings = self.state.server_info[0].settings
|
||||||
|
return self.get_server_location(server_settings)
|
||||||
|
except IndexError:
|
||||||
|
location = "ASGI" if self.asgi else "unknown" # type: ignore
|
||||||
|
return f"http://<{location}>"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_server_location(
|
def get_server_location(
|
||||||
|
|
|
@ -38,7 +38,12 @@ from httptools import parse_url
|
||||||
from httptools.parser.errors import HttpParserInvalidURLError
|
from httptools.parser.errors import HttpParserInvalidURLError
|
||||||
|
|
||||||
from sanic.compat import CancelledErrors, Header
|
from sanic.compat import CancelledErrors, Header
|
||||||
from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE
|
from sanic.constants import (
|
||||||
|
CACHEABLE_HTTP_METHODS,
|
||||||
|
DEFAULT_HTTP_CONTENT_TYPE,
|
||||||
|
IDEMPOTENT_HTTP_METHODS,
|
||||||
|
SAFE_HTTP_METHODS,
|
||||||
|
)
|
||||||
from sanic.exceptions import BadRequest, BadURL, ServerError
|
from sanic.exceptions import BadRequest, BadURL, ServerError
|
||||||
from sanic.headers import (
|
from sanic.headers import (
|
||||||
AcceptContainer,
|
AcceptContainer,
|
||||||
|
@ -986,6 +991,33 @@ class Request:
|
||||||
|
|
||||||
return self.transport.scope
|
return self.transport.scope
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_safe(self) -> bool:
|
||||||
|
"""
|
||||||
|
:return: Whether the HTTP method is safe.
|
||||||
|
See https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return self.method in SAFE_HTTP_METHODS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_idempotent(self) -> bool:
|
||||||
|
"""
|
||||||
|
:return: Whether the HTTP method is iempotent.
|
||||||
|
See https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.2
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return self.method in IDEMPOTENT_HTTP_METHODS
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_cacheable(self) -> bool:
|
||||||
|
"""
|
||||||
|
:return: Whether the HTTP method is cacheable.
|
||||||
|
See https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.3
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
|
return self.method in CACHEABLE_HTTP_METHODS
|
||||||
|
|
||||||
|
|
||||||
class File(NamedTuple):
|
class File(NamedTuple):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -546,3 +546,13 @@ async def test_signals_triggered(app):
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.text == "test_signals_triggered"
|
assert response.text == "test_signals_triggered"
|
||||||
assert signals_triggered == signals_expected
|
assert signals_triggered == signals_expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_asgi_serve_location(app):
|
||||||
|
@app.get("/")
|
||||||
|
def _request(request: Request):
|
||||||
|
return text(request.app.serve_location)
|
||||||
|
|
||||||
|
_, response = await app.asgi_client.get("/")
|
||||||
|
assert response.text == "http://<ASGI>"
|
||||||
|
|
|
@ -76,7 +76,7 @@ def test_full_message(client):
|
||||||
)
|
)
|
||||||
response = client.recv()
|
response = client.recv()
|
||||||
|
|
||||||
# AltSvcCheck touchup removes the Alt-Svc header from the
|
# AltSvcCheck touchup removes the Alt-Svc header from the
|
||||||
# response in the Python 3.9+ in this case
|
# response in the Python 3.9+ in this case
|
||||||
assert len(response) == (151 if version_info < (3, 9) else 140)
|
assert len(response) == (151 if version_info < (3, 9) else 140)
|
||||||
assert b"200 OK" in response
|
assert b"200 OK" in response
|
||||||
|
|
|
@ -243,3 +243,54 @@ def test_request_stream_id(app):
|
||||||
|
|
||||||
_, resp = app.test_client.get("/")
|
_, resp = app.test_client.get("/")
|
||||||
assert resp.text == "Stream ID is only a property of a HTTP/3 request"
|
assert resp.text == "Stream ID is only a property of a HTTP/3 request"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"method,safe",
|
||||||
|
(
|
||||||
|
("DELETE", False),
|
||||||
|
("GET", True),
|
||||||
|
("HEAD", True),
|
||||||
|
("OPTIONS", True),
|
||||||
|
("PATCH", False),
|
||||||
|
("POST", False),
|
||||||
|
("PUT", False),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_request_safe(method, safe):
|
||||||
|
request = Request(b"/", {}, None, method, None, None)
|
||||||
|
assert request.is_safe is safe
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"method,idempotent",
|
||||||
|
(
|
||||||
|
("DELETE", True),
|
||||||
|
("GET", True),
|
||||||
|
("HEAD", True),
|
||||||
|
("OPTIONS", True),
|
||||||
|
("PATCH", False),
|
||||||
|
("POST", False),
|
||||||
|
("PUT", True),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_request_idempotent(method, idempotent):
|
||||||
|
request = Request(b"/", {}, None, method, None, None)
|
||||||
|
assert request.is_idempotent is idempotent
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"method,cacheable",
|
||||||
|
(
|
||||||
|
("DELETE", False),
|
||||||
|
("GET", True),
|
||||||
|
("HEAD", True),
|
||||||
|
("OPTIONS", False),
|
||||||
|
("PATCH", False),
|
||||||
|
("POST", False),
|
||||||
|
("PUT", False),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_request_cacheable(method, cacheable):
|
||||||
|
request = Request(b"/", {}, None, method, None, None)
|
||||||
|
assert request.is_cacheable is cacheable
|
||||||
|
|
|
@ -1266,3 +1266,22 @@ async def test_added_callable_route_ctx_kwargs(app):
|
||||||
|
|
||||||
assert request.route.ctx.foo() == "foo"
|
assert request.route.ctx.foo() == "foo"
|
||||||
assert await request.route.ctx.bar() == 99
|
assert await request.route.ctx.bar() == 99
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_duplicate_route_deprecation(app):
|
||||||
|
@app.route("/foo", name="duped")
|
||||||
|
async def handler_foo(request):
|
||||||
|
return text("...")
|
||||||
|
|
||||||
|
@app.route("/bar", name="duped")
|
||||||
|
async def handler_bar(request):
|
||||||
|
return text("...")
|
||||||
|
|
||||||
|
message = (
|
||||||
|
r"\[DEPRECATION v23\.3\] Duplicate route names detected: "
|
||||||
|
r"test_duplicate_route_deprecation\.duped\. In the future, "
|
||||||
|
r"Sanic will enforce uniqueness in route naming\."
|
||||||
|
)
|
||||||
|
with pytest.warns(DeprecationWarning, match=message):
|
||||||
|
await app._startup()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user