Merge branch 'master' into master
This commit is contained in:
commit
65a7060d3b
|
@ -58,6 +58,8 @@ Sanic | Build fast. Run fast.
|
||||||
|
|
||||||
Sanic is a **Python 3.6+** web server and web framework that's written to go fast. It allows the usage of the ``async/await`` syntax added in Python 3.5, which makes your code non-blocking and speedy.
|
Sanic is a **Python 3.6+** web server and web framework that's written to go fast. It allows the usage of the ``async/await`` syntax added in Python 3.5, which makes your code non-blocking and speedy.
|
||||||
|
|
||||||
|
Sanic is also ASGI compliant, so you can deploy it with an `alternative ASGI webserver <https://sanic.readthedocs.io/en/latest/sanic/deploying.html#running-via-asgi>`_.
|
||||||
|
|
||||||
`Source code on GitHub <https://github.com/huge-success/sanic/>`_ | `Help and discussion board <https://community.sanicframework.org/>`_.
|
`Source code on GitHub <https://github.com/huge-success/sanic/>`_ | `Help and discussion board <https://community.sanicframework.org/>`_.
|
||||||
|
|
||||||
The project is maintained by the community, for the community. **Contributions are welcome!**
|
The project is maintained by the community, for the community. **Contributions are welcome!**
|
||||||
|
@ -104,7 +106,7 @@ Hello World Example
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=8000)
|
app.run(host='0.0.0.0', port=8000)
|
||||||
|
|
||||||
Sanic can now be easily run using ``python3 hello.py``.
|
Sanic can now be easily run using ``sanic hello.app``.
|
||||||
|
|
||||||
.. code::
|
.. code::
|
||||||
|
|
||||||
|
|
|
@ -418,12 +418,13 @@ class HttpProtocol(asyncio.Protocol):
|
||||||
async def stream_append(self):
|
async def stream_append(self):
|
||||||
while self._body_chunks:
|
while self._body_chunks:
|
||||||
body = self._body_chunks.popleft()
|
body = self._body_chunks.popleft()
|
||||||
if self.request.stream.is_full():
|
if self.request:
|
||||||
self.transport.pause_reading()
|
if self.request.stream.is_full():
|
||||||
await self.request.stream.put(body)
|
self.transport.pause_reading()
|
||||||
self.transport.resume_reading()
|
await self.request.stream.put(body)
|
||||||
else:
|
self.transport.resume_reading()
|
||||||
await self.request.stream.put(body)
|
else:
|
||||||
|
await self.request.stream.put(body)
|
||||||
|
|
||||||
def on_message_complete(self):
|
def on_message_complete(self):
|
||||||
# Entire request (headers and whole body) is received.
|
# Entire request (headers and whole body) is received.
|
||||||
|
|
|
@ -11,6 +11,8 @@ from sanic.response import text
|
||||||
|
|
||||||
|
|
||||||
ASGI_HOST = "mockserver"
|
ASGI_HOST = "mockserver"
|
||||||
|
ASGI_PORT = 1234
|
||||||
|
ASGI_BASE_URL = f"http://{ASGI_HOST}:{ASGI_PORT}"
|
||||||
HOST = "127.0.0.1"
|
HOST = "127.0.0.1"
|
||||||
PORT = None
|
PORT = None
|
||||||
|
|
||||||
|
@ -195,24 +197,19 @@ async def app_call_with_return(self, scope, receive, send):
|
||||||
return await asgi_app()
|
return await asgi_app()
|
||||||
|
|
||||||
|
|
||||||
class SanicASGIDispatch(httpx.ASGIDispatch):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class SanicASGITestClient(httpx.AsyncClient):
|
class SanicASGITestClient(httpx.AsyncClient):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
app,
|
app,
|
||||||
base_url: str = f"http://{ASGI_HOST}",
|
base_url: str = ASGI_BASE_URL,
|
||||||
suppress_exceptions: bool = False,
|
suppress_exceptions: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
app.__class__.__call__ = app_call_with_return
|
app.__class__.__call__ = app_call_with_return
|
||||||
app.asgi = True
|
app.asgi = True
|
||||||
|
|
||||||
self.app = app
|
self.app = app
|
||||||
|
transport = httpx.ASGITransport(app=app, client=(ASGI_HOST, ASGI_PORT))
|
||||||
dispatch = SanicASGIDispatch(app=app, client=(ASGI_HOST, PORT or 0))
|
super().__init__(transport=transport, base_url=base_url)
|
||||||
super().__init__(dispatch=dispatch, base_url=base_url)
|
|
||||||
|
|
||||||
self.last_request = None
|
self.last_request = None
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -81,7 +81,7 @@ requirements = [
|
||||||
"aiofiles>=0.3.0",
|
"aiofiles>=0.3.0",
|
||||||
"websockets>=8.1,<9.0",
|
"websockets>=8.1,<9.0",
|
||||||
"multidict>=4.0,<5.0",
|
"multidict>=4.0,<5.0",
|
||||||
"httpx==0.11.1",
|
"httpx==0.15.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
tests_require = [
|
tests_require = [
|
||||||
|
|
|
@ -3,6 +3,7 @@ import asyncio
|
||||||
from asyncio import sleep as aio_sleep
|
from asyncio import sleep as aio_sleep
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
|
|
||||||
|
import httpcore
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from sanic import Sanic, server
|
from sanic import Sanic, server
|
||||||
|
@ -12,67 +13,26 @@ from sanic.testing import HOST, SanicTestClient
|
||||||
|
|
||||||
CONFIG_FOR_TESTS = {"KEEP_ALIVE_TIMEOUT": 2, "KEEP_ALIVE": True}
|
CONFIG_FOR_TESTS = {"KEEP_ALIVE_TIMEOUT": 2, "KEEP_ALIVE": True}
|
||||||
|
|
||||||
old_conn = None
|
|
||||||
PORT = 42101 # test_keep_alive_timeout_reuse doesn't work with random port
|
PORT = 42101 # test_keep_alive_timeout_reuse doesn't work with random port
|
||||||
|
|
||||||
|
from httpcore._async.base import ConnectionState
|
||||||
|
from httpcore._async.connection import AsyncHTTPConnection
|
||||||
|
from httpcore._types import Origin
|
||||||
|
|
||||||
class ReusableSanicConnectionPool(
|
|
||||||
httpx.dispatch.connection_pool.ConnectionPool
|
|
||||||
):
|
|
||||||
@property
|
|
||||||
def cert(self):
|
|
||||||
return self.ssl.cert
|
|
||||||
|
|
||||||
@property
|
class ReusableSanicConnectionPool(httpcore.AsyncConnectionPool):
|
||||||
def verify(self):
|
last_reused_connection = None
|
||||||
return self.ssl.verify
|
|
||||||
|
|
||||||
@property
|
async def _get_connection_from_pool(self, *args, **kwargs):
|
||||||
def trust_env(self):
|
conn = await super()._get_connection_from_pool(*args, **kwargs)
|
||||||
return self.ssl.trust_env
|
self.__class__.last_reused_connection = conn
|
||||||
|
return conn
|
||||||
@property
|
|
||||||
def http2(self):
|
|
||||||
return self.ssl.http2
|
|
||||||
|
|
||||||
async def acquire_connection(self, origin, timeout):
|
|
||||||
global old_conn
|
|
||||||
connection = self.pop_connection(origin)
|
|
||||||
|
|
||||||
if connection is None:
|
|
||||||
pool_timeout = None if timeout is None else timeout.pool_timeout
|
|
||||||
|
|
||||||
await self.max_connections.acquire(timeout=pool_timeout)
|
|
||||||
ssl_config = httpx.config.SSLConfig(
|
|
||||||
cert=self.cert,
|
|
||||||
verify=self.verify,
|
|
||||||
trust_env=self.trust_env,
|
|
||||||
http2=self.http2,
|
|
||||||
)
|
|
||||||
connection = httpx.dispatch.connection.HTTPConnection(
|
|
||||||
origin,
|
|
||||||
ssl=ssl_config,
|
|
||||||
backend=self.backend,
|
|
||||||
release_func=self.release_connection,
|
|
||||||
uds=self.uds,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.active_connections.add(connection)
|
|
||||||
|
|
||||||
if old_conn is not None:
|
|
||||||
if old_conn != connection:
|
|
||||||
raise RuntimeError(
|
|
||||||
"We got a new connection, wanted the same one!"
|
|
||||||
)
|
|
||||||
old_conn = connection
|
|
||||||
|
|
||||||
return connection
|
|
||||||
|
|
||||||
|
|
||||||
class ResusableSanicSession(httpx.AsyncClient):
|
class ResusableSanicSession(httpx.AsyncClient):
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
dispatch = ReusableSanicConnectionPool()
|
transport = ReusableSanicConnectionPool()
|
||||||
super().__init__(dispatch=dispatch, *args, **kwargs)
|
super().__init__(transport=transport, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class ReuseableSanicTestClient(SanicTestClient):
|
class ReuseableSanicTestClient(SanicTestClient):
|
||||||
|
@ -258,6 +218,7 @@ def test_keep_alive_timeout_reuse():
|
||||||
request, response = client.get("/1")
|
request, response = client.get("/1")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.text == "OK"
|
assert response.text == "OK"
|
||||||
|
assert ReusableSanicConnectionPool.last_reused_connection
|
||||||
finally:
|
finally:
|
||||||
client.kill_server()
|
client.kill_server()
|
||||||
|
|
||||||
|
@ -270,20 +231,15 @@ def test_keep_alive_client_timeout():
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
client = ReuseableSanicTestClient(keep_alive_app_client_timeout, loop)
|
client = ReuseableSanicTestClient(keep_alive_app_client_timeout, loop)
|
||||||
headers = {"Connection": "keep-alive"}
|
headers = {"Connection": "keep-alive"}
|
||||||
try:
|
request, response = client.get(
|
||||||
request, response = client.get(
|
"/1", headers=headers, request_keepalive=1
|
||||||
"/1", headers=headers, request_keepalive=1
|
)
|
||||||
)
|
assert response.status == 200
|
||||||
assert response.status == 200
|
assert response.text == "OK"
|
||||||
assert response.text == "OK"
|
loop.run_until_complete(aio_sleep(2))
|
||||||
loop.run_until_complete(aio_sleep(2))
|
exception = None
|
||||||
exception = None
|
request, response = client.get("/1", request_keepalive=1)
|
||||||
request, response = client.get("/1", request_keepalive=1)
|
assert ReusableSanicConnectionPool.last_reused_connection is None
|
||||||
except ValueError as e:
|
|
||||||
exception = e
|
|
||||||
assert exception is not None
|
|
||||||
assert isinstance(exception, ValueError)
|
|
||||||
assert "got a new connection" in exception.args[0]
|
|
||||||
finally:
|
finally:
|
||||||
client.kill_server()
|
client.kill_server()
|
||||||
|
|
||||||
|
@ -298,22 +254,14 @@ def test_keep_alive_server_timeout():
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
client = ReuseableSanicTestClient(keep_alive_app_server_timeout, loop)
|
client = ReuseableSanicTestClient(keep_alive_app_server_timeout, loop)
|
||||||
headers = {"Connection": "keep-alive"}
|
headers = {"Connection": "keep-alive"}
|
||||||
try:
|
request, response = client.get(
|
||||||
request, response = client.get(
|
"/1", headers=headers, request_keepalive=60
|
||||||
"/1", headers=headers, request_keepalive=60
|
|
||||||
)
|
|
||||||
assert response.status == 200
|
|
||||||
assert response.text == "OK"
|
|
||||||
loop.run_until_complete(aio_sleep(3))
|
|
||||||
exception = None
|
|
||||||
request, response = client.get("/1", request_keepalive=60)
|
|
||||||
except ValueError as e:
|
|
||||||
exception = e
|
|
||||||
assert exception is not None
|
|
||||||
assert isinstance(exception, ValueError)
|
|
||||||
assert (
|
|
||||||
"Connection reset" in exception.args[0]
|
|
||||||
or "got a new connection" in exception.args[0]
|
|
||||||
)
|
)
|
||||||
|
assert response.status == 200
|
||||||
|
assert response.text == "OK"
|
||||||
|
loop.run_until_complete(aio_sleep(3))
|
||||||
|
exception = None
|
||||||
|
request, response = client.get("/1", request_keepalive=60)
|
||||||
|
assert ReusableSanicConnectionPool.last_reused_connection is None
|
||||||
finally:
|
finally:
|
||||||
client.kill_server()
|
client.kill_server()
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from sanic.blueprints import Blueprint
|
from sanic.blueprints import Blueprint
|
||||||
from sanic.exceptions import HeaderExpectationFailed
|
from sanic.exceptions import HeaderExpectationFailed
|
||||||
from sanic.request import StreamBuffer
|
from sanic.request import StreamBuffer
|
||||||
from sanic.response import json, stream, text
|
from sanic.response import json, stream, text
|
||||||
|
from sanic.server import HttpProtocol
|
||||||
from sanic.views import CompositionView, HTTPMethodView
|
from sanic.views import CompositionView, HTTPMethodView
|
||||||
from sanic.views import stream as stream_decorator
|
from sanic.views import stream as stream_decorator
|
||||||
|
|
||||||
|
@ -337,6 +340,22 @@ def test_request_stream_handle_exception(app):
|
||||||
assert "Method GET not allowed for URL /post/random_id" in response.text
|
assert "Method GET not allowed for URL /post/random_id" in response.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_request_stream_unread(app):
|
||||||
|
"""ensure no error is raised when leaving unread bytes in byte-buffer"""
|
||||||
|
|
||||||
|
err = None
|
||||||
|
protocol = HttpProtocol(loop=asyncio.get_event_loop(), app=app)
|
||||||
|
try:
|
||||||
|
protocol.request = None
|
||||||
|
protocol._body_chunks.append("this is a test")
|
||||||
|
await protocol.stream_append()
|
||||||
|
except AttributeError as e:
|
||||||
|
err = e
|
||||||
|
|
||||||
|
assert err is None and not protocol._body_chunks
|
||||||
|
|
||||||
|
|
||||||
def test_request_stream_blueprint(app):
|
def test_request_stream_blueprint(app):
|
||||||
"""for self.is_request_stream = True"""
|
"""for self.is_request_stream = True"""
|
||||||
bp = Blueprint("test_blueprint_request_stream_blueprint")
|
bp = Blueprint("test_blueprint_request_stream_blueprint")
|
||||||
|
|
|
@ -1,64 +1,54 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
import httpcore
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
|
from httpcore._async.base import (
|
||||||
|
AsyncByteStream,
|
||||||
|
AsyncHTTPTransport,
|
||||||
|
ConnectionState,
|
||||||
|
NewConnectionRequired,
|
||||||
|
)
|
||||||
|
from httpcore._async.connection import AsyncHTTPConnection
|
||||||
|
from httpcore._async.connection_pool import ResponseByteStream
|
||||||
|
from httpcore._exceptions import LocalProtocolError, UnsupportedProtocol
|
||||||
|
from httpcore._types import TimeoutDict
|
||||||
|
from httpcore._utils import url_to_origin
|
||||||
|
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
from sanic.response import text
|
from sanic.response import text
|
||||||
from sanic.testing import SanicTestClient
|
from sanic.testing import SanicTestClient
|
||||||
|
|
||||||
|
|
||||||
class DelayableHTTPConnection(httpx.dispatch.connection.HTTPConnection):
|
class DelayableHTTPConnection(httpcore._async.connection.AsyncHTTPConnection):
|
||||||
def __init__(self, *args, **kwargs):
|
async def arequest(self, *args, **kwargs):
|
||||||
self._request_delay = None
|
await asyncio.sleep(2)
|
||||||
if "request_delay" in kwargs:
|
return await super().arequest(*args, **kwargs)
|
||||||
self._request_delay = kwargs.pop("request_delay")
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
async def send(self, request, timeout=None):
|
|
||||||
|
|
||||||
if self.connection is None:
|
|
||||||
self.connection = await self.connect(timeout=timeout)
|
|
||||||
|
|
||||||
|
async def _open_socket(self, *args, **kwargs):
|
||||||
|
retval = await super()._open_socket(*args, **kwargs)
|
||||||
if self._request_delay:
|
if self._request_delay:
|
||||||
await asyncio.sleep(self._request_delay)
|
await asyncio.sleep(self._request_delay)
|
||||||
|
return retval
|
||||||
response = await self.connection.send(request, timeout=timeout)
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
class DelayableSanicConnectionPool(
|
class DelayableSanicConnectionPool(httpcore.AsyncConnectionPool):
|
||||||
httpx.dispatch.connection_pool.ConnectionPool
|
|
||||||
):
|
|
||||||
def __init__(self, request_delay=None, *args, **kwargs):
|
def __init__(self, request_delay=None, *args, **kwargs):
|
||||||
self._request_delay = request_delay
|
self._request_delay = request_delay
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
async def acquire_connection(self, origin, timeout=None):
|
async def _add_to_pool(self, connection, timeout):
|
||||||
connection = self.pop_connection(origin)
|
connection.__class__ = DelayableHTTPConnection
|
||||||
|
connection._request_delay = self._request_delay
|
||||||
if connection is None:
|
await super()._add_to_pool(connection, timeout)
|
||||||
pool_timeout = None if timeout is None else timeout.pool_timeout
|
|
||||||
|
|
||||||
await self.max_connections.acquire(timeout=pool_timeout)
|
|
||||||
connection = DelayableHTTPConnection(
|
|
||||||
origin,
|
|
||||||
ssl=self.ssl,
|
|
||||||
backend=self.backend,
|
|
||||||
release_func=self.release_connection,
|
|
||||||
uds=self.uds,
|
|
||||||
request_delay=self._request_delay,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.active_connections.add(connection)
|
|
||||||
|
|
||||||
return connection
|
|
||||||
|
|
||||||
|
|
||||||
class DelayableSanicSession(httpx.AsyncClient):
|
class DelayableSanicSession(httpx.AsyncClient):
|
||||||
def __init__(self, request_delay=None, *args, **kwargs) -> None:
|
def __init__(self, request_delay=None, *args, **kwargs) -> None:
|
||||||
dispatch = DelayableSanicConnectionPool(request_delay=request_delay)
|
transport = DelayableSanicConnectionPool(request_delay=request_delay)
|
||||||
super().__init__(dispatch=dispatch, *args, **kwargs)
|
super().__init__(transport=transport, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DelayableSanicTestClient(SanicTestClient):
|
class DelayableSanicTestClient(SanicTestClient):
|
||||||
|
|
|
@ -12,7 +12,14 @@ from sanic import Blueprint, Sanic
|
||||||
from sanic.exceptions import ServerError
|
from sanic.exceptions import ServerError
|
||||||
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE, Request, RequestParameters
|
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE, Request, RequestParameters
|
||||||
from sanic.response import html, json, text
|
from sanic.response import html, json, text
|
||||||
from sanic.testing import ASGI_HOST, HOST, PORT, SanicTestClient
|
from sanic.testing import (
|
||||||
|
ASGI_BASE_URL,
|
||||||
|
ASGI_HOST,
|
||||||
|
ASGI_PORT,
|
||||||
|
HOST,
|
||||||
|
PORT,
|
||||||
|
SanicTestClient,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------ #
|
# ------------------------------------------------------------ #
|
||||||
|
@ -59,7 +66,10 @@ async def test_ip_asgi(app):
|
||||||
|
|
||||||
request, response = await app.asgi_client.get("/")
|
request, response = await app.asgi_client.get("/")
|
||||||
|
|
||||||
assert response.text == "http://mockserver/"
|
if response.text.endswith("/") and not ASGI_BASE_URL.endswith("/"):
|
||||||
|
response.text[:-1] == ASGI_BASE_URL
|
||||||
|
else:
|
||||||
|
assert response.text == ASGI_BASE_URL
|
||||||
|
|
||||||
|
|
||||||
def test_text(app):
|
def test_text(app):
|
||||||
|
@ -573,7 +583,7 @@ async def test_standard_forwarded_asgi(app):
|
||||||
assert response.json() == {"for": "127.0.0.2", "proto": "ws"}
|
assert response.json() == {"for": "127.0.0.2", "proto": "ws"}
|
||||||
assert request.remote_addr == "127.0.0.2"
|
assert request.remote_addr == "127.0.0.2"
|
||||||
assert request.scheme == "ws"
|
assert request.scheme == "ws"
|
||||||
assert request.server_port == 80
|
assert request.server_port == ASGI_PORT
|
||||||
|
|
||||||
app.config.FORWARDED_SECRET = "mySecret"
|
app.config.FORWARDED_SECRET = "mySecret"
|
||||||
request, response = await app.asgi_client.get("/", headers=headers)
|
request, response = await app.asgi_client.get("/", headers=headers)
|
||||||
|
@ -1044,9 +1054,9 @@ def test_url_attributes_no_ssl(app, path, query, expected_url):
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"path,query,expected_url",
|
"path,query,expected_url",
|
||||||
[
|
[
|
||||||
("/foo", "", "http://{}/foo"),
|
("/foo", "", "{}/foo"),
|
||||||
("/bar/baz", "", "http://{}/bar/baz"),
|
("/bar/baz", "", "{}/bar/baz"),
|
||||||
("/moo/boo", "arg1=val1", "http://{}/moo/boo?arg1=val1"),
|
("/moo/boo", "arg1=val1", "{}/moo/boo?arg1=val1"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -1057,7 +1067,7 @@ async def test_url_attributes_no_ssl_asgi(app, path, query, expected_url):
|
||||||
app.add_route(handler, path)
|
app.add_route(handler, path)
|
||||||
|
|
||||||
request, response = await app.asgi_client.get(path + f"?{query}")
|
request, response = await app.asgi_client.get(path + f"?{query}")
|
||||||
assert request.url == expected_url.format(ASGI_HOST)
|
assert request.url == expected_url.format(ASGI_BASE_URL)
|
||||||
|
|
||||||
parsed = urlparse(request.url)
|
parsed = urlparse(request.url)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
import httpcore
|
||||||
import httpx
|
import httpx
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -139,8 +140,9 @@ def test_unix_connection():
|
||||||
|
|
||||||
@app.listener("after_server_start")
|
@app.listener("after_server_start")
|
||||||
async def client(app, loop):
|
async def client(app, loop):
|
||||||
|
transport = httpcore.AsyncConnectionPool(uds=SOCKPATH)
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(uds=SOCKPATH) as client:
|
async with httpx.AsyncClient(transport=transport) as client:
|
||||||
r = await client.get("http://myhost.invalid/")
|
r = await client.get("http://myhost.invalid/")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.text == os.path.abspath(SOCKPATH)
|
assert r.text == os.path.abspath(SOCKPATH)
|
||||||
|
@ -179,8 +181,9 @@ async def test_zero_downtime():
|
||||||
from time import monotonic as current_time
|
from time import monotonic as current_time
|
||||||
|
|
||||||
async def client():
|
async def client():
|
||||||
|
transport = httpcore.AsyncConnectionPool(uds=SOCKPATH)
|
||||||
for _ in range(40):
|
for _ in range(40):
|
||||||
async with httpx.AsyncClient(uds=SOCKPATH) as client:
|
async with httpx.AsyncClient(transport=transport) as client:
|
||||||
r = await client.get("http://localhost/sleep/0.1")
|
r = await client.get("http://localhost/sleep/0.1")
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.text == f"Slept 0.1 seconds.\n"
|
assert r.text == f"Slept 0.1 seconds.\n"
|
||||||
|
|
5
tox.ini
5
tox.ini
|
@ -13,7 +13,7 @@ deps =
|
||||||
pytest-sanic
|
pytest-sanic
|
||||||
pytest-sugar
|
pytest-sugar
|
||||||
httpcore==0.3.0
|
httpcore==0.3.0
|
||||||
httpx==0.11.1
|
httpx==0.15.4
|
||||||
chardet<=2.3.0
|
chardet<=2.3.0
|
||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
gunicorn
|
gunicorn
|
||||||
|
@ -55,6 +55,9 @@ commands =
|
||||||
[pytest]
|
[pytest]
|
||||||
filterwarnings =
|
filterwarnings =
|
||||||
ignore:.*async with lock.* instead:DeprecationWarning
|
ignore:.*async with lock.* instead:DeprecationWarning
|
||||||
|
addopts = --strict-markers
|
||||||
|
markers =
|
||||||
|
asyncio
|
||||||
|
|
||||||
[testenv:security]
|
[testenv:security]
|
||||||
deps =
|
deps =
|
||||||
|
|
Loading…
Reference in New Issue
Block a user