-
Notifications
You must be signed in to change notification settings - Fork 4
Cloud backup support #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |||||
| ClientSession, | ||||||
| ClientTimeout, | ||||||
| ) | ||||||
| from multidict import MultiDict | ||||||
| from yarl import URL | ||||||
|
|
||||||
| from .const import DEFAULT_TIMEOUT, ResponseType | ||||||
|
|
@@ -27,6 +28,7 @@ | |||||
| SupervisorTimeoutError, | ||||||
| ) | ||||||
| from .models.base import Response, ResultType | ||||||
| from .utils.aiohttp import ChunkAsyncStreamIterator | ||||||
|
|
||||||
| VERSION = metadata.version(__package__) | ||||||
|
|
||||||
|
|
@@ -53,12 +55,33 @@ class _SupervisorClient: | |||||
| session: ClientSession | None = None | ||||||
| _close_session: bool = field(default=False, init=False) | ||||||
|
|
||||||
| async def _raise_on_status(self, response: ClientResponse) -> None: | ||||||
| """Raise appropriate exception on status.""" | ||||||
| if response.status >= HTTPStatus.BAD_REQUEST.value: | ||||||
| exc_type: type[SupervisorError] = SupervisorError | ||||||
| match response.status: | ||||||
| case HTTPStatus.BAD_REQUEST: | ||||||
| exc_type = SupervisorBadRequestError | ||||||
| case HTTPStatus.UNAUTHORIZED: | ||||||
| exc_type = SupervisorAuthenticationError | ||||||
| case HTTPStatus.FORBIDDEN: | ||||||
| exc_type = SupervisorForbiddenError | ||||||
| case HTTPStatus.NOT_FOUND: | ||||||
| exc_type = SupervisorNotFoundError | ||||||
| case HTTPStatus.SERVICE_UNAVAILABLE: | ||||||
| exc_type = SupervisorServiceUnavailableError | ||||||
|
|
||||||
| if is_json(response): | ||||||
| result = Response.from_json(await response.text()) | ||||||
| raise exc_type(result.message, result.job_id) | ||||||
| raise exc_type() | ||||||
|
|
||||||
| async def _request( | ||||||
| self, | ||||||
| method: HTTPMethod, | ||||||
| uri: str, | ||||||
| *, | ||||||
| params: dict[str, str] | None, | ||||||
| params: dict[str, str] | MultiDict[str, str] | None, | ||||||
| response_type: ResponseType, | ||||||
| json: dict[str, Any] | None = None, | ||||||
| data: Any = None, | ||||||
|
|
@@ -94,42 +117,28 @@ async def _request( | |||||
| self._close_session = True | ||||||
|
|
||||||
| try: | ||||||
| async with self.session.request( | ||||||
| response = await self.session.request( | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure it matters, but if want to keep the context manager we could separate the cases where we can use the context manager from the cases we don't: if response_type == ResponseType.RAW:
response = await self.session.request(
...
)
...
else:
async with self.session.request(
...
) as response:
... |
||||||
| method.value, | ||||||
| url, | ||||||
| timeout=timeout, | ||||||
| headers=headers, | ||||||
| params=params, | ||||||
| json=json, | ||||||
| data=data, | ||||||
| ) as response: | ||||||
| if response.status >= HTTPStatus.BAD_REQUEST.value: | ||||||
| exc_type: type[SupervisorError] = SupervisorError | ||||||
| match response.status: | ||||||
| case HTTPStatus.BAD_REQUEST: | ||||||
| exc_type = SupervisorBadRequestError | ||||||
| case HTTPStatus.UNAUTHORIZED: | ||||||
| exc_type = SupervisorAuthenticationError | ||||||
| case HTTPStatus.FORBIDDEN: | ||||||
| exc_type = SupervisorForbiddenError | ||||||
| case HTTPStatus.NOT_FOUND: | ||||||
| exc_type = SupervisorNotFoundError | ||||||
| case HTTPStatus.SERVICE_UNAVAILABLE: | ||||||
| exc_type = SupervisorServiceUnavailableError | ||||||
|
|
||||||
| if is_json(response): | ||||||
| result = Response.from_json(await response.text()) | ||||||
| raise exc_type(result.message, result.job_id) | ||||||
| raise exc_type() | ||||||
|
|
||||||
| match response_type: | ||||||
| case ResponseType.JSON: | ||||||
| is_json(response, raise_on_fail=True) | ||||||
| return Response.from_json(await response.text()) | ||||||
| case ResponseType.TEXT: | ||||||
| return Response(ResultType.OK, await response.text()) | ||||||
| case _: | ||||||
| return Response(ResultType.OK) | ||||||
| ) | ||||||
| await self._raise_on_status(response) | ||||||
| match response_type: | ||||||
| case ResponseType.JSON: | ||||||
| is_json(response, raise_on_fail=True) | ||||||
| return Response.from_json(await response.text()) | ||||||
| case ResponseType.TEXT: | ||||||
| return Response(ResultType.OK, await response.text()) | ||||||
| case ResponseType.STREAM: | ||||||
| return Response( | ||||||
| ResultType.OK, ChunkAsyncStreamIterator(response.content) | ||||||
| ) | ||||||
| case _: | ||||||
| return Response(ResultType.OK) | ||||||
|
|
||||||
| except (UnicodeDecodeError, ClientResponseError) as err: | ||||||
| raise SupervisorResponseError( | ||||||
|
|
@@ -146,7 +155,7 @@ async def get( | |||||
| self, | ||||||
| uri: str, | ||||||
| *, | ||||||
| params: dict[str, str] | None = None, | ||||||
| params: dict[str, str] | MultiDict[str, str] | None = None, | ||||||
|
||||||
| params: dict[str, str] | MultiDict[str, str] | None = None, | |
| params: Query = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems Query would need a newer aiohttp version, it came in with this PR in 3.11.0 I think (currently pyproject.toml specifies 3.3.0 as minimum).
Let's go with the fixed MultiDict typing for now.
agners marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
agners marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
agners marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
agners marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,4 +13,5 @@ class ResponseType(StrEnum): | |
|
|
||
| NONE = "none" | ||
| JSON = "json" | ||
| STREAM = "stream" | ||
| TEXT = "text" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Utilities used internally in library.""" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| """Utilities for interacting with aiohttp.""" | ||
|
|
||
| from typing import Self | ||
|
|
||
| from aiohttp import StreamReader | ||
|
|
||
|
|
||
| class ChunkAsyncStreamIterator: | ||
| """Async iterator for chunked streams. | ||
|
|
||
| Based on aiohttp.streams.ChunkTupleAsyncStreamIterator, but yields | ||
| bytes instead of tuple[bytes, bool]. | ||
| Borrowed from home-assistant/core. | ||
| """ | ||
|
|
||
| __slots__ = ("_stream",) | ||
|
|
||
| def __init__(self, stream: StreamReader) -> None: | ||
| """Initialize.""" | ||
| self._stream = stream | ||
|
|
||
| def __aiter__(self) -> Self: | ||
| """Iterate.""" | ||
| return self | ||
|
|
||
| async def __anext__(self) -> bytes: | ||
| """Yield next chunk.""" | ||
| rv = await self._stream.readchunk() | ||
| if rv == (b"", False): | ||
| raise StopAsyncIteration | ||
| return rv[0] |
Uh oh!
There was an error while loading. Please reload this page.