Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions onvif/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,17 @@ class AsyncTransportProtocolErrorHandler(AIOHTTPTransport):
# once since
"""

@retry_connection_error(attempts=2, exception=aiohttp.ServerDisconnectedError)
@retry_connection_error(
attempts=2, exception=aiohttp.ServerDisconnectedError, backoff=0
)
async def post(
self, address: str, message: str, headers: dict[str, str]
) -> httpx.Response:
return await super().post(address, message, headers)

@retry_connection_error(attempts=2, exception=aiohttp.ServerDisconnectedError)
@retry_connection_error(
attempts=2, exception=aiohttp.ServerDisconnectedError, backoff=0
)
async def get(
self,
address: str,
Expand All @@ -129,6 +133,14 @@ async def get(
) -> Response:
return await super().get(address, params, headers)

@retry_connection_error(
attempts=2, exception=aiohttp.ServerDisconnectedError, backoff=0
)
async def post_xml(
self, address: str, envelope: Any, headers: dict[str, str]
) -> Response:
return await super().post_xml(address, envelope, headers)


async def _cached_document(url: str) -> Document:
"""Load external XML document from disk."""
Expand Down
25 changes: 23 additions & 2 deletions onvif/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
def retry_connection_error(
attempts: int = DEFAULT_ATTEMPTS,
exception: type[Exception] = aiohttp.ClientError,
backoff: float | None = None,
) -> Callable[[Callable[P, Awaitable[T]]], Callable[P, Awaitable[T]]]:
"""Define a wrapper to retry on connection error."""
if backoff is None:
backoff = BACKOFF_TIME

def _decorator_retry_connection_error(
func: Callable[P, Awaitable[T]],
Expand All @@ -35,8 +38,17 @@ def _decorator_retry_connection_error(
async def _async_wrap_connection_error_retry( # type: ignore[return]
*args: P.args, **kwargs: P.kwargs
) -> T:
logger.debug(
"retry_connection_error wrapper called for %s with exception=%s, attempts=%s",
func.__name__,
exception,
attempts,
)
for attempt in range(attempts):
try:
logger.debug(
"Attempt %s/%s for %s", attempt + 1, attempts, func.__name__
)
return await func(*args, **kwargs)
except exception as ex:
#
Expand All @@ -49,16 +61,25 @@ async def _async_wrap_connection_error_retry( # type: ignore[return]
# to close the connection at any time, we treat this as a normal and try again
# once since we do not want to declare the camera as not supporting PullPoint
# if it just happened to close the connection at the wrong time.
logger.debug(
"Caught exception %s (type: %s) on attempt %s/%s",
ex,
type(ex).__name__,
attempt + 1,
attempts,
exc_info=True,
)
if attempt == attempts - 1:
logger.debug("Final attempt failed, re-raising exception")
raise
logger.debug(
"Error: %s while calling %s, backing off: %s, retrying...",
ex,
func,
BACKOFF_TIME,
backoff,
exc_info=True,
)
await asyncio.sleep(BACKOFF_TIME)
await asyncio.sleep(backoff)

return _async_wrap_connection_error_retry

Expand Down
Loading