Merge branch 'master' into 20.12LTS
This commit is contained in:
commit
b66fb6f9e8
@ -17,11 +17,11 @@ environment:
|
|||||||
PYTHON_VERSION: "3.8.x"
|
PYTHON_VERSION: "3.8.x"
|
||||||
PYTHON_ARCH: "64"
|
PYTHON_ARCH: "64"
|
||||||
|
|
||||||
- TOXENV: py39-no-ext
|
# - TOXENV: py39-no-ext
|
||||||
PYTHON: "C:\\Python39-x64\\python"
|
# PYTHON: "C:\\Python39-x64\\python"
|
||||||
PYTHONPATH: "C:\\Python39-x64"
|
# PYTHONPATH: "C:\\Python39-x64"
|
||||||
PYTHON_VERSION: "3.9.x"
|
# PYTHON_VERSION: "3.9.x"
|
||||||
PYTHON_ARCH: "64"
|
# PYTHON_ARCH: "64"
|
||||||
|
|
||||||
init: SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
init: SET "PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||||
|
|
||||||
|
@ -60,3 +60,26 @@ Open the address `http://0.0.0.0:8000 <http://0.0.0.0:8000>`_ in your web browse
|
|||||||
the message *Hello world!*.
|
the message *Hello world!*.
|
||||||
|
|
||||||
You now have a working Sanic server!
|
You now have a working Sanic server!
|
||||||
|
|
||||||
|
5. Application registry
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. code-block:: 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")
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
app = Sanic.get_app("my_awesome_server", force_create=True)
|
||||||
|
@ -133,7 +133,7 @@ which allows the handler function to work with any of the HTTP methods in the li
|
|||||||
async def get_handler(request):
|
async def get_handler(request):
|
||||||
return text('GET request - {}'.format(request.args))
|
return text('GET request - {}'.format(request.args))
|
||||||
|
|
||||||
There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is a also a route with no host, it will be the default.
|
There is also an optional `host` argument (which can be a list or a string). This restricts a route to the host or hosts provided. If there is also a route with no host, it will be the default.
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -58,10 +58,6 @@ More information about
|
|||||||
the available arguments to `httpx` can be found
|
the available arguments to `httpx` can be found
|
||||||
[in the documentation for `httpx <https://www.encode.io/httpx/>`_.
|
[in the documentation for `httpx <https://www.encode.io/httpx/>`_.
|
||||||
|
|
||||||
Additionally, Sanic has an asynchronous testing client. The difference is that the async client will not stand up an
|
|
||||||
instance of your application, but will instead reach inside it using ASGI. All listeners and middleware are still
|
|
||||||
executed.
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_index_returns_200():
|
async def test_index_returns_200():
|
||||||
|
45
sanic/app.py
45
sanic/app.py
@ -2,12 +2,11 @@ import logging
|
|||||||
import logging.config
|
import logging.config
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import warnings
|
|
||||||
|
|
||||||
from asyncio import CancelledError, Protocol, ensure_future, get_event_loop
|
from asyncio import CancelledError, Protocol, ensure_future, get_event_loop
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import getmodulename, isawaitable, signature, stack
|
from inspect import isawaitable, signature
|
||||||
from socket import socket
|
from socket import socket
|
||||||
from ssl import Purpose, SSLContext, create_default_context
|
from ssl import Purpose, SSLContext, create_default_context
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
@ -38,6 +37,9 @@ from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
|||||||
|
|
||||||
|
|
||||||
class Sanic:
|
class Sanic:
|
||||||
|
_app_registry: Dict[str, "Sanic"] = {}
|
||||||
|
test_mode = False
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name=None,
|
name=None,
|
||||||
@ -52,15 +54,10 @@ class Sanic:
|
|||||||
|
|
||||||
# Get name from previous stack frame
|
# Get name from previous stack frame
|
||||||
if name is None:
|
if name is None:
|
||||||
warnings.warn(
|
raise SanicException(
|
||||||
"Sanic(name=None) is deprecated and None value support "
|
"Sanic instance cannot be unnamed. "
|
||||||
"for `name` will be removed in the next release. "
|
|
||||||
"Please use Sanic(name='your_application_name') instead.",
|
"Please use Sanic(name='your_application_name') instead.",
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
)
|
||||||
frame_records = stack()[1]
|
|
||||||
name = getmodulename(frame_records[1])
|
|
||||||
|
|
||||||
# logging
|
# logging
|
||||||
if configure_logging:
|
if configure_logging:
|
||||||
@ -90,7 +87,8 @@ class Sanic:
|
|||||||
self.named_response_middleware = {}
|
self.named_response_middleware = {}
|
||||||
# Register alternative method names
|
# Register alternative method names
|
||||||
self.go_fast = self.run
|
self.go_fast = self.run
|
||||||
self.test_mode = False
|
|
||||||
|
self.__class__.register_app(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loop(self):
|
def loop(self):
|
||||||
@ -1401,9 +1399,36 @@ class Sanic:
|
|||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
# Configuration
|
# Configuration
|
||||||
# -------------------------------------------------------------------- #
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
def update_config(self, config: Union[bytes, str, dict, Any]):
|
def update_config(self, config: Union[bytes, str, dict, Any]):
|
||||||
"""Update app.config.
|
"""Update app.config.
|
||||||
|
|
||||||
Please refer to config.py::Config.update_config for documentation."""
|
Please refer to config.py::Config.update_config for documentation."""
|
||||||
|
|
||||||
self.config.update_config(config)
|
self.config.update_config(config)
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------- #
|
||||||
|
# Class methods
|
||||||
|
# -------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_app(cls, app: "Sanic") -> None:
|
||||||
|
"""Register a Sanic instance"""
|
||||||
|
if not isinstance(app, cls):
|
||||||
|
raise SanicException("Registered app must be an instance of Sanic")
|
||||||
|
|
||||||
|
name = app.name
|
||||||
|
if name in cls._app_registry and not cls.test_mode:
|
||||||
|
raise SanicException(f'Sanic app name "{name}" already in use.')
|
||||||
|
|
||||||
|
cls._app_registry[name] = app
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_app(cls, name: str, *, force_create: bool = False) -> "Sanic":
|
||||||
|
"""Retrieve an instantiated Sanic instance"""
|
||||||
|
try:
|
||||||
|
return cls._app_registry[name]
|
||||||
|
except KeyError:
|
||||||
|
if force_create:
|
||||||
|
return cls(name)
|
||||||
|
raise SanicException(f'Sanic app name "{name}" not found.')
|
||||||
|
@ -13,6 +13,7 @@ from sanic.exceptions import (
|
|||||||
InvalidUsage,
|
InvalidUsage,
|
||||||
)
|
)
|
||||||
from sanic.handlers import ContentRangeHandler
|
from sanic.handlers import ContentRangeHandler
|
||||||
|
from sanic.log import error_logger
|
||||||
from sanic.response import HTTPResponse, file, file_stream
|
from sanic.response import HTTPResponse, file, file_stream
|
||||||
|
|
||||||
|
|
||||||
@ -40,6 +41,10 @@ async def _static_request_handler(
|
|||||||
# match filenames which got encoded (filenames with spaces etc)
|
# match filenames which got encoded (filenames with spaces etc)
|
||||||
file_path = path.abspath(unquote(file_path))
|
file_path = path.abspath(unquote(file_path))
|
||||||
if not file_path.startswith(path.abspath(unquote(root_path))):
|
if not file_path.startswith(path.abspath(unquote(root_path))):
|
||||||
|
error_logger.exception(
|
||||||
|
f"File not found: path={file_or_directory}, "
|
||||||
|
f"relative_url={file_uri}"
|
||||||
|
)
|
||||||
raise FileNotFound(
|
raise FileNotFound(
|
||||||
"File not found", path=file_or_directory, relative_url=file_uri
|
"File not found", path=file_or_directory, relative_url=file_uri
|
||||||
)
|
)
|
||||||
@ -94,6 +99,10 @@ async def _static_request_handler(
|
|||||||
except ContentRangeError:
|
except ContentRangeError:
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
|
error_logger.exception(
|
||||||
|
f"File not found: path={file_or_directory}, "
|
||||||
|
f"relative_url={file_uri}"
|
||||||
|
)
|
||||||
raise FileNotFound(
|
raise FileNotFound(
|
||||||
"File not found", path=file_or_directory, relative_url=file_uri
|
"File not found", path=file_or_directory, relative_url=file_uri
|
||||||
)
|
)
|
||||||
|
14
setup.py
14
setup.py
@ -5,6 +5,7 @@ import codecs
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from distutils.util import strtobool
|
from distutils.util import strtobool
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
@ -24,6 +25,7 @@ class PyTest(TestCommand):
|
|||||||
|
|
||||||
def run_tests(self):
|
def run_tests(self):
|
||||||
import shlex
|
import shlex
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
errno = pytest.main(shlex.split(self.pytest_args))
|
errno = pytest.main(shlex.split(self.pytest_args))
|
||||||
@ -38,7 +40,9 @@ def open_local(paths, mode="r", encoding="utf8"):
|
|||||||
|
|
||||||
with open_local(["sanic", "__version__.py"], encoding="latin1") as fp:
|
with open_local(["sanic", "__version__.py"], encoding="latin1") as fp:
|
||||||
try:
|
try:
|
||||||
version = re.findall(r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M)[0]
|
version = re.findall(
|
||||||
|
r"^__version__ = \"([^']+)\"\r?$", fp.read(), re.M
|
||||||
|
)[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise RuntimeError("Unable to determine version.")
|
raise RuntimeError("Unable to determine version.")
|
||||||
|
|
||||||
@ -72,7 +76,9 @@ setup_kwargs = {
|
|||||||
"entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]},
|
"entry_points": {"console_scripts": ["sanic = sanic.__main__:main"]},
|
||||||
}
|
}
|
||||||
|
|
||||||
env_dependency = '; sys_platform != "win32" ' 'and implementation_name == "cpython"'
|
env_dependency = (
|
||||||
|
'; sys_platform != "win32" ' 'and implementation_name == "cpython"'
|
||||||
|
)
|
||||||
ujson = "ujson>=1.35" + env_dependency
|
ujson = "ujson>=1.35" + env_dependency
|
||||||
uvloop = "uvloop>=0.5.3" + env_dependency
|
uvloop = "uvloop>=0.5.3" + env_dependency
|
||||||
|
|
||||||
@ -89,9 +95,9 @@ requirements = [
|
|||||||
tests_require = [
|
tests_require = [
|
||||||
"pytest==5.2.1",
|
"pytest==5.2.1",
|
||||||
"multidict>=5.0,<6.0",
|
"multidict>=5.0,<6.0",
|
||||||
"gunicorn",
|
"gunicorn==20.0.4",
|
||||||
"pytest-cov",
|
"pytest-cov",
|
||||||
"httpcore==0.3.0",
|
"httpcore==0.11.*",
|
||||||
"beautifulsoup4",
|
"beautifulsoup4",
|
||||||
uvloop,
|
uvloop,
|
||||||
ujson,
|
ujson,
|
||||||
|
@ -11,6 +11,7 @@ from sanic.router import RouteExists, Router
|
|||||||
|
|
||||||
|
|
||||||
random.seed("Pack my box with five dozen liquor jugs.")
|
random.seed("Pack my box with five dozen liquor jugs.")
|
||||||
|
Sanic.test_mode = True
|
||||||
|
|
||||||
if sys.platform in ["win32", "cygwin"]:
|
if sys.platform in ["win32", "cygwin"]:
|
||||||
collect_ignore = ["test_worker.py"]
|
collect_ignore = ["test_worker.py"]
|
||||||
|
@ -258,7 +258,7 @@ def test_handle_request_with_nested_sanic_exception(app, monkeypatch, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_app_name_required():
|
def test_app_name_required():
|
||||||
with pytest.deprecated_call():
|
with pytest.raises(SanicException):
|
||||||
Sanic()
|
Sanic()
|
||||||
|
|
||||||
|
|
||||||
@ -274,14 +274,35 @@ def test_app_has_test_mode_sync():
|
|||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.asyncio
|
def test_app_registry():
|
||||||
# async def test_app_has_test_mode_async():
|
instance = Sanic("test")
|
||||||
# app = Sanic("test")
|
assert Sanic._app_registry["test"] is instance
|
||||||
|
|
||||||
# @app.get("/")
|
|
||||||
# async def handler(request):
|
|
||||||
# assert request.app.test_mode
|
|
||||||
# return text("test")
|
|
||||||
|
|
||||||
# _, response = await app.asgi_client.get("/")
|
def test_app_registry_wrong_type():
|
||||||
# assert response.status == 200
|
with pytest.raises(SanicException):
|
||||||
|
Sanic.register_app(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_app_registry_name_reuse():
|
||||||
|
Sanic("test")
|
||||||
|
Sanic.test_mode = False
|
||||||
|
with pytest.raises(SanicException):
|
||||||
|
Sanic("test")
|
||||||
|
Sanic.test_mode = True
|
||||||
|
|
||||||
|
|
||||||
|
def test_app_registry_retrieval():
|
||||||
|
instance = Sanic("test")
|
||||||
|
assert Sanic.get_app("test") is instance
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_app_does_not_exist():
|
||||||
|
with pytest.raises(SanicException):
|
||||||
|
Sanic.get_app("does-not-exist")
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_app_does_not_exist_force_create():
|
||||||
|
assert isinstance(
|
||||||
|
Sanic.get_app("does-not-exist", force_create=True), Sanic
|
||||||
|
)
|
||||||
|
10
tox.ini
10
tox.ini
@ -7,18 +7,18 @@ setenv =
|
|||||||
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UJSON=1
|
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UJSON=1
|
||||||
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UVLOOP=1
|
{py36,py37,py38,py39,pyNightly}-no-ext: SANIC_NO_UVLOOP=1
|
||||||
deps =
|
deps =
|
||||||
coverage
|
coverage==5.3
|
||||||
pytest==5.2.1
|
pytest==5.2.1
|
||||||
pytest-cov
|
pytest-cov
|
||||||
pytest-sanic
|
pytest-sanic
|
||||||
pytest-sugar
|
pytest-sugar
|
||||||
pytest-benchmark
|
pytest-benchmark
|
||||||
pytest-dependency
|
pytest-dependency
|
||||||
httpcore==0.3.0
|
httpcore==0.11.*
|
||||||
httpx==0.15.4
|
httpx==0.15.4
|
||||||
chardet<=2.3.0
|
chardet==3.*
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
gunicorn
|
gunicorn==20.0.4
|
||||||
uvicorn
|
uvicorn
|
||||||
websockets>=8.1,<9.0
|
websockets>=8.1,<9.0
|
||||||
commands =
|
commands =
|
||||||
@ -76,7 +76,7 @@ deps =
|
|||||||
recommonmark>=0.5.0
|
recommonmark>=0.5.0
|
||||||
docutils
|
docutils
|
||||||
pygments
|
pygments
|
||||||
gunicorn
|
gunicorn==20.0.4
|
||||||
|
|
||||||
commands =
|
commands =
|
||||||
make docs-test
|
make docs-test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user