From 054ca7e810597b85f6a2b77dd7080ad1669f781e Mon Sep 17 00:00:00 2001 From: Joachim Ungar Date: Tue, 3 Dec 2024 11:25:12 +0100 Subject: [PATCH 1/3] fix masking of ClientResponseError where some tool raises a generic Exception instead of the original ClientResponseError --- mapchete/io/_misc.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/mapchete/io/_misc.py b/mapchete/io/_misc.py index 9d618d61..c37822c5 100644 --- a/mapchete/io/_misc.py +++ b/mapchete/io/_misc.py @@ -1,6 +1,7 @@ import logging from enum import Enum +from aiohttp import ClientResponseError import rasterio from rasterio.warp import calculate_default_transform from shapely.errors import TopologicalError @@ -196,14 +197,24 @@ def copy(src_path, dst_path, src_fs=None, dst_fs=None, overwrite=False): # create parent directories on local filesystems dst_path.parent.makedirs() - # copy either within a filesystem or between filesystems - if src_path.fs == dst_path.fs: - src_path.fs.copy(str(src_path), str(dst_path)) - else: - # read source data first - with src_path.open("rb") as src: - content = src.read() - # only write to destination if reading source data didn't raise errors, - # otherwise we can end up with empty objects on an object store - with dst_path.open("wb") as dst: - dst.write(content) + try: + # copy either within a filesystem or between filesystems + if src_path.fs == dst_path.fs: + src_path.fs.copy(str(src_path), str(dst_path)) + else: + # read source data first + with src_path.open("rb") as src: + content = src.read() + # only write to destination if reading source data didn't raise errors, + # otherwise we can end up with empty objects on an object store + with dst_path.open("wb") as dst: + dst.write(content) + except Exception as exception: + # This is a hack because some tool using aiohttp does not raise a + # ClientResponseError directly but masks it as a generic Exception and thus + # preventing our retry mechanism to kick in. + if repr(exception).startswith( + 'Exception("ClientResponseError' + ): # pragma: no cover + raise ClientResponseError(exception) from exception + raise From 260fc33756a3d8187cbcb5f7d4e07ccf2a961791 Mon Sep 17 00:00:00 2001 From: Joachim Ungar Date: Tue, 3 Dec 2024 11:36:50 +0100 Subject: [PATCH 2/3] raise as more generic ConnectionError, because ClientResponseError cannot be created easily --- mapchete/executor/future.py | 1 + mapchete/io/_misc.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mapchete/executor/future.py b/mapchete/executor/future.py index 351c82c3..dcea5734 100644 --- a/mapchete/executor/future.py +++ b/mapchete/executor/future.py @@ -123,6 +123,7 @@ def skip(skip_info: Optional[Any] = None, result: Optional[Any] = None) -> MFutu def from_func( func: Callable, fargs: Optional[Tuple] = None, fkwargs: Optional[Dict] = None ) -> MFuture: + fkwargs = fkwargs or {} try: return MFuture(result=func(*fargs, **fkwargs)) except Exception as exc: # pragma: no cover diff --git a/mapchete/io/_misc.py b/mapchete/io/_misc.py index c37822c5..44eba2f5 100644 --- a/mapchete/io/_misc.py +++ b/mapchete/io/_misc.py @@ -1,7 +1,6 @@ import logging from enum import Enum -from aiohttp import ClientResponseError import rasterio from rasterio.warp import calculate_default_transform from shapely.errors import TopologicalError @@ -216,5 +215,7 @@ def copy(src_path, dst_path, src_fs=None, dst_fs=None, overwrite=False): if repr(exception).startswith( 'Exception("ClientResponseError' ): # pragma: no cover - raise ClientResponseError(exception) from exception + raise ConnectionError(repr(exception)).with_traceback( + exception.__traceback__ + ) from exception raise From a00eb6f4b11feeb5f8a0146003e0d4204f33fca1 Mon Sep 17 00:00:00 2001 From: Joachim Ungar Date: Tue, 3 Dec 2024 11:55:51 +0100 Subject: [PATCH 3/3] exclude whole exception block from code coverage --- mapchete/io/_misc.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mapchete/io/_misc.py b/mapchete/io/_misc.py index 44eba2f5..2a0475c2 100644 --- a/mapchete/io/_misc.py +++ b/mapchete/io/_misc.py @@ -208,13 +208,11 @@ def copy(src_path, dst_path, src_fs=None, dst_fs=None, overwrite=False): # otherwise we can end up with empty objects on an object store with dst_path.open("wb") as dst: dst.write(content) - except Exception as exception: + except Exception as exception: # pragma: no cover # This is a hack because some tool using aiohttp does not raise a # ClientResponseError directly but masks it as a generic Exception and thus # preventing our retry mechanism to kick in. - if repr(exception).startswith( - 'Exception("ClientResponseError' - ): # pragma: no cover + if repr(exception).startswith('Exception("ClientResponseError'): raise ConnectionError(repr(exception)).with_traceback( exception.__traceback__ ) from exception