|
1 | 1 | from collections.abc import AsyncIterator, Iterator
|
| 2 | +from contextlib import asynccontextmanager, contextmanager |
2 | 3 | from typing import Any, Generic, Optional
|
3 | 4 | from typing_extensions import TypeVar
|
4 | 5 |
|
5 | 6 | import httpx
|
6 | 7 |
|
7 | 8 | from .compat import type_validate_json
|
| 9 | +from .exception import RequestError, RequestTimeout |
8 | 10 |
|
9 | 11 | MT = TypeVar("MT", default=Any)
|
10 | 12 | JT = TypeVar("JT", default=Any)
|
@@ -94,32 +96,62 @@ def json(self, **kwargs: Any) -> JT:
|
94 | 96 | def parsed_data(self) -> MT:
|
95 | 97 | return type_validate_json(self._data_model, self.content)
|
96 | 98 |
|
| 99 | + @contextmanager |
| 100 | + def _catch_and_close(self): |
| 101 | + try: |
| 102 | + yield |
| 103 | + except httpx.TimeoutException as e: |
| 104 | + raise RequestTimeout(e) from e |
| 105 | + except Exception as e: |
| 106 | + raise RequestError(e) from e |
| 107 | + finally: |
| 108 | + self._response.close() |
| 109 | + |
| 110 | + @asynccontextmanager |
| 111 | + async def _acatch_and_close(self): |
| 112 | + try: |
| 113 | + yield |
| 114 | + except httpx.TimeoutException as e: |
| 115 | + raise RequestTimeout(e) from e |
| 116 | + except Exception as e: |
| 117 | + raise RequestError(e) from e |
| 118 | + finally: |
| 119 | + await self._response.aclose() |
| 120 | + |
97 | 121 | def iter_bytes(self, chunk_size: Optional[int] = None) -> Iterator[bytes]:
|
98 |
| - yield from self._response.iter_bytes(chunk_size=chunk_size) |
| 122 | + with self._catch_and_close(): |
| 123 | + yield from self._response.iter_bytes(chunk_size=chunk_size) |
99 | 124 |
|
100 | 125 | def iter_text(self, chunk_size: Optional[int] = None) -> Iterator[str]:
|
101 |
| - yield from self._response.iter_text(chunk_size=chunk_size) |
| 126 | + with self._catch_and_close(): |
| 127 | + yield from self._response.iter_text(chunk_size=chunk_size) |
102 | 128 |
|
103 | 129 | def iter_lines(self) -> Iterator[str]:
|
104 |
| - yield from self._response.iter_lines() |
| 130 | + with self._catch_and_close(): |
| 131 | + yield from self._response.iter_lines() |
105 | 132 |
|
106 | 133 | def iter_raw(self, chunk_size: Optional[int] = None) -> Iterator[bytes]:
|
107 |
| - yield from self._response.iter_raw(chunk_size=chunk_size) |
| 134 | + with self._catch_and_close(): |
| 135 | + yield from self._response.iter_raw(chunk_size=chunk_size) |
108 | 136 |
|
109 | 137 | async def aiter_bytes(
|
110 | 138 | self, chunk_size: Optional[int] = None
|
111 | 139 | ) -> AsyncIterator[bytes]:
|
112 |
| - async for chunk in self._response.aiter_bytes(chunk_size=chunk_size): |
113 |
| - yield chunk |
| 140 | + async with self._acatch_and_close(): |
| 141 | + async for chunk in self._response.aiter_bytes(chunk_size=chunk_size): |
| 142 | + yield chunk |
114 | 143 |
|
115 | 144 | async def aiter_text(self, chunk_size: Optional[int] = None) -> AsyncIterator[str]:
|
116 |
| - async for chunk in self._response.aiter_text(chunk_size=chunk_size): |
117 |
| - yield chunk |
| 145 | + async with self._acatch_and_close(): |
| 146 | + async for chunk in self._response.aiter_text(chunk_size=chunk_size): |
| 147 | + yield chunk |
118 | 148 |
|
119 | 149 | async def aiter_lines(self) -> AsyncIterator[str]:
|
120 |
| - async for line in self._response.aiter_lines(): |
121 |
| - yield line |
| 150 | + async with self._acatch_and_close(): |
| 151 | + async for line in self._response.aiter_lines(): |
| 152 | + yield line |
122 | 153 |
|
123 | 154 | async def aiter_raw(self, chunk_size: Optional[int] = None) -> AsyncIterator[bytes]:
|
124 |
| - async for chunk in self._response.aiter_raw(chunk_size=chunk_size): |
125 |
| - yield chunk |
| 155 | + async with self._acatch_and_close(): |
| 156 | + async for chunk in self._response.aiter_raw(chunk_size=chunk_size): |
| 157 | + yield chunk |
0 commit comments