hack-house/.venv/lib/python3.12/site-packages/sanic_testing/reusable.py
leetcrypt bb1d662ee1 chore: rename project coven → hack-house ⛧
Rebrand the Rust client crate (coven/ → hh/, package+binary "hack-house"),
README, CLI strings, and branch (coven → hack-house). Gitea repo renamed
cmd-chat → hack-house to match. Crypto/server logic unchanged; selftest +
golden-vector test still green, binary is now `hack-house`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-30 13:29:14 -07:00

238 lines
7.3 KiB
Python

import asyncio
import typing
from functools import partial
from random import randint
from typing import Any, Dict, List, Optional, Tuple
import httpx
from sanic import Sanic
from sanic.application.state import ApplicationServerInfo
from sanic.log import logger
from sanic.request import Request
from sanic_testing.websocket import websocket_proxy
from .testing import HOST, PORT, TestingResponse
class ReusableClient:
def __init__(
self,
app: Sanic,
host=HOST,
port=PORT,
loop=None,
server_kwargs=None,
client_kwargs=None,
):
if not loop:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
server_kwargs = server_kwargs or {}
client_kwargs = client_kwargs or {}
Sanic.test_mode = True
self.app = app
self.host = host
self.port = port or randint(5000, 65000)
self._loop = loop
self.debug = False
self._server = None
self.app.state.server_info.append(
ApplicationServerInfo(
settings={
"version": "1.1",
"ssl": None,
"unix": None,
"sock": None,
"loop": None,
"host": self.host,
"port": self.port,
}
)
)
self._session = httpx.AsyncClient(verify=False, **client_kwargs)
self._server_co = self.app.create_server(
host=self.host,
debug=self.debug,
port=self.port,
return_asyncio_server=True,
**server_kwargs,
)
def __enter__(self):
self.run()
return self
def __exit__(self, *_):
self.stop()
def run(self):
self._loop._stopping = False
self.app.router.reset()
self.app.signal_router.reset()
self._run(self.app._startup())
self._run(self.app._server_event("init", "before", loop=self._loop))
self._server = self._run(self._server_co)
self._run(self.app._server_event("init", "after", loop=self._loop))
def stop(self):
self._run(
self.app._server_event("shutdown", "before", loop=self._loop)
)
if self._session:
self._run(self._session.aclose())
self._session = None
if self._server:
self._server.close()
self._run(self._server.wait_closed())
self._server = None
self._run(self.app._server_event("shutdown", "after", loop=self._loop))
def _sanic_endpoint_test(
self,
method: str = "get",
uri: str = "/",
gather_request: bool = True,
debug: bool = False,
server_kwargs: Optional[Dict[str, Any]] = None,
host: Optional[str] = None,
port: Optional[int] = None,
allow_none: bool = False,
*request_args,
**request_kwargs,
) -> Tuple[Optional[Request], Optional[TestingResponse]]:
request_data: Dict[str, Request] = {}
exceptions: List[Exception] = []
host = host or self.host
port = port or self.port
if gather_request:
_collect_request = partial(self._collect_request, request_data)
self.app.request_middleware.appendleft(_collect_request) # type: ignore # noqa
for route in self.app.router.routes:
if _collect_request not in route.extra.request_middleware:
route.extra.request_middleware.appendleft(_collect_request)
if uri.startswith(
("http:", "https:", "ftp:", "ftps://", "//", "ws:", "wss:")
):
url = uri
else:
uri = uri if uri.startswith("/") else f"/{uri}"
scheme = "ws" if method == "websocket" else "http"
url = f"{scheme}://{host}:{port}{uri}"
if exceptions:
raise ValueError(f"Exception during request: {exceptions}")
response = self._run(
self._local_request(method, url, *request_args, **request_kwargs)
)
try:
self.app.request_middleware.remove(_collect_request) # type: ignore # noqa
except BaseException: # noqa
pass
try:
request = request_data.get("request") if gather_request else None
if response is None:
if not allow_none:
raise ValueError(
"No response returned to Sanic Test Client."
)
return request, response
except BaseException: # noqa
if not allow_none:
raise ValueError(
"Request and response object expected, "
f"got ({request}, {response})"
)
return None, None
async def _local_request(self, method, url, *args, **kwargs):
raw_cookies = kwargs.pop("raw_cookies", None)
if method == "websocket":
return await websocket_proxy(url, *args, **kwargs)
else:
session = self._session
try:
if method == "request":
args = tuple([url] + list(args))
url = kwargs.pop("http_method", "GET").upper()
response = await getattr(session, method.lower())(
url, *args, **kwargs
)
except httpx.HTTPError as e:
if hasattr(e, "response"):
response = getattr(e, "response")
else:
logger.error(
f"{method.upper()} {url} received no response!",
exc_info=True,
)
return None
response.__class__ = TestingResponse
if raw_cookies:
response.raw_cookies = {}
for cookie in response.cookies.jar:
response.raw_cookies[cookie.name] = cookie
return response
def _run(self, coro):
if not self._loop:
raise RuntimeError("Test client has no loop")
return self._loop.run_until_complete(coro)
@staticmethod
def _collect_request(data, request):
data["request"] = request
def request(self, *args, **kwargs):
return self._sanic_endpoint_test("request", *args, **kwargs)
def get(self, *args, **kwargs):
return self._sanic_endpoint_test("get", *args, **kwargs)
def post(self, *args, **kwargs):
return self._sanic_endpoint_test("post", *args, **kwargs)
def put(self, *args, **kwargs):
return self._sanic_endpoint_test("put", *args, **kwargs)
def delete(self, *args, **kwargs):
return self._sanic_endpoint_test("delete", *args, **kwargs)
def patch(self, *args, **kwargs):
return self._sanic_endpoint_test("patch", *args, **kwargs)
def options(self, *args, **kwargs):
return self._sanic_endpoint_test("options", *args, **kwargs)
def head(self, *args, **kwargs):
return self._sanic_endpoint_test("head", *args, **kwargs)
def websocket(
self,
*args,
mimic: typing.Optional[
typing.Callable[..., typing.Coroutine[None, None, typing.Any]]
] = None,
**kwargs,
):
kwargs["mimic"] = mimic
return self._sanic_endpoint_test("websocket", *args, **kwargs)