Skip to content

Commit 7c9d206

Browse files
authored
aiohttp migration fixes (#117)
1 parent 74b496a commit 7c9d206

File tree

4 files changed

+57
-19
lines changed

4 files changed

+57
-19
lines changed

onvif/client.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -248,12 +248,12 @@ def __init__(
248248
self.dt_diff = dt_diff
249249
self.binding_name = binding_name
250250
# Create soap client
251-
connector = TCPConnector(
251+
self._connector = TCPConnector(
252252
ssl=_NO_VERIFY_SSL_CONTEXT,
253253
keepalive_timeout=KEEPALIVE_EXPIRY,
254254
)
255-
session = ClientSession(
256-
connector=connector,
255+
self._session = ClientSession(
256+
connector=self._connector,
257257
timeout=aiohttp.ClientTimeout(
258258
total=_DEFAULT_TIMEOUT,
259259
connect=_CONNECT_TIMEOUT,
@@ -262,12 +262,12 @@ def __init__(
262262
)
263263
self.transport = (
264264
AsyncTransportProtocolErrorHandler(
265-
session=session,
265+
session=self._session,
266266
verify_ssl=False,
267267
)
268268
if no_cache
269269
else AIOHTTPTransport(
270-
session=session,
270+
session=self._session,
271271
verify_ssl=False,
272272
cache=SqliteCache(),
273273
)
@@ -316,6 +316,8 @@ async def setup(self):
316316
async def close(self):
317317
"""Close the transport."""
318318
await self.transport.aclose()
319+
await self._session.close()
320+
await self._connector.close()
319321

320322
@staticmethod
321323
@safe_func

onvif/managers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def __init__(
6262
@property
6363
def closed(self) -> bool:
6464
"""Return True if the manager is closed."""
65-
return not self._subscription or self._subscription.transport.client.is_closed
65+
return not self._subscription or self._subscription.transport.session.closed
6666

6767
async def start(self) -> None:
6868
"""Setup the manager."""

onvif/zeep_aiohttp.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from zeep.utils import get_version
1212
from zeep.wsdl.utils import etree_to_string
1313

14-
import aiohttp
1514
import httpx
1615
from aiohttp import ClientResponse, ClientSession
1716
from requests import Response
@@ -160,24 +159,21 @@ async def _post(
160159
proxy=self.proxy,
161160
timeout=self._client_timeout,
162161
)
163-
response.raise_for_status()
164162

165-
# Read the content to log it
163+
# Read the content to log it before checking status
166164
content = await response.read()
167165
_LOGGER.debug(
168166
"HTTP Response from %s (status: %d):\n%s",
169167
address,
170168
response.status,
171-
content.decode("utf-8", errors="replace"),
169+
content,
172170
)
173171

174172
# Convert to httpx Response
175173
return self._aiohttp_to_httpx_response(response, content)
176174

177175
except TimeoutError as exc:
178176
raise TimeoutError(f"Request to {address} timed out") from exc
179-
except aiohttp.ClientError as exc:
180-
raise ConnectionError(f"Error connecting to {address}: {exc}") from exc
181177

182178
async def post_xml(
183179
self, address: str, envelope: _Element, headers: dict[str, str]
@@ -239,24 +235,22 @@ async def _get(
239235
proxy=self.proxy,
240236
timeout=self._client_timeout,
241237
)
242-
response.raise_for_status()
243238

244-
# Read content
239+
# Read content and log before checking status
245240
content = await response.read()
246241

247242
_LOGGER.debug(
248-
"HTTP Response from %s (status: %d)",
243+
"HTTP Response from %s (status: %d):\n%s",
249244
address,
250245
response.status,
246+
content,
251247
)
252248

253249
# Convert directly to requests.Response
254250
return self._aiohttp_to_requests_response(response, content)
255251

256252
except TimeoutError as exc:
257253
raise TimeoutError(f"Request to {address} timed out") from exc
258-
except aiohttp.ClientError as exc:
259-
raise ConnectionError(f"Error connecting to {address}: {exc}") from exc
260254

261255
def _httpx_to_requests_response(self, response: httpx.Response) -> Response:
262256
"""Convert an httpx.Response object to a requests.Response object"""

tests/test_zeep_transport.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ async def test_post_returns_httpx_response():
3636
mock_aiohttp_response.url = "http://example.com/service"
3737
mock_aiohttp_response.charset = "utf-8"
3838
mock_aiohttp_response.cookies = {}
39-
mock_aiohttp_response.raise_for_status = Mock()
4039

4140
mock_content = b"<response>test</response>"
4241
mock_aiohttp_response.read = AsyncMock(return_value=mock_content)
@@ -201,7 +200,7 @@ async def test_connection_error_handling():
201200

202201
transport.session = mock_session
203202

204-
with pytest.raises(ConnectionError, match="Error connecting to"):
203+
with pytest.raises(aiohttp.ClientError, match="Connection failed"):
205204
await transport.get("http://example.com/wsdl")
206205

207206

@@ -869,3 +868,46 @@ async def test_cookie_jar_type():
869868
# Verify cookies are accessible in requests response
870869
assert hasattr(requests_result.cookies, "__getitem__")
871870
assert "test" in requests_result.cookies
871+
872+
873+
@pytest.mark.asyncio
874+
async def test_http_error_responses_no_exception():
875+
"""Test that HTTP error responses (401, 500, etc.) don't raise exceptions."""
876+
mock_session = create_mock_session()
877+
transport = AIOHTTPTransport(session=mock_session)
878+
879+
# Test 401 Unauthorized
880+
mock_401_response = Mock(spec=aiohttp.ClientResponse)
881+
mock_401_response.status = 401
882+
mock_401_response.headers = {"Content-Type": "text/xml"}
883+
mock_401_response.method = "POST"
884+
mock_401_response.url = "http://example.com/service"
885+
mock_401_response.charset = "utf-8"
886+
mock_401_response.cookies = {}
887+
mock_401_response.read = AsyncMock(return_value=b"<error>Unauthorized</error>")
888+
889+
mock_session = Mock(spec=aiohttp.ClientSession)
890+
mock_session.post = AsyncMock(return_value=mock_401_response)
891+
transport.session = mock_session
892+
893+
# Should not raise exception
894+
result = await transport.post("http://example.com/service", "<request/>", {})
895+
assert isinstance(result, httpx.Response)
896+
assert result.status_code == 401
897+
assert result.read() == b"<error>Unauthorized</error>"
898+
899+
# Test 500 Internal Server Error
900+
mock_500_response = Mock(spec=aiohttp.ClientResponse)
901+
mock_500_response.status = 500
902+
mock_500_response.headers = {"Content-Type": "text/xml"}
903+
mock_500_response.charset = "utf-8"
904+
mock_500_response.cookies = {}
905+
mock_500_response.read = AsyncMock(return_value=b"<error>Server Error</error>")
906+
907+
mock_session.get = AsyncMock(return_value=mock_500_response)
908+
909+
# Should not raise exception
910+
result = await transport.get("http://example.com/wsdl")
911+
assert isinstance(result, RequestsResponse)
912+
assert result.status_code == 500
913+
assert result.content == b"<error>Server Error</error>"

0 commit comments

Comments
 (0)