1 """Helper to track the current http request."""
3 from __future__
import annotations
6 from collections.abc
import Awaitable, Callable
7 from contextvars
import ContextVar
8 from http
import HTTPStatus
10 from typing
import Any, Final
12 from aiohttp
import web
13 from aiohttp.typedefs
import LooseHeaders
14 from aiohttp.web
import AppKey, Request
15 from aiohttp.web_exceptions
import (
17 HTTPInternalServerError,
20 from aiohttp.web_urldispatcher
import AbstractResource, AbstractRoute
21 import voluptuous
as vol
23 from homeassistant
import exceptions
28 from .json
import find_paths_unserializable_data, json_bytes, json_dumps
30 _LOGGER = logging.getLogger(__name__)
33 type AllowCorsType = Callable[[AbstractRoute | AbstractResource],
None]
34 KEY_AUTHENTICATED: Final =
"ha_authenticated"
35 KEY_ALLOW_ALL_CORS = AppKey[AllowCorsType](
"allow_all_cors")
36 KEY_ALLOW_CONFIGURED_CORS = AppKey[AllowCorsType](
"allow_configured_cors")
37 KEY_HASS: AppKey[HomeAssistant] = AppKey(
"hass")
39 current_request: ContextVar[Request |
None] = ContextVar(
40 "current_request", default=
None
45 hass: HomeAssistant, view: HomeAssistantView, handler: Callable
46 ) -> Callable[[web.Request], Awaitable[web.StreamResponse]]:
47 """Wrap the handler classes."""
48 is_coroutinefunction = asyncio.iscoroutinefunction(handler)
51 ),
"Handler should be a coroutine or a callback."
53 async
def handle(request: web.Request) -> web.StreamResponse:
54 """Handle incoming request."""
56 return web.Response(status=HTTPStatus.SERVICE_UNAVAILABLE)
58 authenticated = request.get(KEY_AUTHENTICATED,
False)
60 if view.requires_auth
and not authenticated:
61 raise HTTPUnauthorized
63 if _LOGGER.isEnabledFor(logging.DEBUG):
65 "Serving %s to %s (auth: %s)",
72 if is_coroutinefunction:
73 result = await handler(request, **request.match_info)
75 result = handler(request, **request.match_info)
76 except vol.Invalid
as err:
77 raise HTTPBadRequest
from err
79 raise HTTPInternalServerError
from err
81 raise HTTPUnauthorized
from err
83 if isinstance(result, web.StreamResponse):
87 status_code = HTTPStatus.OK
88 if isinstance(result, tuple):
89 result, status_code = result
91 if isinstance(result, bytes):
92 return web.Response(body=result, status=status_code)
94 if isinstance(result, str):
95 return web.Response(text=result, status=status_code)
98 return web.Response(body=b
"", status=status_code)
101 f
"Result should be None, string, bytes or StreamResponse. Got: {result}"
108 """Base view for all views."""
110 url: str |
None =
None
111 extra_urls: list[str] = []
118 """Generate a context from a request."""
119 if (user := request.get(
"hass_user"))
is None:
122 return Context(user_id=user.id)
127 status_code: HTTPStatus | int = HTTPStatus.OK,
128 headers: LooseHeaders |
None =
None,
130 """Return a JSON response."""
133 except JSON_ENCODE_EXCEPTIONS
as err:
135 "Unable to serialize to JSON. Bad data found at %s",
140 raise HTTPInternalServerError
from err
141 response = web.Response(
143 content_type=CONTENT_TYPE_JSON,
144 status=
int(status_code),
146 zlib_executor_size=32768,
148 response.enable_compression()
154 status_code: HTTPStatus | int = HTTPStatus.OK,
155 message_code: str |
None =
None,
156 headers: LooseHeaders |
None =
None,
158 """Return a JSON message response."""
159 data = {
"message": message}
160 if message_code
is not None:
161 data[
"code"] = message_code
162 return self.
jsonjson(data, status_code, headers=headers)
165 self, hass: HomeAssistant, app: web.Application, router: web.UrlDispatcher
167 """Register the view with a router."""
168 assert self.url
is not None,
"No url set for view"
169 urls = [self.url, *self.extra_urls]
170 routes: list[AbstractRoute] = []
172 for method
in (
"get",
"post",
"delete",
"put",
"patch",
"head",
"options"):
173 if not (handler := getattr(self, method,
None)):
178 routes.extend(router.add_route(method, url, handler)
for url
in urls)
182 allow_cors = app.get(KEY_ALLOW_ALL_CORS)
184 allow_cors = app.get(KEY_ALLOW_CONFIGURED_CORS)
None register(self, HomeAssistant hass, web.Application app, web.UrlDispatcher router)
web.Response json(Any result, HTTPStatus|int status_code=HTTPStatus.OK, LooseHeaders|None headers=None)
web.Response json_message(self, str message, HTTPStatus|int status_code=HTTPStatus.OK, str|None message_code=None, LooseHeaders|None headers=None)
Context context(web.Request request)
bool is_callback(Callable[..., Any] func)
Callable[[web.Request], Awaitable[web.StreamResponse]] request_handler_factory(HomeAssistant hass, HomeAssistantView view, Callable handler)
dict[str, Any] find_paths_unserializable_data(Any bad_data, *Callable[[Any], str] dump=json.dumps)
str format_unserializable_data(dict[str, Any] data)