Merge branch 'main' into middleware-revamp

This commit is contained in:
Adam Hopkins 2022-08-17 14:17:34 +03:00 committed by GitHub
commit b59131504b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 186 additions and 36 deletions

View File

@ -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. -->

View File

@ -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.

View File

@ -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. -->

View File

@ -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.

View File

@ -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

View File

@ -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"

View File

@ -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(

View File

@ -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):
""" """

View File

@ -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>"

View File

@ -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

View File

@ -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

View File

@ -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()