Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP/3 flow #2403

Merged
merged 38 commits into from May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d26d79c
Add TLS password to config
ahopkins Feb 23, 2022
06035c8
Move HTTP streaming events to receiver
ahopkins Feb 23, 2022
2262c69
Streaming send
ahopkins Feb 24, 2022
517d7d5
Setup response headers
ahopkins Feb 24, 2022
7523e87
remove overlapping slots from app.Sanic, fix broken slots inherit of …
ariebovenberg Feb 24, 2022
8b090f2
Merge branch 'main' of github.com:sanic-org/sanic into http3-flow
ahopkins Feb 27, 2022
0c9df02
Add a docstring to `Request.respond()` (#2409)
Bluenix2 Mar 14, 2022
2a8e910
Add two new events on the reloader process (#2413)
ahopkins Mar 22, 2022
44b108b
Changes to CLI (#2401)
duckduckdroid Mar 23, 2022
c9dbc8e
Remove loop as required listener arg (#2414)
ahopkins Mar 23, 2022
0030425
Conditionally inject CLI arguments into factory (#2402)
ahopkins Mar 23, 2022
6e0a687
Upgrade tests for sanic-routing changes (#2405)
ahopkins Mar 23, 2022
32962d1
Fixing typing for ListenerMixin.listener (#2376)
aericson Mar 23, 2022
361c242
remove error_logger on websockets (#2373)
jonra1993 Mar 23, 2022
f6fdc80
allow multidict version 6 (#2396)
dotlambda Mar 23, 2022
0309874
Add config option to skip Touchup step, for debugging purposes (#2361)
ashleysommer Mar 24, 2022
0cb342a
Better exception for bad URL parse (#2415)
ahopkins Mar 24, 2022
bb44748
Fix "DeprecationWarning: There is no current event loop" (#2390)
jmarcet Mar 30, 2022
874718d
Bump version and 22.3 changelog (#2418)
ahopkins Mar 30, 2022
00218aa
22.3 Internal version bumps (#2419)
ahopkins Mar 31, 2022
cc97287
Add fall back for Windows even loop fetching (#2421)
ahopkins Apr 17, 2022
3a6cc73
feat: easier websocket interface annotation (#2438)
SaidBySolo Apr 24, 2022
78b6723
Preserve blank form values for urlencoded forms (option) (#2439)
sjsadowski Apr 24, 2022
5d683c6
Expose scope parameter in request object (#2432)
azimovMichael Apr 26, 2022
b66b460
Merge conflicts
ahopkins May 11, 2022
35bbdfe
Logging
ahopkins May 11, 2022
199d6ea
Move verbosity filtering to logger
ahopkins May 11, 2022
293278b
Resolve warning issue with error handler mismatch warning (#2452)
ahopkins May 11, 2022
2bfa65e
Current release mergeback (#2454)
ahopkins May 11, 2022
7eb64bb
Merge branch 'main' of github.com:sanic-org/sanic into verbosity
ahopkins May 11, 2022
e98a631
Fix verbosity test on ASGI
ahopkins May 11, 2022
4d2afed
Add Sanic color
ahopkins May 11, 2022
cccf536
Merge conflicts
ahopkins May 11, 2022
425182c
WIP
ahopkins May 12, 2022
86ae5f9
refactor: consistent exception naming (#2420)
prryplatypus May 12, 2022
4ee2e57
Properly catch websocket CancelledError in websocket handler in Pytho…
ashleysommer May 23, 2022
46af59b
merge conflict
ahopkins May 23, 2022
9045719
Finish flow
ahopkins May 24, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion CONTRIBUTING.rst
Expand Up @@ -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
*****
Expand Down Expand Up @@ -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.

Expand Down
1 change: 1 addition & 0 deletions docs/sanic/changelog.rst
@@ -1,6 +1,7 @@
📜 Changelog
============

.. mdinclude:: ./releases/22/22.3.md
.. mdinclude:: ./releases/21/21.12.md
.. mdinclude:: ./releases/21/21.9.md
.. include:: ../../CHANGELOG.rst
52 changes: 52 additions & 0 deletions docs/sanic/releases/22/22.3.md
@@ -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`
3 changes: 2 additions & 1 deletion examples/delayed_response.py
Expand Up @@ -4,14 +4,15 @@


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")
Expand Down
2 changes: 2 additions & 0 deletions sanic/__init__.py
Expand Up @@ -4,6 +4,7 @@
from sanic.constants import HTTPMethod
from sanic.request import Request
from sanic.response import HTTPResponse, html, json, text
from sanic.server.websockets.impl import WebsocketImplProtocol as Websocket


__all__ = (
Expand All @@ -13,6 +14,7 @@
"HTTPMethod",
"HTTPResponse",
"Request",
"Websocket",
"html",
"json",
"text",
Expand Down
2 changes: 1 addition & 1 deletion sanic/__version__.py
@@ -1 +1 @@
__version__ = "22.3.0.dev1"
__version__ = "22.3.2"
43 changes: 30 additions & 13 deletions sanic/app.py
Expand Up @@ -11,7 +11,6 @@
CancelledError,
Task,
ensure_future,
get_event_loop,
get_running_loop,
wait_for,
)
Expand Down Expand Up @@ -59,12 +58,13 @@
from sanic.compat import OS_IS_WINDOWS, enable_windows_color_support
from sanic.config import SANIC_PREFIX, Config
from sanic.exceptions import (
InvalidUsage,
BadRequest,
SanicException,
ServerError,
URLBuildError,
)
from sanic.handlers import ErrorHandler
from sanic.helpers import _default
from sanic.http import Stage
from sanic.log import (
LOGGING_CONFIG_DEFAULTS,
Expand Down Expand Up @@ -142,7 +142,6 @@ class Sanic(BaseSanic, RunnerMixin, metaclass=TouchUpMeta):
"error_handler",
"go_fast",
"listeners",
"name",
"named_request_middleware",
"named_response_middleware",
"request_class",
Expand Down Expand Up @@ -254,7 +253,13 @@ def loop(self):
"Loop can only be retrieved after the app has started "
"running. Not supported with `create_server` function"
)
return get_event_loop()
try:
return get_running_loop()
except RuntimeError:
if sys.version_info > (3, 10):
return asyncio.get_event_loop_policy().get_event_loop()
else:
return asyncio.get_event_loop()

# -------------------------------------------------------------------- #
# Registration
Expand All @@ -277,7 +282,7 @@ def register_listener(
valid = ", ".join(
map(lambda x: x.lower(), ListenerEvent.__members__.keys())
)
raise InvalidUsage(f"Invalid event: {event}. Use one of: {valid}")
raise BadRequest(f"Invalid event: {event}. Use one of: {valid}")

if "." in _event:
self.signal(_event.value)(
Expand Down Expand Up @@ -945,6 +950,7 @@ async def handle_request(self, request: Request): # no cov
"response": response,
},
)
...
await response.send(end_stream=True)
elif isinstance(response, ResponseStream):
resp = await response(request)
Expand Down Expand Up @@ -988,10 +994,10 @@ async def _websocket_handler(
cancelled = False
try:
await fut
except Exception as e:
self.error_handler.log(request, e)
except (CancelledError, ConnectionClosed):
cancelled = True
except Exception as e:
self.error_handler.log(request, e)
finally:
self.websocket_tasks.remove(fut)
if cancelled:
Expand Down Expand Up @@ -1132,7 +1138,10 @@ def _cancel_websocket_tasks(cls, app, loop):
async def _listener(
app: Sanic, loop: AbstractEventLoop, listener: ListenerType
):
maybe_coro = listener(app, loop)
try:
maybe_coro = listener(app) # type: ignore
except TypeError:
maybe_coro = listener(app, loop) # type: ignore
if maybe_coro and isawaitable(maybe_coro):
await maybe_coro

Expand Down Expand Up @@ -1509,7 +1518,8 @@ def finalize(self):
if not Sanic.test_mode:
raise e

def signalize(self):
def signalize(self, allow_fail_builtin=True):
self.signal_router.allow_fail_builtin = allow_fail_builtin
try:
self.signal_router.finalize()
except FinalizationError as e:
Expand All @@ -1524,8 +1534,13 @@ async def _startup(self):
if hasattr(self, "_ext"):
self.ext._display()

if self.state.is_debug and self.config.TOUCHUP is not True:
self.config.TOUCHUP = False
elif self.config.TOUCHUP is _default:
self.config.TOUCHUP = True

# Setup routers
self.signalize()
self.signalize(self.config.TOUCHUP)
self.finalize()

# TODO: Replace in v22.6 to check against apps in app registry
Expand All @@ -1545,7 +1560,8 @@ async def _startup(self):
# TODO:
# - Raise warning if secondary apps have error handler config
ErrorHandler.finalize(self.error_handler, config=self.config)
TouchUp.run(self)
if self.config.TOUCHUP:
TouchUp.run(self)

self.state.is_started = True

Expand All @@ -1561,8 +1577,9 @@ async def _server_event(
"shutdown",
):
raise SanicException(f"Invalid server event: {event}")
if self.state.verbosity >= 1:
logger.debug(f"Triggering server events: {event}")
logger.debug(
f"Triggering server events: {event}", extra={"verbosity": 1}
)
reverse = concern == "shutdown"
if loop is None:
loop = self.loop
Expand Down
5 changes: 4 additions & 1 deletion sanic/application/state.py
Expand Up @@ -9,7 +9,7 @@
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union

from sanic.application.constants import Mode, Server, ServerStage
from sanic.log import logger
from sanic.log import VerbosityFilter, logger
from sanic.server.async_server import AsyncioServer


Expand Down Expand Up @@ -69,6 +69,9 @@ def set_mode(self, value: Union[str, Mode]):
if getattr(self.app, "configure_logging", False) and self.app.debug:
logger.setLevel(logging.DEBUG)

def set_verbosity(self, value: int):
VerbosityFilter.verbosity = value

@property
def is_debug(self):
return self.mode is Mode.DEBUG
Expand Down
43 changes: 22 additions & 21 deletions sanic/asgi.py
Expand Up @@ -25,27 +25,28 @@ class Lifespan:
def __init__(self, asgi_app: ASGIApp) -> None:
self.asgi_app = asgi_app

if self.asgi_app.sanic_app.state.verbosity > 0:
if (
"server.init.before"
in self.asgi_app.sanic_app.signal_router.name_index
):
logger.debug(
'You have set a listener for "before_server_start" '
"in ASGI mode. "
"It will be executed as early as possible, but not before "
"the ASGI server is started."
)
if (
"server.shutdown.after"
in self.asgi_app.sanic_app.signal_router.name_index
):
logger.debug(
'You have set a listener for "after_server_stop" '
"in ASGI mode. "
"It will be executed as late as possible, but not after "
"the ASGI server is stopped."
)
if (
"server.init.before"
in self.asgi_app.sanic_app.signal_router.name_index
):
logger.debug(
'You have set a listener for "before_server_start" '
"in ASGI mode. "
"It will be executed as early as possible, but not before "
"the ASGI server is started.",
extra={"verbosity": 1},
)
if (
"server.shutdown.after"
in self.asgi_app.sanic_app.signal_router.name_index
):
logger.debug(
'You have set a listener for "after_server_stop" '
"in ASGI mode. "
"It will be executed as late as possible, but not after "
"the ASGI server is stopped.",
extra={"verbosity": 1},
)

async def startup(self) -> None:
"""
Expand Down
31 changes: 29 additions & 2 deletions sanic/cli/app.py
Expand Up @@ -71,6 +71,13 @@ def run(self):
legacy_version = len(sys.argv) == 2 and sys.argv[-1] == "-v"
parse_args = ["--version"] if legacy_version else None

if not parse_args:
parsed, unknown = self.parser.parse_known_args()
if unknown and parsed.factory:
for arg in unknown:
if arg.startswith("--"):
self.parser.add_argument(arg.split("=")[0])

self.args = self.parser.parse_args(args=parse_args)
self._precheck()

Expand Down Expand Up @@ -120,21 +127,41 @@ def _get_app(self):
delimiter = ":" if ":" in self.args.module else "."
module_name, app_name = self.args.module.rsplit(delimiter, 1)

if module_name == "" and os.path.isdir(self.args.module):
raise ValueError(
"App not found.\n"
" Please use --simple if you are passing a "
"directory to sanic.\n"
f" eg. sanic {self.args.module} --simple"
)

if app_name.endswith("()"):
self.args.factory = True
app_name = app_name[:-2]

module = import_module(module_name)
app = getattr(module, app_name, None)
if self.args.factory:
app = app()
try:
app = app(self.args)
except TypeError:
app = app()

app_type_name = type(app).__name__

if not isinstance(app, Sanic):
if callable(app):
solution = f"sanic {self.args.module} --factory"
raise ValueError(
"Module is not a Sanic app, it is a"
f"{app_type_name}\n"
" If this callable returns a"
f"Sanic instance try: \n{solution}"
)

raise ValueError(
f"Module is not a Sanic app, it is a {app_type_name}\n"
f" Perhaps you meant {self.args.module}.app?"
f" Perhaps you meant {self.args.module}:app?"
)
except ImportError as e:
if module_name.startswith(e.name):
Expand Down
2 changes: 1 addition & 1 deletion sanic/compat.py
Expand Up @@ -72,7 +72,7 @@ async def stay_active(app):
"""Asyncio wakeups to allow receiving SIGINT in Python"""
while not die:
# If someone else stopped the app, just exit
if app.is_stopping:
if app.state.is_stopping:
return
# Windows Python blocks signal handlers while the event loop is
# waiting for I/O. Frequent wakeups keep interrupts flowing.
Expand Down