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**
|
||||
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**
|
||||
Relevant source code, make sure to remove what is not necessary.
|
||||
<!-- Relevant source code, make sure to remove what is not necessary. -->
|
||||
|
||||
|
||||
**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):**
|
||||
- OS: [e.g. iOS]
|
||||
- Version [e.g. 0.8.3]
|
||||
<!-- 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 ``` -->
|
||||
- OS:
|
||||
- Sanic Version:
|
||||
|
||||
|
||||
**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
|
||||
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.
|
||||
|
|
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.**
|
||||
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**
|
||||
A clear and concise description of what you want to happen.
|
||||
<!-- 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.
|
||||
<!-- 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.
|
||||
|
||||
| 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.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
|
||||
|
||||
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.
|
||||
|
|
14
sanic/app.py
14
sanic/app.py
|
@ -1308,7 +1308,7 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
|||
self.config.update_config(config)
|
||||
|
||||
@property
|
||||
def asgi(self):
|
||||
def asgi(self) -> bool:
|
||||
return self.state.asgi
|
||||
|
||||
@asgi.setter
|
||||
|
@ -1515,6 +1515,18 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
|
|||
self.signalize(self.config.TOUCHUP)
|
||||
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
|
||||
if (
|
||||
self.__class__._uvloop_setting is not None
|
||||
|
|
|
@ -34,6 +34,15 @@ class LocalCertCreator(str, Enum):
|
|||
|
||||
|
||||
HTTP_METHODS = tuple(HTTPMethod.__members__.values())
|
||||
SAFE_HTTP_METHODS = (HTTPMethod.GET, HTTPMethod.HEAD, HTTPMethod.OPTIONS)
|
||||
IDEMPOTENT_HTTP_METHODS = (
|
||||
HTTPMethod.GET,
|
||||
HTTPMethod.HEAD,
|
||||
HTTPMethod.PUT,
|
||||
HTTPMethod.DELETE,
|
||||
HTTPMethod.OPTIONS,
|
||||
)
|
||||
CACHEABLE_HTTP_METHODS = (HTTPMethod.GET, HTTPMethod.HEAD)
|
||||
DEFAULT_HTTP_CONTENT_TYPE = "application/octet-stream"
|
||||
DEFAULT_LOCAL_TLS_KEY = "key.pem"
|
||||
DEFAULT_LOCAL_TLS_CERT = "cert.pem"
|
||||
|
|
|
@ -525,7 +525,7 @@ class RunnerMixin(metaclass=SanicMeta):
|
|||
)
|
||||
)
|
||||
else:
|
||||
server = ""
|
||||
server = "ASGI" if self.asgi else "unknown" # type: ignore
|
||||
|
||||
display = {
|
||||
"mode": " ".join(mode),
|
||||
|
@ -571,8 +571,12 @@ class RunnerMixin(metaclass=SanicMeta):
|
|||
|
||||
@property
|
||||
def serve_location(self) -> str:
|
||||
server_settings = self.state.server_info[0].settings
|
||||
return self.get_server_location(server_settings)
|
||||
try:
|
||||
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
|
||||
def get_server_location(
|
||||
|
|
|
@ -38,7 +38,12 @@ from httptools import parse_url
|
|||
from httptools.parser.errors import HttpParserInvalidURLError
|
||||
|
||||
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.headers import (
|
||||
AcceptContainer,
|
||||
|
@ -986,6 +991,33 @@ class Request:
|
|||
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -546,3 +546,13 @@ async def test_signals_triggered(app):
|
|||
assert response.status_code == 200
|
||||
assert response.text == "test_signals_triggered"
|
||||
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>"
|
||||
|
|
|
@ -243,3 +243,54 @@ def test_request_stream_id(app):
|
|||
|
||||
_, resp = app.test_client.get("/")
|
||||
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 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