Files
homeassistant/custom_components/yandex_smart_home/http.py
Victor Alexandrovich Tsyrenschikov 373ed28445 python
2026-03-30 20:25:42 +05:00

162 lines
5.8 KiB
Python

"""The Yandex Smart Home HTTP interface."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any, Awaitable, Callable, Coroutine, TypeVar
from aiohttp.web import HTTPServiceUnavailable, Request, Response, json_response
from homeassistant.components.http import KEY_HASS, KEY_HASS_REFRESH_TOKEN_ID, HomeAssistantView
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import issue_registry as ir
from . import DOMAIN, handlers
from .const import ISSUE_ID_MISSING_INTEGRATION
from .helpers import RequestData, SmartHomePlatform
if TYPE_CHECKING:
from . import YandexSmartHome
_LOGGER = logging.getLogger(__name__)
_T = TypeVar("_T", bound="YandexSmartHomeView")
async def _log_request(request: Request) -> None:
"""Log the request."""
if body := await request.text():
_LOGGER.debug(f"Request: {request.url} ({request.method} data: {body})")
else:
_LOGGER.debug(f"Request: {request.url} ({request.method})")
@callback
def async_register_http(hass: HomeAssistant, component: YandexSmartHome) -> None:
"""Register HTTP views for Yandex Smart Home."""
hass.http.register_view(YandexSmartHomeUnauthorizedView(component))
return hass.http.register_view(YandexSmartHomeAPIView(component))
def async_http_request(
func: Callable[[_T, HomeAssistant, Request, RequestData], Awaitable[Response]]
) -> Callable[[_T, Request], Coroutine[Any, Any, Response]]:
"""Decorate an async function to handle authorized HTTP requests."""
async def decorator(self: _T, request: Request) -> Response:
"""Decorate."""
await _log_request(request)
hass: HomeAssistant = request.app[KEY_HASS]
context = self.context(request)
refresh_token = hass.auth.async_get_refresh_token(request[KEY_HASS_REFRESH_TOKEN_ID])
assert refresh_token is not None
platform = SmartHomePlatform.from_client_id(refresh_token.client_id or "")
if not platform:
_LOGGER.error(f"Request from unsupported platform, client_id: {refresh_token.client_id}")
raise HTTPServiceUnavailable()
entry_data = self._component.get_direct_connection_entry_data(platform, refresh_token.user.id)
if not entry_data and len(hass.config_entries.async_entries(DOMAIN)) == 1:
# backward compatibility
entry_data = self._component.get_direct_connection_entry_data(platform, None)
issue_id = f"{ISSUE_ID_MISSING_INTEGRATION}_{platform}_{refresh_token.user.id}"
if not entry_data:
_LOGGER.error(
f"Failed to find Yandex Smart Home integration for request "
f"from {platform} (user {refresh_token.user.name})"
)
ir.async_create_issue(
hass,
DOMAIN,
issue_id,
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key=ISSUE_ID_MISSING_INTEGRATION,
translation_placeholders={
"platform": platform,
"username": refresh_token.user.name or refresh_token.user.id,
},
)
raise HTTPServiceUnavailable()
else:
ir.async_delete_issue(hass, DOMAIN, issue_id)
data = RequestData(
entry_data=entry_data,
context=context,
platform=platform,
request_user_id=context.user_id,
request_id=request.headers.get("X-Request-Id"),
)
if entry_data.skill:
data.request_user_id = entry_data.skill.user_id
return await func(self, hass, request, data)
return decorator
class YandexSmartHomeView(HomeAssistantView):
def __init__(self, component: YandexSmartHome):
self._component = component
class YandexSmartHomeUnauthorizedView(YandexSmartHomeView):
"""View to handle Yandex Smart Home unauthorized HTTP requests."""
url = f"/api/{DOMAIN}/v1.0"
extra_urls = [
url + "/ping",
]
name = f"api:{DOMAIN}:unauthorized"
requires_auth = False
@staticmethod
async def head(request: Request) -> Response:
"""Handle Yandex Smart Home HEAD requests."""
await _log_request(request)
return Response(status=200)
@staticmethod
async def get(request: Request) -> Response:
"""Handle Yandex Smart Home GET requests."""
await _log_request(request)
return Response(text="Yandex Smart Home", status=200)
class YandexSmartHomeAPIView(YandexSmartHomeView):
"""View to handle Yandex Smart Home HTTP requests."""
url = f"/api/{DOMAIN}/v1.0"
extra_urls = [
url + "/user/unlink",
url + "/user/devices",
url + "/user/devices/query",
url + "/user/devices/action",
]
name = f"api:{DOMAIN}"
requires_auth = True
async def _async_handle_request(self, hass: HomeAssistant, request: Request, data: RequestData) -> Response:
"""Handle Yandex Smart Home requests."""
assert self.url is not None
result = await handlers.async_handle_request(
hass, data, action=request.path.replace(self.url, "", 1), payload=await request.text()
)
response = json_response(text=result.as_json())
_LOGGER.debug(f"Response: {response.text}")
return response
@async_http_request
async def post(self, hass: HomeAssistant, request: Request, data: RequestData) -> Response:
"""Handle Yandex Smart Home POST requests."""
return await self._async_handle_request(hass, request, data)
@async_http_request
async def get(self, hass: HomeAssistant, request: Request, data: RequestData) -> Response:
"""Handle Yandex Smart Home GET requests."""
return await self._async_handle_request(hass, request, data)