Files
GarminSync/Design.md
2025-08-08 12:33:11 -07:00

9.1 KiB

Of course. The design has been updated to use the python-garminconnect library and now includes a dedicated documentation section with links to all key upstream libraries.


GarminSync Application Design (Python Version)

Basic Info

App Name: GarminSync What it does: A CLI application that downloads .fit files for every activity in Garmin Connect.


Core Features

  1. List all activities (garminsync list --all)
  2. List activities that have not been downloaded (garminsync list --missing)
  3. List activities that have been downloaded (garminsync list --downloaded)
  4. Download all missing activities (garminsync download --missing)

Tech Stack 🐍

  • Frontend: CLI (Python)
  • Backend: Python
  • Database: SQLite (garmin.db)
  • Hosting: Docker container
  • Key Libraries:
    • python-garminconnect: The library for Garmin Connect API communication.
    • typer: A modern and easy-to-use CLI framework (built on click).
    • python-dotenv: For loading credentials from a .env file.
    • sqlalchemy: A robust ORM for database interaction and schema management (using Alembic for migrations). The built-in sqlite3 module can be used for simpler needs.
    • tqdm: For creating user-friendly progress bars.

Data Structure

The core database schema remains the same. It can be defined using raw SQL with the sqlite3 module or, more robustly, with an SQLAlchemy model.

SQLAlchemy Model Example (models.py):

from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.orm import declarative_base

Base = declarative_base()

class Activity(Base):
    __tablename__ = 'activities'
    
    activity_id = Column(Integer, primary_key=True)
    start_time = Column(String, nullable=False)
    filename = Column(String, unique=True, nullable=True)
    downloaded = Column(Boolean, default=False, nullable=False)

User Flow

  1. User launches the container with their credentials: docker run -it --env-file .env -v $(pwd)/data:/app/data garminsync
  2. The application presents a help menu generated by Typer.
  3. User runs a command, e.g., garminsync download --missing.
  4. The application executes the task, showing progress indicators for API calls and downloads.
  5. Upon completion, the application displays a summary of actions taken.

File Structure

A standard Python project structure is recommended.

/garminsync
├── garminsync/              # Main application package
│   ├── __init__.py
│   ├── cli.py             # Typer CLI commands and entrypoint
│   ├── config.py          # Configuration loading (dotenv)
│   ├── database.py        # SQLAlchemy models and sync logic
│   └── garmin.py          # Wrapper for the python-garminconnect client
├── data/                    # Directory for downloaded .fit files and DB
├── tests/                   # Unit and integration tests
├── .env                     # Stores GARMIN_EMAIL/GARMIN_PASSWORD
├── .gitignore
├── Dockerfile
├── README.md
└── requirements.txt         # Pinned Python dependencies

Technical Implementation Notes

  • Architecture: A Python application using Typer for the CLI. Logic is separated into modules for configuration, database interaction, and Garmin communication.
  • Authentication: Credentials from GARMIN_EMAIL and GARMIN_PASSWORD environment variables are loaded via python-dotenv and passed to python-garminconnect.
  • File Naming: Files will be named using Python's f-strings: f"activity_{activity_id}_{timestamp}.fit".
  • Rate Limiting: A simple time.sleep(2) will be inserted between API requests to avoid overloading Garmin's servers.
  • Database: The schema will be managed by SQLAlchemy. Versioned migrations can be handled by Alembic, which integrates with SQLAlchemy.
  • Database Sync: Before any operation, a sync function will use python-garminconnect to fetch the latest activities and reconcile them with the local SQLite database.
  • Package Management: Dependencies and their versions will be pinned in requirements.txt for reproducible builds (pip freeze > requirements.txt).
  • Session Management: python-garminconnect handles authentication and session management.
  • Docker Permissions: Mounting a local ./data directory into the container allows downloaded files and the database to persist. The sudo prefix is generally not required if the user is part of the docker group.

Development Phases (Proposed for Python)

Phase 1: Core Infrastructure

  • Dockerfile creation for a Python environment.
    • Uses Python 3.10 slim base image
    • Sets PYTHONDONTWRITEBYTECODE and PYTHONUNBUFFERED
    • Installs build dependencies
    • Copies requirements first for layer caching
    • Sets ENV_FILE for configuration
    • Entrypoint runs application via python -m garminsync
  • Setup python-dotenv for environment variable handling.
    • Added python-dotenv to requirements.txt
    • Implemented environment loading in config.py:
      from dotenv import load_dotenv
      load_dotenv()  # Load .env file
      
    • Updated Dockerfile to copy .env file during build
    • Added documentation for environment variable setup
  • Initialize the Typer CLI framework in cli.py.
    • Created basic Typer app structure
    • Implemented skeleton commands for list and download
    • Added proper type hints and docstrings
    • Integrated with config.py for environment variable loading
  • Implement the python-garminconnect client wrapper (garmin.py).
    • Created GarminClient class with authentication
    • Implemented get_activities() with rate limiting
    • Implemented download_activity_fit() method
    • Added error handling for missing credentials

Phase 2: Activity Listing

  • Implement the SQLAlchemy schema and database connection.
    • Created database.py with Activity model
    • Implemented init_db() for database initialization
    • Added get_session() for session management
    • Implemented sync_database() to fetch activities from Garmin and update database
  • Create the list commands (--all, --missing, --downloaded).
    • Implemented command logic in cli.py
    • Added database synchronization before listing
    • Added input validation for filter options
    • Used tqdm for progress display

Phase 3: Download Pipeline

  • Implement the .fit file download function using python-garminconnect.
    • Added download logic to cli.py
    • Used GarminClient to download FIT files
    • Created filename-safe timestamps
    • Saved files to data directory
  • Ensure downloads are idempotent (don't re-download existing files).
    • Database tracks downloaded status
    • Only missing activities are downloaded
  • Update the database record to downloaded=True on success.
    • Updated activity record after successful download
    • Set filename path in database

Phase 4: Polish

  • Add tqdm progress bars for the download command.
    • Implemented in both list and download commands
  • Update Dockerfile to:
    • Use multi-stage builds for smaller image size
    • Add health checks
    • Set non-root user for security
  • Implement robust error handling (e.g., API errors, network issues).
    • Basic error handling implemented but needs improvement
  • Write comprehensive README.md documentation.
  • Add unit tests for database and utility functions.

Current Issues

  1. Docker Build Warnings

    • Legacy ENV format warnings during build
    • Solution: Update Dockerfile to use ENV key=value format
  2. Typer Command Parameters

    • Occasional Parameter.make_metavar() errors
    • Resolved by renaming reserved keywords and fixing decorators
  3. Configuration Loading

    • Initial import issues between modules
    • Resolved by restructuring config loading

Documentation 📚

Here are links to the official documentation for the key libraries used in this design.