Skip to content

Add /health endpoint for monitoring authentication and download state…#1291

Open
JonasGao wants to merge 102 commits intoicloud-photos-downloader:masterfrom
dev-zapi:master
Open

Add /health endpoint for monitoring authentication and download state…#1291
JonasGao wants to merge 102 commits intoicloud-photos-downloader:masterfrom
dev-zapi:master

Conversation

@JonasGao
Copy link
Copy Markdown

Add a health endpoint for docker container to show the user loged apple in or not

AndreyNikiforov and others added 30 commits September 4, 2025 08:07
The test_handle_session_error_during_download now uses a cassette with
an actual session error response instead of mocking PyiCloudAPIResponseException.
This works because the error handling code in session.py (before commit c82491d)
properly parses error responses and raises exceptions.

Changes:
- Created listing_photos_session_error_download.yml cassette with 401 "Invalid global session" response
- Removed all mocks from test_handle_session_error_during_download
- Test now relies entirely on cassette for session error simulation
- Demonstrates that cassettes can trigger PyiCloudAPIResponseException without mocks

This is a cleaner approach that tests the actual error handling path rather than
bypassing it with mocks.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…tests

- Converted 4 session error tests from using mocks to using VCR cassettes
- test_handle_session_error_during_download: uses cassette with 401 response during download
- test_handle_session_error_during_photo_iteration: uses cassette with 401 response during photo listing
- test_handle_session_error_during_download_name_id7: uses cassette for name-id7 file match policy
- test_handle_session_error_during_photo_iteration_name_id7: uses cassette for name-id7 with listing error

All tests properly trigger PyiCloudAPIResponseException from HTTP responses in cassettes
without needing to mock the exception directly. This proves that cassettes can simulate
session errors when the error handling code is active.

Note: One test (download_name_id7) needs cassette completion for the second download attempt
after re-authentication.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Since MAX_RETRIES is set to 0 and retry logic was removed, the time.sleep
mocks and sleep count assertions are no longer needed. The tests now only
verify that re-authentication happens (by counting 'Authenticating...' messages)
and that the session error message appears in the output.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove mock.patch.object for send_verification_code in test_2sa_flow_failed_send_code
- Create dedicated VCR cassette (2sa_flow_failed_send_code.yml) with failed response
- Cassette simulates service unavailable (success:false) response
- Remove unnecessary mock import from test file

This demonstrates that mocks can be replaced with more realistic VCR cassettes
that test the full HTTP request/response handling stack.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Use consistent error response format with errorTitle, errorMessage, and errorCode
- Match the structure used in other Apple API error responses
- Remove incorrectly added 'status' field from headers section
- Update content-length to match new response size

The response now matches Apple's actual API format as seen in other
captured cassettes like 2sa_flow_invalid_code.yml.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
… mocks

- Created listing_albums_error.yml cassette with 500 status and API error response
- Removed PhotoLibrary._fetch_folders mock and authenticate mock
- Test now relies on cassette to trigger PyiCloudAPIResponseException
- Removed unused PhotoLibrary import

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
The session.py code has two paths for error handling:
1. HTTP errors (non-OK status codes)
2. API errors in JSON payload with 200 status

The test_handle_albums_error was originally mocking an API error, so the
cassette should return HTTP 200 with error in the JSON payload, not HTTP 500.

Updated cassette response to: {"success":false,"error":"Api Error","errorCode":"100"}

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- test_handle_albums_error_name_id7: uses same cassette as albums_error test
- test_handle_internal_error_during_download: uses cassette with 500 status
  during photo download

Key learnings:
- API errors (200 with error JSON) vs HTTP errors (4xx/5xx status) are handled
  differently in session.py
- Download errors need HTTP status errors (500) since downloads use streaming
  and the error handling differs from regular JSON API calls
- Removed unused PhotoLibrary and constants imports

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Download errors behave differently than API errors:
- API endpoints (albums, photo listing): Can use HTTP 200 with error JSON
- Download endpoints: Must use HTTP error status (4xx/5xx) because downloads
  use streaming and don't parse JSON responses

The test behavior changes slightly:
- Original mock: Raised PyiCloudAPIResponseException with "INTERNAL_ERROR"
- Cassette: HTTP 500 results in "Could not find URL to download" message
- Exit code changes from 1 to 0 as error is handled gracefully

This is an acceptable difference as the error is still caught and handled,
just reported differently.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Remove is_streaming_response check that was preventing exceptions for streaming downloads
- Add special handling to exclude 404 errors from raising exceptions
- Update cassette to return HTTP 500 for download errors
- Ensure download errors raise exceptions and cause exit code 1

The is_streaming_response check was preventing the session from raising
exceptions for HTTP errors on streaming downloads, causing inconsistent
behavior between mock tests and cassette tests. Checking response status
doesn't load the stream into memory since it's part of the HTTP headers.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Convert test_handle_internal_error_during_photo_iteration to use cassette
- Convert test_handle_internal_error_during_download_name_id7 to use cassette
- Add cassettes for simulating HTTP 500 errors during photo operations
- Remove mock dependencies from converted tests
- Clean up unused imports

These tests now use actual HTTP responses via cassettes instead of mocks,
providing more realistic testing of error handling behavior.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
The session error cassettes now include successful download after
re-authentication, demonstrating proper retry behavior. Updated test
comments to accurately reflect cassette contents and expected behavior.

Both tests now:
- Experience session error on first download attempt
- Re-authenticate automatically
- Successfully download on retry
- Exit with code 0 (success)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Minor formatting improvements to session error test comments
and file download expectations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…cassettes

The listing_photos_session_error_iteration.yml and listing_photos_session_error_iteration_name_id7.yml
cassettes had delete file requests at the end that shouldn't be there. These have been removed.
Both tests still pass after the cleanup.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
AndreyNikiforov and others added 24 commits September 22, 2025 07:10
…ic ADT types

- Created specific ADT types for each authentication failure case:
  - AuthPasswordNotProvided for missing password
  - AuthInvalidCredentials for invalid credentials (401/421 errors)
  - AuthServiceNotActivated for service not activated
  - AuthServiceUnavailable for 503 errors
  - AuthAPIError for generic API errors
  - AuthUnexpectedError for unexpected errors

- Updated authenticate_adt() to return specific ADTs instead of wrapped exceptions
- Updated create_pyicloud_service_adt() to handle all new ADT types
- Updated authenticator() and core_single_run() to pattern match on new ADTs
- Fixed test to expect AuthInvalidCredentials instead of wrapped exception
- All tests pass, strict mypy clean on code and tests

This avoids the anti-pattern of wrapping exceptions in ADTs and provides
more precise error types for better error handling.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Changed core_single_run return type to int | CoreSingleRunErrorResult
- Added CoreSingleRunErrorResult union type to response_types.py
- Updated caller with pattern matching to handle both int and ADT results
- Maintained dual mode - still throws exceptions for backward compatibility
- Infrastructure ready for gradual exception-to-ADT conversion
- All tests pass including critical test_failed_auth_503_watch
…p 1)

- Replaced sys.exit(1) with ADT return for AuthenticatorTwoSAExit
- Tests still pass including critical test_failed_auth_503_watch
- First successful exception-to-ADT conversion
- Changed line 1009 from raising PyiCloudConnectionException to returning ADT
- Removed unused PyiCloudConnectionException import
- All tests passing (218 passed, 2 skipped)
- Changed line 1010 from raising PyiCloudFailedLoginException to returning ADT
- All tests passing (19 passed, 1 skipped)
- Changed line 1012 from raising PyiCloudServiceNotActivatedException to returning ADT
- All tests passing (19 passed, 1 skipped)
- Changed line 1016 from raising PyiCloudAPIResponseException to returning ADT
- Skipped AuthServiceUnavailable as it breaks watch functionality
- All tests passing (19 passed, 1 skipped)
- Replace PyiCloudServiceNotActivatedException with ADT return
- Add special logging in pattern matching to preserve error message
- All tests passing (218 passed)
- Document successful conversion of 7/10 authentication errors
- Identify 3 cases that must remain as exceptions
- Add learnings about watch mode and MFA requirements
- Note that service access errors need separate refactoring
- Replace AuthUnexpectedError with specific AuthConnectionError ADT
- Only catch PyiCloudConnectionErrorException (network errors) with ADT
- Let all other unexpected exceptions propagate and crash the program
- This aligns with the principle that we only handle expected IO errors
- Removes unnecessary complexity from error handling
- All 218 tests passing
- Authentication now returns ADT results instead of throwing exceptions
- Moved authentication code outside the try block
- Removed PyiCloudFailedLoginException handler (no longer raised)
- Converted PyiCloudFailedMFAException to direct ADT handling
- Kept necessary exception handlers for service/IO errors
- This completes the authentication exception-to-ADT refactoring
- All 218 tests passing
…e_run

- Replace PyiCloud2SARequiredException with Response2SARequired return in private libraries check
- Replace PyiCloudServiceNotActivatedException with ResponseServiceNotActivated return in private libraries check
- All tests pass with these minimal changes
…es check

- Replace PyiCloudAPIResponseException with ResponseAPIError return
- Replace PyiCloudServiceUnavailableException with ResponseServiceUnavailable return
- All tests continue to pass
- Replace all four exception types with corresponding ADT returns
- Maintains consistent error handling pattern
- All tests pass
- Removed get_album_count function that raised exceptions
- Replaced functional programming approach with direct for loop
- Added inline error handling for session errors
- Invalid global session errors now trigger re-authentication
- Other API errors are logged and return error code directly
- All tests pass, mypy strict mode passes
- Added error_occurred flag to track when retry is needed
- Replaced exception raising in photo iteration with direct handling
- Session errors trigger retry via flag instead of exception
- WebUI errors that can be retried also use flag mechanism
- Other errors return error code directly
- All tests pass, mypy strict mode passes
- Replaced exception raising in download result handling with direct error handling
- Used needs_retry flag to coordinate retry logic across nested loops
- Session errors during download now trigger retry via flag mechanism
- 500 errors and service unavailable return error code directly
- Other API errors are logged but continue processing
- Fixed retry logic to properly break from albums loop
- All tests pass, mypy strict mode passes
- Replaced exception raising in delete photo result handling with direct error handling
- Session errors during delete now trigger retry via needs_retry flag
- 500 errors return error code 1 (important for delete operations)
- Service unavailable returns error code 1
- Other API errors are logged but continue processing
- All tests pass, mypy strict mode passes
- Replaced exception raising in autodelete result handling with direct error handling
- Session errors trigger retry by continuing the while loop
- WebUI errors that can be retried also continue the loop
- Service unavailable returns error code 1
- Other errors return appropriate ADT results or error codes
- Removed unused PyiCloud2SARequiredException import
- All tests pass, mypy strict mode passes
Replace try-except block with direct ADT error handling throughout
core_single_run function. All error cases now use pattern matching
directly without exception catching.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…#1)

* Initial plan

* Add health check endpoint to Flask server

Co-authored-by: JonasGao <4549584+JonasGao@users.noreply.github.com>

* Refactor health check tests to reduce duplication

Co-authored-by: JonasGao <4549584+JonasGao@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: JonasGao <4549584+JonasGao@users.noreply.github.com>
@stavros-k
Copy link
Copy Markdown

That would be great way to notify users instead of smtp.
ie I have already a uptime-kuma monitoring health of my containers and send me alerts if something is unhealthy

Awesome

Copilot AI review requested due to automatic review settings January 25, 2026 08:54
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds a /health endpoint for Docker container monitoring to check authentication and download state. It includes a significant refactoring introducing Algebraic Data Types (ADTs) for better error handling throughout the codebase, particularly in authentication, photo service access, and download operations.

Changes:

  • Added /health endpoint returning 200 when authenticated (NO_INPUT_NEEDED status) or 503 when authentication is needed
  • Introduced comprehensive ADT system in pyicloud_ipd/response_types.py for type-safe error handling
  • Refactored authentication flow, photo services, and download logic to use ADTs instead of exceptions
  • Updated test infrastructure with new VCR cassettes for authentication scenarios
  • Modified command-line tool to focus on keyring management only
  • Added directory existence validation in CLI

Reviewed changes

Copilot reviewed 41 out of 62 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/icloudpd/server/init.py Added /health endpoint returning status based on authentication state
tests/test_health_check.py New test suite for health endpoint with multiple status scenarios
src/pyicloud_ipd/response_types.py New 790-line ADT definitions for all response types
src/pyicloud_ipd/session.py Refactored response evaluation to return ADTs instead of raising exceptions
src/pyicloud_ipd/base.py Major authentication refactoring using ADTs with factory pattern
src/pyicloud_ipd/services/photos.py Photo service operations now return ADTs for error handling
src/icloudpd/authentication.py Authentication flow updated to handle ADT results
src/icloudpd/download.py Download logic refactored to use ADTs
src/icloudpd/autodelete.py Autodelete operations updated with ADT error handling
src/icloudpd/cli.py Added directory validation before processing
src/pyicloud_ipd/cmdline.py Simplified to keyring management tool only
tests/*.py Multiple test updates for new authentication and error handling patterns
tests/vcr_cassettes/*.yml Updated/added VCR cassettes for test fixtures
.gitignore Added *.har files to ignore list

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if _status == Status.NO_INPUT_NEEDED:
return make_response("ok", 200)
else:
return make_response("fail", 503)
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The health endpoint returns 503 status when authentication is needed, which is semantically incorrect. HTTP 503 means "Service Unavailable" and typically indicates the server cannot handle requests due to maintenance or overload. For authentication/authorization issues, a more appropriate status would be 401 (Unauthorized) or 503 should be reserved for actual service unavailability. Consider returning 401 or another 4xx status code when authentication input is needed.

Suggested change
return make_response("fail", 503)
return make_response("fail", 401)

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,85 @@
"""Tests for the health check endpoint"""
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says "loged" but should be "logged". This typo is visible in the PR title/description.

Copilot uses AI. Check for mistakes.
@pytest.mark.skip(
reason="Extrapolated normalized payload expected to send exception in session response parsing, but app code in base.py expects only "
"success"
" node in response. I cannot test SA on any of my accounts to get real server reponse and change app code accordingly"
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment has a typo: "reponse" should be "response".

Suggested change
" node in response. I cannot test SA on any of my accounts to get real server reponse and change app code accordingly"
" node in response. I cannot test SA on any of my accounts to get real server response and change app code accordingly"

Copilot uses AI. Check for mistakes.
)
self.assertEqual(2, result.output.count("Waiting for 1 sec..."))
# self.assertTrue("Can't overwrite existing cassette" in str(context.exception))
self.assertTrue("Can't overwrite existing cassette" in str(result.exception))
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assertTrue(a in b) cannot provide an informative message. Using assertIn(a, b) instead will give more informative messages.

Suggested change
self.assertTrue("Can't overwrite existing cassette" in str(result.exception))
self.assertIn("Can't overwrite existing cassette", str(result.exception))

Copilot uses AI. Check for mistakes.
Comment on lines +705 to +731
# def _authenticate_raw_password(self, password: str) -> None:
# data = {
# "accountName": self.apple_id,
# "password": password,
# "rememberMe": True,
# "trustTokens": [],
# }
# if self.session_data.get("trust_token"):
# data["trustTokens"] = [self.session_data.get("trust_token")]

# headers = self._get_auth_headers(origin_referer_headers(self.AUTH_ROOT_ENDPOINT))
# try:
# # set observer with obfuscator
# if self.response_observer:
# rules = list(
# chain(
# self.cookie_obfuscate_rules,
# self.header_obfuscate_rules,
# self.header_pass_rules,
# self.header_drop_rules,
# self.auth_raw_body_obfuscate_rules,
# self.auth_raw_body_drop_rules,
# )
# )
# else:
# rules = []

Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment appears to contain commented-out code.

Suggested change
# def _authenticate_raw_password(self, password: str) -> None:
# data = {
# "accountName": self.apple_id,
# "password": password,
# "rememberMe": True,
# "trustTokens": [],
# }
# if self.session_data.get("trust_token"):
# data["trustTokens"] = [self.session_data.get("trust_token")]
# headers = self._get_auth_headers(origin_referer_headers(self.AUTH_ROOT_ENDPOINT))
# try:
# # set observer with obfuscator
# if self.response_observer:
# rules = list(
# chain(
# self.cookie_obfuscate_rules,
# self.header_obfuscate_rules,
# self.header_pass_rules,
# self.header_drop_rules,
# self.auth_raw_body_obfuscate_rules,
# self.auth_raw_body_drop_rules,
# )
# )
# else:
# rules = []

Copilot uses AI. Check for mistakes.
Comment on lines +709 to +739
# "rememberMe": True,
# "trustTokens": [],
# }
# if self.session_data.get("trust_token"):
# data["trustTokens"] = [self.session_data.get("trust_token")]

# headers = self._get_auth_headers(origin_referer_headers(self.AUTH_ROOT_ENDPOINT))
# try:
# # set observer with obfuscator
# if self.response_observer:
# rules = list(
# chain(
# self.cookie_obfuscate_rules,
# self.header_obfuscate_rules,
# self.header_pass_rules,
# self.header_drop_rules,
# self.auth_raw_body_obfuscate_rules,
# self.auth_raw_body_drop_rules,
# )
# )
# else:
# rules = []

# with self.use_rules(rules):
# response = self.session.post(
# f"{self.AUTH_ENDPOINT}/signin",
# params={"isRememberMeEnabled": "true"},
# data=json.dumps(data),
# headers=headers,
# )
# result = self.session.evaluate_response(response)
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment appears to contain commented-out code.

Suggested change
# "rememberMe": True,
# "trustTokens": [],
# }
# if self.session_data.get("trust_token"):
# data["trustTokens"] = [self.session_data.get("trust_token")]
# headers = self._get_auth_headers(origin_referer_headers(self.AUTH_ROOT_ENDPOINT))
# try:
# # set observer with obfuscator
# if self.response_observer:
# rules = list(
# chain(
# self.cookie_obfuscate_rules,
# self.header_obfuscate_rules,
# self.header_pass_rules,
# self.header_drop_rules,
# self.auth_raw_body_obfuscate_rules,
# self.auth_raw_body_drop_rules,
# )
# )
# else:
# rules = []
# with self.use_rules(rules):
# response = self.session.post(
# f"{self.AUTH_ENDPOINT}/signin",
# params={"isRememberMeEnabled": "true"},
# data=json.dumps(data),
# headers=headers,
# )
# result = self.session.evaluate_response(response)
# Note: a legacy authentication flow previously used here has been removed.
# Refer to version control history if the old implementation details are needed.

Copilot uses AI. Check for mistakes.
Comment on lines +720 to +750
# chain(
# self.cookie_obfuscate_rules,
# self.header_obfuscate_rules,
# self.header_pass_rules,
# self.header_drop_rules,
# self.auth_raw_body_obfuscate_rules,
# self.auth_raw_body_drop_rules,
# )
# )
# else:
# rules = []

# with self.use_rules(rules):
# response = self.session.post(
# f"{self.AUTH_ENDPOINT}/signin",
# params={"isRememberMeEnabled": "true"},
# data=json.dumps(data),
# headers=headers,
# )
# result = self.session.evaluate_response(response)
# match result:
# case ResponseSuccess(_):
# pass # Success, continue
# case Response2SARequired(account_name):
# raise PyiCloud2SARequiredException(account_name)
# case ResponseServiceNotActivated(reason, code):
# raise PyiCloudServiceNotActivatedException(reason, code)
# case ResponseAPIError(reason, code):
# raise PyiCloudAPIResponseException(reason, code)
# case ResponseServiceUnavailable(reason):
# raise PyiCloudServiceUnavailableException(reason)
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment appears to contain commented-out code.

Suggested change
# chain(
# self.cookie_obfuscate_rules,
# self.header_obfuscate_rules,
# self.header_pass_rules,
# self.header_drop_rules,
# self.auth_raw_body_obfuscate_rules,
# self.auth_raw_body_drop_rules,
# )
# )
# else:
# rules = []
# with self.use_rules(rules):
# response = self.session.post(
# f"{self.AUTH_ENDPOINT}/signin",
# params={"isRememberMeEnabled": "true"},
# data=json.dumps(data),
# headers=headers,
# )
# result = self.session.evaluate_response(response)
# match result:
# case ResponseSuccess(_):
# pass # Success, continue
# case Response2SARequired(account_name):
# raise PyiCloud2SARequiredException(account_name)
# case ResponseServiceNotActivated(reason, code):
# raise PyiCloudServiceNotActivatedException(reason, code)
# case ResponseAPIError(reason, code):
# raise PyiCloudAPIResponseException(reason, code)
# case ResponseServiceUnavailable(reason):
# raise PyiCloudServiceUnavailableException(reason)
# Note: an older sign-in implementation using additional obfuscation and
# response handling logic was removed here to avoid keeping commented-out code.
# Refer to version control history if the previous logic is needed.

Copilot uses AI. Check for mistakes.
Comment on lines +733 to +753
# response = self.session.post(
# f"{self.AUTH_ENDPOINT}/signin",
# params={"isRememberMeEnabled": "true"},
# data=json.dumps(data),
# headers=headers,
# )
# result = self.session.evaluate_response(response)
# match result:
# case ResponseSuccess(_):
# pass # Success, continue
# case Response2SARequired(account_name):
# raise PyiCloud2SARequiredException(account_name)
# case ResponseServiceNotActivated(reason, code):
# raise PyiCloudServiceNotActivatedException(reason, code)
# case ResponseAPIError(reason, code):
# raise PyiCloudAPIResponseException(reason, code)
# case ResponseServiceUnavailable(reason):
# raise PyiCloudServiceUnavailableException(reason)
# except PyiCloudAPIResponseException as error:
# msg = "Invalid email/password combination."
# raise PyiCloudFailedLoginException(msg, error) from error
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment appears to contain commented-out code.

Suggested change
# response = self.session.post(
# f"{self.AUTH_ENDPOINT}/signin",
# params={"isRememberMeEnabled": "true"},
# data=json.dumps(data),
# headers=headers,
# )
# result = self.session.evaluate_response(response)
# match result:
# case ResponseSuccess(_):
# pass # Success, continue
# case Response2SARequired(account_name):
# raise PyiCloud2SARequiredException(account_name)
# case ResponseServiceNotActivated(reason, code):
# raise PyiCloudServiceNotActivatedException(reason, code)
# case ResponseAPIError(reason, code):
# raise PyiCloudAPIResponseException(reason, code)
# case ResponseServiceUnavailable(reason):
# raise PyiCloudServiceUnavailableException(reason)
# except PyiCloudAPIResponseException as error:
# msg = "Invalid email/password combination."
# raise PyiCloudFailedLoginException(msg, error) from error

Copilot uses AI. Check for mistakes.
result = self.session.evaluate_response(response)
match result:
case ResponseSuccess(resp):
response = resp
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable response is not used.

Copilot uses AI. Check for mistakes.
Comment on lines +1413 to +1415
download_result = (
DownloadMediaSkipped()
) # Filtered files are skipped
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to 'download_result' is unnecessary as it is redefined before this value is used.

Suggested change
download_result = (
DownloadMediaSkipped()
) # Filtered files are skipped
# Filtered files are skipped
pass

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants