Skip to content

Conversation

@cyberjunky
Copy link
Owner

@cyberjunky cyberjunky commented Oct 10, 2025

Summary by CodeRabbit

  • New Features

    • Interactive multi-file upload: choose from available activity files (glob support) with updated per-file prompts and detailed HTTP error messages.
  • Chores

    • Switched pre-commit tooling to Ruff and adjusted type-check invocation.
    • CI: only upload coverage artifact when a coverage report is present.
    • Updated project/tooling configs and cleaned README formatting.
    • Cleared notebook outputs in docs for a clean source.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 10, 2025

Walkthrough

Replaces Black with Ruff in pre-commit tooling, updates demo.py to use a glob and interactive GPX selection with expanded HTTP error handling, adjusts CI to gate coverage upload on an actual report, removes a pdm-specific Python path from pyproject, updates coderabbit profile, and clears outputs in a docs notebook and formatting in README.

Changes

Cohort / File(s) Summary
Pre-commit / packaging tooling
\.pre-commit-config.yaml, pyproject.toml, \.coderabbit.yaml
Removed Black pre-commit hook and added/kept Ruff hooks (ruff-pre-commit, ruff-format) with Black commented out; removed explicit [tool.pdm.python] virtualenv path from pyproject.toml; changed reviews.profile from pythonic to assertive and removed the auto_fix block from .coderabbit.yaml.
Interactive upload workflow
demo.py
Config.activityfile changed from "test_data/sample_activity.gpx" to a glob "test_data/*.gpx"; added glob usage and interactive file selection prompt; uploads the selected file via api.upload_activity(selected_file); expanded FileNotFoundError and HTTP error handling with specific messages for 400/401/409/413/422/429; updated logging/messages to reference the selected file.
CI workflow
.github/workflows/ci.yml
Added a pre-check step that sets an output flag when coverage.xml exists and updated the coverage artifact upload step to run only when Python is 3.11 and the coverage file was generated.
Docs & README
README.md, docs/reference.ipynb
Formatted README with additional blank lines around code blocks/sections (cosmetic); cleared execution outputs and set execution_count to null across many cells in docs/reference.ipynb (removes precomputed outputs).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant CLI as demo.py
  participant FS as FileSystem
  participant API as Server

  User->>CLI: run upload command
  CLI->>FS: glob "test_data/*.gpx"
  FS-->>CLI: list of GPX files
  alt no files
    CLI-->>User: "No GPX files found" & exit
  else files found
    CLI-->>User: display numbered list & prompt
    User->>CLI: choose index
    alt invalid index
      CLI-->>User: "Invalid selection" & exit
    else valid index
      CLI->>API: POST upload(selected_file)
      alt success (2xx)
        API-->>CLI: success
        CLI-->>User: "Upload successful"
      else error (400/401/409/413/422/429)
        API-->>CLI: error
        CLI-->>User: specific error message
      end
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title concisely captures the primary demo enhancement of an interactive activity file selector while also indicating the removal of Black integration, both of which are central changes in this PR. It directly reflects the updates to demo.py and the pre-commit configuration, using clear and specific phrasing without extraneous detail. Reviewers scanning the history can immediately understand the main modifications.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch revamp

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1bd560e and b346f03.

📒 Files selected for processing (6)
  • .coderabbit.yaml (2 hunks)
  • .github/workflows/ci.yml (1 hunks)
  • .pre-commit-config.yaml (2 hunks)
  • README.md (4 hunks)
  • docs/reference.ipynb (5 hunks)
  • pyproject.toml (0 hunks)
💤 Files with no reviewable changes (1)
  • pyproject.toml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.10)
🔇 Additional comments (5)
README.md (1)

213-217: Formatting tweaks look good.

Extra whitespace around the commands reads cleanly and keeps the Markdown renderer happy.

.coderabbit.yaml (1)

5-24: Config schema compliance verified.

The profile swap and removal of the auto-fix section keep the config within the documented schema and align with the new review stance.

.pre-commit-config.yaml (1)

21-40: Ruff swap and mypy entry look solid.

Replacing Black with Ruff’s formatter keeps tooling consistent, and running mypy via pdm run avoids brittle venv paths.

.github/workflows/ci.yml (1)

64-75: Good guard around coverage artifacts.

Adding an existence check before uploading prevents noisy failures when no coverage report is produced.

docs/reference.ipynb (1)

12-232: Notebook cleanup appreciated.

Clearing execution counts and outputs keeps the reference notebook reproducible and diff-friendly.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
demo.py (2)

73-73: Clarify config usage or update the glob pattern.

The activityfile config value is now a glob pattern ("test_data/*.gpx"), but the actual file selection logic at line 1294 uses a hardcoded pattern instead of referencing config.activityfile. This inconsistency can lead to confusion.

Apply this diff to use the config value consistently:

-        gpx_files = glob.glob("test_data/*.gpx")
+        gpx_files = glob.glob(config.activityfile)

Alternatively, if you prefer the hardcoded approach, update the config comment to reflect that this value is for display purposes only.


1289-1318: Remove redundant import and file existence check.

Two minor inefficiencies:

  1. os is already imported at line 23, so the import on line 1290 is redundant.
  2. The file existence check on lines 1316-1318 is unnecessary since selected_file was just chosen from glob.glob() results, which only returns existing files.

Apply this diff to remove redundancies:

     """Upload activity data from file."""
     import glob
-    import os
 
     try:
         # List all .gpx files in test_data
         gpx_files = glob.glob("test_data/*.gpx")
         if not gpx_files:
             print("❌ No .gpx files found in test_data directory.")
             print("ℹ️ Please add GPX files to test_data before uploading.")
             return
 
         print("Select a GPX file to upload:")
         for idx, fname in enumerate(gpx_files, 1):
             print(f"  {idx}. {fname}")
 
         while True:
             try:
                 choice = int(input(f"Enter number (1-{len(gpx_files)}): "))
                 if 1 <= choice <= len(gpx_files):
                     selected_file = gpx_files[choice - 1]
                     break
                 else:
                     print("Invalid selection. Try again.")
             except ValueError:
                 print("Please enter a valid number.")
 
         print(f"📤 Uploading activity from file: {selected_file}")
-        if not os.path.exists(selected_file):
-            print(f"❌ File not found: {selected_file}")
-            return
 
         result = api.upload_activity(selected_file)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 749c248 and c17cd54.

📒 Files selected for processing (2)
  • .pre-commit-config.yaml (1 hunks)
  • demo.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
demo.py (1)
garminconnect/__init__.py (1)
  • upload_activity (1648-1700)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.10)
🔇 Additional comments (2)
.pre-commit-config.yaml (1)

21-26: LGTM!

The removal of Black in favor of Ruff is well-documented with a clear inline comment explaining the rationale. This prevents formatting conflicts and consolidates linting and formatting under a single tool.

demo.py (1)

1336-1412: LGTM! Comprehensive error handling.

The expanded HTTP error handling provides excellent user experience with:

  • Specific messages for common status codes (409, 413, 422, 400, 401, 429)
  • Helpful troubleshooting tips for each scenario
  • Clear explanations of what went wrong

This will greatly improve debugging and user guidance when uploads fail.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
demo.py (1)

1316-1327: Critical: Activity uploaded twice.

This issue was already flagged in a previous review. The activity is uploaded twice:

  1. Line 1316: result = api.upload_activity(selected_file)
  2. Lines 1320-1325: call_and_display(api.upload_activity, selected_file, ...) uploads it again

Remove the direct api.upload_activity(selected_file) call on line 1316 and let call_and_display perform the upload. Capture the return value from call_and_display to determine success/failure.

🧹 Nitpick comments (2)
demo.py (2)

73-73: Consider using config.activityfile or removing this config change.

The updated glob pattern "test_data/*.gpx" on this line is never used—line 1293 hardcodes the same pattern directly in glob.glob("test_data/*.gpx"). Either reference config.activityfile in the upload function, or revert this config change if it's not needed.

Apply this diff to use the config variable:

-        gpx_files = glob.glob("test_data/*.gpx")
+        gpx_files = glob.glob(config.activityfile)

1332-1408: Consider consolidating duplicate error handling logic.

The error messages for status codes 409, 413, 422, 400, 401, and 429 are duplicated between the requests.exceptions.HTTPError block (lines 1332-1364) and the generic Exception block (lines 1374-1408). While this works correctly, it increases maintenance burden when messages need updating.

Consider extracting the status code → error message mapping into a helper function or dictionary:

def get_upload_error_message(status_code: int) -> tuple[str, str]:
    """Returns (error_message, hint) for given HTTP status code."""
    error_map = {
            "Activity already exists: This activity has already been uploaded to Garmin Connect",
            "Try modifying the activity timestamps or creating a new activity file"
        ),
            "File too large: The activity file exceeds Garmin Connect's size limit",
            "Try compressing the file or reducing the number of data points"
        ),
        # ... other mappings
    }
    return error_map.get(status_code, (f"HTTP Error {status_code}", "Please try again"))

Then use it in both exception handlers to reduce duplication.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c17cd54 and 1ee1664.

📒 Files selected for processing (1)
  • demo.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
demo.py (1)
garminconnect/__init__.py (1)
  • upload_activity (1648-1700)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.10)
🔇 Additional comments (3)
demo.py (3)

1289-1290: LGTM!

The glob import is appropriate for the new file selection functionality.


1292-1312: LGTM!

The interactive file selection logic is well-implemented with proper input validation and clear user feedback. The flow handles edge cases (no files, invalid input, out-of-range selection) appropriately.


1314-1314: LGTM!

The messages correctly reference selected_file instead of the previous static config.activityfile, which is appropriate for the new interactive file selection workflow.

Also applies to: 1327-1327, 1330-1330

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
demo.py (1)

1323-1402: Redundant error handling—call_and_display already handles HTTP errors.

call_and_display (line 1316) wraps safe_api_call (lines 946-1029), which already catches GarthHTTPError and handles status codes 400, 401, 403, 404, 429, 500, 503 with user-friendly messages. The explicit except blocks for requests.exceptions.HTTPError (lines 1326-1358) and the generic Exception block with string matching (lines 1368-1402) duplicate this logic and are largely unreachable, since api.upload_activity raises GarthHTTPError, not requests.exceptions.HTTPError.

This redundancy can lead to double error reporting or maintenance burden as error handling logic diverges between the two layers.

Simplify by removing the redundant HTTP error handling and keeping only the library-specific exceptions:

-    except FileNotFoundError:
-        print(f"❌ File not found: {selected_file if selected_file else 'No file selected'}")
-        print("ℹ️ Please ensure the activity file exists in the current directory")
-    except requests.exceptions.HTTPError as e:
-        if e.response.status_code == 409:
-            print(
-                "⚠️ Activity already exists: This activity has already been uploaded to Garmin Connect"
-            )
-            print("ℹ️ Garmin Connect prevents duplicate activities from being uploaded")
-            print(
-                "💡 Try modifying the activity timestamps or creating a new activity file"
-            )
-        elif e.response.status_code == 413:
-            print(
-                "❌ File too large: The activity file exceeds Garmin Connect's size limit"
-            )
-            print("💡 Try compressing the file or reducing the number of data points")
-        elif e.response.status_code == 422:
-            print(
-                "❌ Invalid file format: The activity file format is not supported or corrupted"
-            )
-            print("ℹ️ Supported formats: FIT, GPX, TCX")
-            print("💡 Try converting to a different format or check file integrity")
-        elif e.response.status_code == 400:
-            print("❌ Bad request: Invalid activity data or malformed file")
-            print(
-                "💡 Check if the activity file contains valid GPS coordinates and timestamps"
-            )
-        elif e.response.status_code == 401:
-            print("❌ Authentication failed: Please login again")
-            print("💡 Your session may have expired")
-        elif e.response.status_code == 429:
-            print("❌ Rate limit exceeded: Too many upload requests")
-            print("💡 Please wait a few minutes before trying again")
-        else:
-            print(f"❌ HTTP Error {e.response.status_code}: {e}")
     except GarminConnectAuthenticationError as e:
         print(f"❌ Authentication error: {e}")
         print("💡 Please check your login credentials and try again")
     except GarminConnectConnectionError as e:
         print(f"❌ Connection error: {e}")
         print("💡 Please check your internet connection and try again")
     except GarminConnectTooManyRequestsError as e:
         print(f"❌ Too many requests: {e}")
         print("💡 Please wait a few minutes before trying again")
-    except Exception as e:
-        error_str = str(e)
-        if "409 Client Error: Conflict" in error_str:
-            print(
-                "⚠️ Activity already exists: This activity has already been uploaded to Garmin Connect"
-            )
-            print("ℹ️ Garmin Connect prevents duplicate activities from being uploaded")
-            print(
-                "💡 Try modifying the activity timestamps or creating a new activity file"
-            )
-        elif "413" in error_str and "Request Entity Too Large" in error_str:
-            print(
-                "❌ File too large: The activity file exceeds Garmin Connect's size limit"
-            )
-            print("💡 Try compressing the file or reducing the number of data points")
-        elif "422" in error_str and "Unprocessable Entity" in error_str:
-            print(
-                "❌ Invalid file format: The activity file format is not supported or corrupted"
-            )
-            print("ℹ️ Supported formats: FIT, GPX, TCX")
-            print("💡 Try converting to a different format or check file integrity")
-        elif "400" in error_str and "Bad Request" in error_str:
-            print("❌ Bad request: Invalid activity data or malformed file")
-            print(
-                "💡 Check if the activity file contains valid GPS coordinates and timestamps"
-            )
-        elif "401" in error_str and "Unauthorized" in error_str:
-            print("❌ Authentication failed: Please login again")
-            print("💡 Your session may have expired")
-        elif "429" in error_str and "Too Many Requests" in error_str:
-            print("❌ Rate limit exceeded: Too many upload requests")
-            print("💡 Please wait a few minutes before trying again")
-        else:
-            print(f"❌ Unexpected error uploading activity: {e}")
-            print("💡 Please check the file format and try again")

If you need custom handling for specific HTTP status codes not covered by safe_api_call, extend the central error handler instead of duplicating logic here.

🧹 Nitpick comments (1)
demo.py (1)

1289-1289: Move import to module level.

Importing glob inside the function works but deviates from Python conventions. Module-level imports improve readability and make dependencies explicit.

Move this import to the top of the file (around line 20) with other standard library imports:

 import datetime
 import json
 import logging
 import os
 import sys
+import glob
 from contextlib import suppress

Then remove line 1289.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ee1664 and 9c05b96.

📒 Files selected for processing (1)
  • demo.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
demo.py (1)
garminconnect/__init__.py (1)
  • upload_activity (1648-1700)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (1)
demo.py (1)

1316-1321: Good fix—activity upload no longer duplicated.

The previous review flagged that the activity was uploaded twice. The current code correctly calls api.upload_activity only once via call_and_display, resolving that issue.

self.activityfile = (
"test_data/sample_activity.gpx" # Supported file types: .fit .gpx .tcx
)
self.activityfile = "test_data/*.gpx" # Supported file types: .fit .gpx .tcx
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

config.activityfile is no longer used in the upload flow.

The glob pattern is defined here but never referenced in upload_activity_file (lines 1287-1403), which now performs its own file discovery via glob.glob("test_data/*.gpx"). Additionally, the menu description at line 227 displays config.activityfile, which now shows a glob pattern instead of a single file path, potentially confusing users.

Consider either:

  1. Remove self.activityfile from Config if it's truly unused, or
  2. Use config.activityfile as the glob pattern in upload_activity_file for consistency:
-        gpx_files = glob.glob("test_data/*.gpx")
+        gpx_files = glob.glob(config.activityfile)

And update the menu description at line 227 to be more accurate:

-                "desc": f"Upload activity data from {config.activityfile}",
+                "desc": "Upload activity data (interactive file selection)",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.activityfile = "test_data/*.gpx" # Supported file types: .fit .gpx .tcx
# In upload_activity_file (replace hard-coded glob with config)
gpx_files = glob.glob(config.activityfile)
Suggested change
self.activityfile = "test_data/*.gpx" # Supported file types: .fit .gpx .tcx
# In the menu definition (make description generic)
"desc": "Upload activity data (interactive file selection)",

Comment on lines +1303 to +1324
while True:
try:
choice = int(input(f"Enter number (1-{len(gpx_files)}): "))
if 1 <= choice <= len(gpx_files):
selected_file = gpx_files[choice - 1]
break
else:
print("Invalid selection. Try again.")
except ValueError:
print("Please enter a valid number.")

# Upload the activity
result = api.upload_activity(config.activityfile)
print(f"📤 Uploading activity from file: {selected_file}")

if result:
print("✅ Activity uploaded successfully!")
call_and_display(
api.upload_activity,
config.activityfile,
method_name="upload_activity",
api_call_desc=f"api.upload_activity({config.activityfile})",
)
else:
print(f"❌ Failed to upload activity from {config.activityfile}")
call_and_display(
api.upload_activity,
selected_file,
method_name="upload_activity",
api_call_desc=f"api.upload_activity({selected_file})",
)

except FileNotFoundError:
print(f"❌ File not found: {config.activityfile}")
print(f"❌ File not found: {selected_file}")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Risk of UnboundLocalError if exception occurs before file selection.

The try block starts at line 1291, but selected_file is only assigned at line 1307 inside the while True loop. If an exception occurs before the user completes the selection (e.g., during glob.glob() or input()), the except FileNotFoundError handler at line 1323 will reference an undefined selected_file, causing an UnboundLocalError.

Initialize selected_file before the selection loop:

     try:
+        selected_file = None  # Initialize before potential exceptions
         # List all .gpx files in test_data
         gpx_files = glob.glob("test_data/*.gpx")

Then update the error message to handle the case where no file was selected:

     except FileNotFoundError:
-        print(f"❌ File not found: {selected_file}")
+        print(f"❌ File not found: {selected_file if selected_file else 'No file selected'}")
         print("ℹ️ Please ensure the activity file exists in the current directory")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while True:
try:
choice = int(input(f"Enter number (1-{len(gpx_files)}): "))
if 1 <= choice <= len(gpx_files):
selected_file = gpx_files[choice - 1]
break
else:
print("Invalid selection. Try again.")
except ValueError:
print("Please enter a valid number.")
# Upload the activity
result = api.upload_activity(config.activityfile)
print(f"📤 Uploading activity from file: {selected_file}")
if result:
print("✅ Activity uploaded successfully!")
call_and_display(
api.upload_activity,
config.activityfile,
method_name="upload_activity",
api_call_desc=f"api.upload_activity({config.activityfile})",
)
else:
print(f"❌ Failed to upload activity from {config.activityfile}")
call_and_display(
api.upload_activity,
selected_file,
method_name="upload_activity",
api_call_desc=f"api.upload_activity({selected_file})",
)
except FileNotFoundError:
print(f"❌ File not found: {config.activityfile}")
print(f"❌ File not found: {selected_file}")
try:
selected_file = None # Initialize before potential exceptions
# List all .gpx files in test_data
gpx_files = glob.glob("test_data/*.gpx")
while True:
try:
choice = int(input(f"Enter number (1-{len(gpx_files)}): "))
if 1 <= choice <= len(gpx_files):
selected_file = gpx_files[choice - 1]
break
else:
print("Invalid selection. Try again.")
except ValueError:
print("Please enter a valid number.")
print(f"📤 Uploading activity from file: {selected_file}")
call_and_display(
api.upload_activity,
selected_file,
method_name="upload_activity",
api_call_desc=f"api.upload_activity({selected_file})",
)
except FileNotFoundError:
print(f"❌ File not found: {selected_file if selected_file else 'No file selected'}")
print("ℹ️ Please ensure the activity file exists in the current directory")
🤖 Prompt for AI Agents
In demo.py around lines 1303 to 1324, selected_file is only set inside the
user-selection loop which can lead to an UnboundLocalError in the
FileNotFoundError except block if an exception occurs earlier; initialize
selected_file to None before the selection loop (or at the start of the
enclosing try) and change the except FileNotFoundError handler to check if
selected_file is None and print a generic "❌ File not found or no file selected"
message, otherwise print the specific filename, ensuring the handler never
references an uninitialized variable.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
demo.py (2)

73-73: Menu description should be updated for the glob pattern.

The glob pattern is correctly used at line 1293, but the menu description at line 227 displays config.activityfile which now shows "test_data/*.gpx" instead of describing the interactive selection feature.

Update the menu description to better reflect the interactive selection:

             "6": {
-                "desc": f"Upload activity data from {config.activityfile}",
+                "desc": "Upload activity data (interactive file selection)",
                 "key": "upload_activity",
             },

1291-1324: Risk of UnboundLocalError if exception occurs before file selection.

selected_file is only assigned at line 1307 inside the while True loop. If an exception occurs before this assignment (e.g., during glob.glob() at line 1293 or input() at line 1305), the except FileNotFoundError handler at line 1324 will reference an undefined variable, causing an UnboundLocalError.

Initialize selected_file before the selection logic:

     try:
+        selected_file = None  # Initialize before potential exceptions
         # List all .gpx files in test_data
         gpx_files = glob.glob(config.activityfile)

Then update the error handler to handle None:

     except FileNotFoundError:
-        print(f"❌ File not found: {selected_file}")
+        if selected_file:
+            print(f"❌ File not found: {selected_file}")
+        else:
+            print("❌ File not found: No file was selected")
         print("ℹ️ Please ensure the activity file exists in the current directory")
🧹 Nitpick comments (2)
demo.py (2)

1289-1289: Move import glob to module level.

The glob module is a standard library and should be imported at the top of the file with other imports for better code organization and consistency.

Move the import to line 34 (after other imports):

 from garminconnect import (
     Garmin,
     GarminConnectAuthenticationError,
     GarminConnectConnectionError,
     GarminConnectTooManyRequestsError,
 )
+import glob

And remove it from line 1289:

 def upload_activity_file(api: Garmin) -> None:
     """Upload activity data from file."""
-    import glob
-
     try:

1326-1402: Consider consolidating duplicate error handling logic.

The error handling for HTTP status codes (409, 413, 422, 400, 401, 429) is duplicated between the requests.exceptions.HTTPError handler (lines 1326-1357) and the generic Exception handler (lines 1370-1402). This creates maintenance overhead as error messages must be updated in two places.

Consider extracting the error message logic into a helper function:

def _format_upload_error(status_code: int, error_str: str) -> tuple[str, str]:
    """Return (emoji, message) for upload error status codes."""
    if status_code == 409 or "409" in error_str:
        return "⚠️", "Activity already exists: This activity has already been uploaded to Garmin Connect"
    elif status_code == 413 or "413" in error_str:
        return "❌", "File too large: The activity file exceeds Garmin Connect's size limit"
    # ... etc
    return "❌", f"Unexpected error uploading activity: {error_str}"

# Then use it in both handlers:
except requests.exceptions.HTTPError as e:
    emoji, msg = _format_upload_error(e.response.status_code, str(e))
    print(f"{emoji} {msg}")
    # print tips...
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c05b96 and 1bd560e.

📒 Files selected for processing (1)
  • demo.py (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
demo.py (1)
garminconnect/__init__.py (1)
  • upload_activity (1648-1700)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.13)
  • GitHub Check: test (3.10)
  • GitHub Check: test (3.12)
  • GitHub Check: test (3.10)
🔇 Additional comments (1)
demo.py (1)

1316-1321: Confirm: Duplicate upload issue is resolved.

The previous review flagged a duplicate upload where api.upload_activity was called twice. This has been correctly fixed - the activity is now uploaded only once via call_and_display.

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.

2 participants