Skip to content

Commit 8c80bda

Browse files
do not throw on connection errors (#1193)
1 parent 46865bf commit 8c80bda

File tree

6 files changed

+155
-177
lines changed

6 files changed

+155
-177
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Unreleased
44

5+
- fix: connection errors reported with stack trace [#1187](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/1187)
56
- feat: `--skip-created-after` to limit assets by creation date [#466](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/466) [#1111](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/1111)
67
- fix: connecting to a non-activated iCloud service reported as an error
78
- fix: 503 response reported as an error [#1188](https://github.com/icloud-photos-downloader/icloud_photos_downloader/issues/1188)

src/icloudpd/base.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
from multiprocessing import freeze_support
55

6+
from requests import Timeout
7+
from requests.exceptions import ConnectionError
8+
69
import foundation
710
from foundation.core import compose, constant, identity
811
from icloudpd.mfa_provider import MFAProvider
@@ -1517,6 +1520,14 @@ def should_break(counter: Counter) -> bool:
15171520
return 1
15181521
else:
15191522
pass
1523+
except (ConnectionError, TimeoutError, Timeout) as _error:
1524+
logger.info("Cannot connect to Apple iCloud service")
1525+
# logger.debug(error)
1526+
# it not watching then return error
1527+
if not watch_interval:
1528+
return 1
1529+
else:
1530+
pass
15201531
except TwoStepAuthRequiredError:
15211532
if notification_script is not None:
15221533
subprocess.call([notification_script])
@@ -1537,7 +1548,6 @@ def should_break(counter: Counter) -> bool:
15371548
else:
15381549
pass
15391550
return 1
1540-
15411551
if watch_interval: # pragma: no cover
15421552
logger.info(f"Waiting for {watch_interval} sec...")
15431553
interval: Sequence[int] = range(1, watch_interval)

src/icloudpd/download.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import os
66
import time
77

8-
from requests import Response, Timeout
9-
from requests.exceptions import ConnectionError
8+
from requests import Response
109
from tzlocal import get_localzone
1110

1211
# Import the constants object so that we can mock WAIT_SECONDS in tests
@@ -124,7 +123,7 @@ def download_media(
124123
)
125124
break
126125

127-
except (TimeoutError, ConnectionError, PyiCloudAPIResponseException, Timeout) as ex:
126+
except PyiCloudAPIResponseException as ex:
128127
if "Invalid global session" in str(ex):
129128
logger.error("Session error, re-authenticating...")
130129
if retries > 0:

tests/test_authentication.py

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import inspect
22
import os
33
import shutil
4-
from typing import NamedTuple
5-
from unittest import TestCase
4+
from typing import Any, NamedTuple, NoReturn
5+
from unittest import TestCase, mock
66

77
import pytest
88
from click.testing import CliRunner
9+
from requests import Timeout
10+
from requests.exceptions import ConnectionError
911
from vcr import VCR
1012

1113
# import vcr
@@ -18,6 +20,7 @@
1820
from icloudpd.status import StatusExchange
1921
from pyicloud_ipd.file_match import FileMatchPolicy
2022
from pyicloud_ipd.raw_policy import RawTreatmentPolicy
23+
from pyicloud_ipd.session import PyiCloudSession
2124
from pyicloud_ipd.sms import parse_trusted_phone_numbers_payload
2225
from tests.helpers import path_from_project_root, recreate_path
2326

@@ -413,6 +416,141 @@ def test_failed_auth_503_watch(self) -> None:
413416
# self.assertTrue("Can't overwrite existing cassette" in str(context.exception))
414417
assert result.exit_code == 1 # should error for vcr
415418

419+
def test_connection_error(self) -> None:
420+
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
421+
cookie_dir = os.path.join(base_dir, "cookie")
422+
423+
for dir in [base_dir, cookie_dir]:
424+
recreate_path(dir)
425+
426+
def mock_raise_response_error(_a1: Any, _a2: Any, _a3: Any, **kwargs) -> NoReturn: # type: ignore [no-untyped-def]
427+
raise ConnectionError("Simulated Connection Error")
428+
429+
with (
430+
mock.patch.object(
431+
PyiCloudSession, "request", side_effect=mock_raise_response_error, autospec=True
432+
) as pa_request,
433+
vcr.use_cassette(os.path.join(self.vcr_path, "failed_auth_503.yml")),
434+
): # noqa: SIM117
435+
# errors.CannotOverwriteExistingCassetteException
436+
runner = CliRunner(env={"CLIENT_ID": "EC5646DE-9423-11E8-BF21-14109FE0B321"})
437+
result = runner.invoke(
438+
main,
439+
[
440+
"--username",
441+
"jdoe@gmail.com",
442+
"--password",
443+
"password1",
444+
"--no-progress-bar",
445+
"--directory",
446+
base_dir,
447+
"--cookie-directory",
448+
cookie_dir,
449+
# "--watch-with-interval",
450+
# "1",
451+
],
452+
)
453+
pa_request.assert_called_once()
454+
self.assertIn(
455+
"Authenticating...",
456+
self._caplog.text,
457+
)
458+
self.assertIn(
459+
"INFO Cannot connect to Apple iCloud service",
460+
self._caplog.text,
461+
)
462+
assert result.exit_code == 1 # should error for vcr
463+
464+
def test_timeout_error(self) -> None:
465+
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
466+
cookie_dir = os.path.join(base_dir, "cookie")
467+
468+
for dir in [base_dir, cookie_dir]:
469+
recreate_path(dir)
470+
471+
def mock_raise_response_error(_a1: Any, _a2: Any, _a3: Any, **kwargs) -> NoReturn: # type: ignore [no-untyped-def]
472+
raise TimeoutError("Simulated TimeoutError")
473+
474+
with (
475+
mock.patch.object(
476+
PyiCloudSession, "request", side_effect=mock_raise_response_error, autospec=True
477+
) as pa_request,
478+
vcr.use_cassette(os.path.join(self.vcr_path, "failed_auth_503.yml")),
479+
): # noqa: SIM117
480+
# errors.CannotOverwriteExistingCassetteException
481+
runner = CliRunner(env={"CLIENT_ID": "EC5646DE-9423-11E8-BF21-14109FE0B321"})
482+
result = runner.invoke(
483+
main,
484+
[
485+
"--username",
486+
"jdoe@gmail.com",
487+
"--password",
488+
"password1",
489+
"--no-progress-bar",
490+
"--directory",
491+
base_dir,
492+
"--cookie-directory",
493+
cookie_dir,
494+
# "--watch-with-interval",
495+
# "1",
496+
],
497+
)
498+
pa_request.assert_called_once()
499+
self.assertIn(
500+
"Authenticating...",
501+
self._caplog.text,
502+
)
503+
self.assertIn(
504+
"INFO Cannot connect to Apple iCloud service",
505+
self._caplog.text,
506+
)
507+
assert result.exit_code == 1 # should error for vcr
508+
509+
def test_timeout(self) -> None:
510+
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
511+
cookie_dir = os.path.join(base_dir, "cookie")
512+
513+
for dir in [base_dir, cookie_dir]:
514+
recreate_path(dir)
515+
516+
def mock_raise_response_error(_a1: Any, _a2: Any, _a3: Any, **kwargs) -> NoReturn: # type: ignore [no-untyped-def]
517+
raise Timeout("Simulated Timeout")
518+
519+
with (
520+
mock.patch.object(
521+
PyiCloudSession, "request", side_effect=mock_raise_response_error, autospec=True
522+
) as pa_request,
523+
vcr.use_cassette(os.path.join(self.vcr_path, "failed_auth_503.yml")),
524+
): # noqa: SIM117
525+
# errors.CannotOverwriteExistingCassetteException
526+
runner = CliRunner(env={"CLIENT_ID": "EC5646DE-9423-11E8-BF21-14109FE0B321"})
527+
result = runner.invoke(
528+
main,
529+
[
530+
"--username",
531+
"jdoe@gmail.com",
532+
"--password",
533+
"password1",
534+
"--no-progress-bar",
535+
"--directory",
536+
base_dir,
537+
"--cookie-directory",
538+
cookie_dir,
539+
# "--watch-with-interval",
540+
# "1",
541+
],
542+
)
543+
pa_request.assert_called_once()
544+
self.assertIn(
545+
"Authenticating...",
546+
self._caplog.text,
547+
)
548+
self.assertIn(
549+
"INFO Cannot connect to Apple iCloud service",
550+
self._caplog.text,
551+
)
552+
assert result.exit_code == 1 # should error for vcr
553+
416554

417555
class _TrustedDevice(NamedTuple):
418556
id: int

tests/test_download_photos.py

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from click.testing import CliRunner
1616
from piexif._exceptions import InvalidImageDataError
1717
from requests import Response
18-
from requests.exceptions import ConnectionError
1918
from vcr import VCR
2019

2120
from icloudpd import constants
@@ -543,62 +542,6 @@ def mocked_authenticate(self: PyiCloudService) -> None:
543542

544543
assert result.exit_code == 1
545544

546-
def test_handle_connection_error(self) -> None:
547-
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
548-
549-
def mock_raise_response_error(_arg: Any) -> NoReturn:
550-
raise ConnectionError("Connection Error")
551-
552-
with mock.patch.object(PhotoAsset, "download") as pa_download:
553-
pa_download.side_effect = mock_raise_response_error
554-
555-
# Let the initial authenticate() call succeed,
556-
# but do nothing on the second try.
557-
orig_authenticate = PyiCloudService.authenticate
558-
559-
def mocked_authenticate(self: PyiCloudService) -> None:
560-
if not hasattr(self, "already_authenticated"):
561-
orig_authenticate(self)
562-
setattr(self, "already_authenticated", True) # noqa: B010
563-
564-
with mock.patch("icloudpd.constants.WAIT_SECONDS", 0): # noqa: SIM117
565-
with mock.patch.object(PyiCloudService, "authenticate", new=mocked_authenticate):
566-
_, result = run_icloudpd_test(
567-
self.assertEqual,
568-
self.root_path,
569-
base_dir,
570-
"listing_photos.yml",
571-
[],
572-
[],
573-
[
574-
"--username",
575-
"jdoe@gmail.com",
576-
"--password",
577-
"password1",
578-
"--recent",
579-
"1",
580-
"--skip-videos",
581-
"--skip-live-photos",
582-
"--no-progress-bar",
583-
"--threads-num",
584-
"1",
585-
],
586-
)
587-
588-
# Error msg should be repeated 5 times
589-
assert (
590-
self._caplog.text.count(
591-
"Error downloading IMG_7409.JPG, retrying after 0 seconds..."
592-
)
593-
== 5
594-
)
595-
596-
self.assertIn(
597-
"ERROR Could not download IMG_7409.JPG. Please try again later.",
598-
self._caplog.text,
599-
)
600-
assert result.exit_code == 0
601-
602545
def test_handle_albums_error(self) -> None:
603546
base_dir = os.path.join(self.fixtures_path, inspect.stack()[0][3])
604547

0 commit comments

Comments
 (0)