Merge branch 'master' into 20.12LTS
This commit is contained in:
commit
b66fb6f9e8
|
@ -17,11 +17,11 @@ environment:
|
|||
PYTHON_VERSION: "3.8.x"
|
||||
PYTHON_ARCH: "64"
|
||||
|
||||
- TOXENV: py39-no-ext
|
||||
PYTHON: "C:\\Python39-x64\\python"
|
||||
PYTHONPATH: "C:\\Python39-x64"
|
||||
PYTHON_VERSION: "3.9.x"
|
||||
PYTHON_ARCH: "64"
|
||||
# - TOXENV: py39-no-ext
|
||||
# PYTHON: "C:\\Python39-x64\\python"
|
||||
# PYTHONPATH: "C:\\Python39-x64"
|
||||
# PYTHON_VERSION: "3.9.x"
|
||||
# PYTHON_ARCH: "64"
|
||||
|
||||
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!*.
|
||||
|
||||
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):
|
||||
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
|
||||
|
||||
|
|
|
@ -58,10 +58,6 @@ More information about
|
|||
the available arguments to `httpx` can be found
|
||||
[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
|
||||
@pytest.mark.asyncio
|
||||
async def test_index_returns_200():
|
||||
|
|
45
sanic/app.py
45
sanic/app.py
|
@ -2,12 +2,11 @@ import logging
|
|||
import logging.config
|
||||
import os
|
||||
import re
|
||||
import warnings
|
||||
|
||||
from asyncio import CancelledError, Protocol, ensure_future, get_event_loop
|
||||
from collections import defaultdict, deque
|
||||
from functools import partial
|
||||
from inspect import getmodulename, isawaitable, signature, stack
|
||||
from inspect import isawaitable, signature
|
||||
from socket import socket
|
||||
from ssl import Purpose, SSLContext, create_default_context
|
||||
from traceback import format_exc
|
||||
|
@ -38,6 +37,9 @@ from sanic.websocket import ConnectionClosed, WebSocketProtocol
|
|||
|
||||
|
||||
class Sanic:
|
||||
_app_registry: Dict[str, "Sanic"] = {}
|
||||
test_mode = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name=None,
|
||||
|
@ -52,15 +54,10 @@ class Sanic:
|
|||
|
||||
# Get name from previous stack frame
|
||||
if name is None:
|
||||
warnings.warn(
|
||||
"Sanic(name=None) is deprecated and None value support "
|
||||
"for `name` will be removed in the next release. "
|
||||
raise SanicException(
|
||||
"Sanic instance cannot be unnamed. "
|
||||
"Please use Sanic(name='your_application_name') instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
frame_records = stack()[1]
|
||||
name = getmodulename(frame_records[1])
|
||||
|
||||
# logging
|
||||
if configure_logging:
|
||||
|
@ -90,7 +87,8 @@ class Sanic:
|
|||
self.named_response_middleware = {}
|
||||
# Register alternative method names
|
||||
self.go_fast = self.run
|
||||
self.test_mode = False
|
||||
|
||||
self.__class__.register_app(self)
|
||||
|
||||
@property
|
||||
def loop(self):
|
||||
|
@ -1401,9 +1399,36 @@ class Sanic:
|
|||
# -------------------------------------------------------------------- #
|
||||
# Configuration
|
||||
# -------------------------------------------------------------------- #
|
||||
|
||||
def update_config(self, config: Union[bytes, str, dict, Any]):
|
||||
"""Update app.config.
|
||||
|
||||
Please refer to config.py::Config.update_config for documentation."""
|
||||
|
||||
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,
|
||||
)
|
||||
from sanic.handlers import ContentRangeHandler
|
||||
from sanic.log import error_logger
|
||||
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)
|
||||
file_path = path.abspath(unquote(file_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(
|
||||
"File not found", path=file_or_directory, relative_url=file_uri
|
||||
)
|
||||
|
@ -94,6 +99,10 @@ async def _static_request_handler(
|
|||
except ContentRangeError:
|
||||
raise
|
||||
except Exception:
|
||||
error_logger.exception(
|
||||
f"File not found: path={file_or_directory}, "
|
||||
f"relative_url={file_uri}"
|
||||
)
|
||||
raise FileNotFound(
|
||||
"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 re
|
||||
import sys
|
||||
|
||||
from distutils.util import strtobool
|
||||
|
||||
from setuptools import setup
|
||||
|
@ -24,6 +25,7 @@ class PyTest(TestCommand):
|
|||
|
||||
def run_tests(self):
|
||||
import shlex
|
||||
|
||||
import pytest
|
||||
|
||||
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:
|
||||
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:
|
||||
raise RuntimeError("Unable to determine version.")
|
||||
|
||||
|
@ -72,7 +76,9 @@ setup_kwargs = {
|
|||
"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
|
||||
uvloop = "uvloop>=0.5.3" + env_dependency
|
||||
|
||||
|
@ -89,9 +95,9 @@ requirements = [
|
|||
tests_require = [
|
||||
"pytest==5.2.1",
|
||||
"multidict>=5.0,<6.0",
|
||||
"gunicorn",
|
||||
"gunicorn==20.0.4",
|
||||
"pytest-cov",
|
||||
"httpcore==0.3.0",
|
||||
"httpcore==0.11.*",
|
||||
"beautifulsoup4",
|
||||
uvloop,
|
||||
ujson,
|
||||
|
|
|
@ -11,6 +11,7 @@ from sanic.router import RouteExists, Router
|
|||
|
||||
|
||||
random.seed("Pack my box with five dozen liquor jugs.")
|
||||
Sanic.test_mode = True
|
||||
|
||||
if sys.platform in ["win32", "cygwin"]:
|
||||
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():
|
||||
with pytest.deprecated_call():
|
||||
with pytest.raises(SanicException):
|
||||
Sanic()
|
||||
|
||||
|
||||
|
@ -274,14 +274,35 @@ def test_app_has_test_mode_sync():
|
|||
assert response.status == 200
|
||||
|
||||
|
||||
# @pytest.mark.asyncio
|
||||
# async def test_app_has_test_mode_async():
|
||||
# app = Sanic("test")
|
||||
def test_app_registry():
|
||||
instance = 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("/")
|
||||
# assert response.status == 200
|
||||
def test_app_registry_wrong_type():
|
||||
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_UVLOOP=1
|
||||
deps =
|
||||
coverage
|
||||
coverage==5.3
|
||||
pytest==5.2.1
|
||||
pytest-cov
|
||||
pytest-sanic
|
||||
pytest-sugar
|
||||
pytest-benchmark
|
||||
pytest-dependency
|
||||
httpcore==0.3.0
|
||||
httpcore==0.11.*
|
||||
httpx==0.15.4
|
||||
chardet<=2.3.0
|
||||
chardet==3.*
|
||||
beautifulsoup4
|
||||
gunicorn
|
||||
gunicorn==20.0.4
|
||||
uvicorn
|
||||
websockets>=8.1,<9.0
|
||||
commands =
|
||||
|
@ -76,7 +76,7 @@ deps =
|
|||
recommonmark>=0.5.0
|
||||
docutils
|
||||
pygments
|
||||
gunicorn
|
||||
gunicorn==20.0.4
|
||||
|
||||
commands =
|
||||
make docs-test
|
||||
|
|
Loading…
Reference in New Issue
Block a user