python v1 - working list, download issue

This commit is contained in:
2025-08-08 12:48:15 -07:00
parent 760868c98c
commit 1dbd1321ff
3 changed files with 116 additions and 93 deletions

View File

@@ -15,13 +15,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
# Copy and install Python dependencies # Copy and install Python dependencies
COPY requirements.txt . COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt
# Copy application code # Copy application code
COPY . . COPY garminsync/ ./garminsync/
# Create data directory
RUN mkdir -p /app/data
# Set environment variables from .env file # Set environment variables from .env file
ENV ENV_FILE=/app/.env ENV ENV_FILE=/app/.env
ENV DATA_DIR=/app/data
# Set the entrypoint to run the CLI # Set the entrypoint to run the CLI
ENTRYPOINT ["python", "-m", "garminsync.cli"] ENTRYPOINT ["python", "-m", "garminsync.cli"]

View File

@@ -1,66 +1,72 @@
import os
import typer import typer
from typing_extensions import Annotated
from .config import load_config from .config import load_config
# Initialize environment variables # Initialize environment variables
load_config() load_config()
app = typer.Typer() app = typer.Typer(help="GarminSync - Download Garmin Connect activities", rich_markup_mode=None)
@app.command(name="list") @app.command("list")
def list_activities( def list_activities(
all: bool = typer.Option(False, "--all", help="List all activities"), all_activities: Annotated[bool, typer.Option("--all", help="List all activities")] = False,
missing: bool = typer.Option(False, "--missing", help="List missing activities"), missing: Annotated[bool, typer.Option("--missing", help="List missing activities")] = False,
downloaded: bool = typer.Option(False, "--downloaded", help="List downloaded activities") downloaded: Annotated[bool, typer.Option("--downloaded", help="List downloaded activities")] = False
): ):
""" """List activities based on specified filters"""
List activities based on specified filters
"""
from tqdm import tqdm from tqdm import tqdm
from .database import get_session, Activity from .database import get_session, Activity
from .garmin import GarminClient from .garmin import GarminClient
# Validate input # Validate input
if not any([all, missing, downloaded]): if not any([all_activities, missing, downloaded]):
typer.echo("Error: Please specify at least one filter option (--all, --missing, --downloaded)") typer.echo("Error: Please specify at least one filter option (--all, --missing, --downloaded)")
raise typer.Exit(code=1) raise typer.Exit(code=1)
client = GarminClient() try:
session = get_session() client = GarminClient()
session = get_session()
# Sync database with latest activities # Sync database with latest activities
typer.echo("Syncing activities from Garmin Connect...") typer.echo("Syncing activities from Garmin Connect...")
from .database import sync_database from .database import sync_database
sync_database(client) sync_database(client)
# Build query based on filters # Build query based on filters
query = session.query(Activity) query = session.query(Activity)
if all: if all_activities:
pass # Return all activities pass # Return all activities
elif missing: elif missing:
query = query.filter_by(downloaded=False) query = query.filter_by(downloaded=False)
elif downloaded: elif downloaded:
query = query.filter_by(downloaded=True) query = query.filter_by(downloaded=True)
# Execute query and display results # Execute query and display results
activities = query.all() activities = query.all()
if not activities: if not activities:
typer.echo("No activities found matching your criteria") typer.echo("No activities found matching your criteria")
return return
# Display results with progress bar # Display results with progress bar
typer.echo(f"Found {len(activities)} activities:") typer.echo(f"Found {len(activities)} activities:")
for activity in tqdm(activities, desc="Listing activities"): for activity in tqdm(activities, desc="Listing activities"):
status = "Downloaded" if activity.downloaded else "Missing" status = "Downloaded" if activity.downloaded else "Missing"
typer.echo(f"- ID: {activity.activity_id}, Start: {activity.start_time}, Status: {status}") typer.echo(f"- ID: {activity.activity_id}, Start: {activity.start_time}, Status: {status}")
@app.command() except Exception as e:
typer.echo(f"Error: {str(e)}")
raise typer.Exit(code=1)
finally:
if 'session' in locals():
session.close()
@app.command("download")
def download( def download(
missing: bool = typer.Option(False, "--missing", help="Download missing activities") missing: Annotated[bool, typer.Option("--missing", help="Download missing activities")] = False
): ):
""" """Download activities based on specified filters"""
Download activities based on specified filters
"""
from tqdm import tqdm from tqdm import tqdm
from pathlib import Path from pathlib import Path
from .database import get_session, Activity from .database import get_session, Activity
@@ -71,50 +77,61 @@ def download(
typer.echo("Error: Currently only --missing downloads are supported") typer.echo("Error: Currently only --missing downloads are supported")
raise typer.Exit(code=1) raise typer.Exit(code=1)
client = GarminClient() try:
session = get_session() client = GarminClient()
session = get_session()
# Sync database with latest activities # Sync database with latest activities
typer.echo("Syncing activities from Garmin Connect...") typer.echo("Syncing activities from Garmin Connect...")
from .database import sync_database from .database import sync_database
sync_database(client) sync_database(client)
# Get missing activities # Get missing activities
activities = session.query(Activity).filter_by(downloaded=False).all() activities = session.query(Activity).filter_by(downloaded=False).all()
if not activities: if not activities:
typer.echo("No missing activities found") typer.echo("No missing activities found")
return return
# Create data directory if it doesn't exist # Create data directory if it doesn't exist
data_dir = Path(os.getenv("DATA_DIR", "data")) data_dir = Path(os.getenv("DATA_DIR", "data"))
data_dir.mkdir(parents=True, exist_ok=True) data_dir.mkdir(parents=True, exist_ok=True)
# Download activities with progress bar # Download activities with progress bar
typer.echo(f"Downloading {len(activities)} missing activities...") typer.echo(f"Downloading {len(activities)} missing activities...")
for activity in tqdm(activities, desc="Downloading"): for activity in tqdm(activities, desc="Downloading"):
try: try:
# Download FIT data # Download FIT data
fit_data = client.download_activity_fit(activity.activity_id) fit_data = client.download_activity_fit(activity.activity_id)
# Create filename-safe timestamp # Create filename-safe timestamp
timestamp = activity.start_time.replace(":", "-").replace(" ", "_") timestamp = activity.start_time.replace(":", "-").replace(" ", "_")
filename = f"activity_{activity.activity_id}_{timestamp}.fit" filename = f"activity_{activity.activity_id}_{timestamp}.fit"
filepath = data_dir / filename filepath = data_dir / filename
# Save file # Save file
with open(filepath, "wb") as f: with open(filepath, "wb") as f:
f.write(fit_data) f.write(fit_data)
# Update database # Update database
activity.filename = str(filepath) activity.filename = str(filepath)
activity.downloaded = True activity.downloaded = True
session.commit() session.commit()
except Exception as e: except Exception as e:
typer.echo(f"Error downloading activity {activity.activity_id}: {str(e)}") typer.echo(f"Error downloading activity {activity.activity_id}: {str(e)}")
session.rollback() session.rollback()
typer.echo("Download completed successfully") typer.echo("Download completed successfully")
except Exception as e:
typer.echo(f"Error: {str(e)}")
raise typer.Exit(code=1)
finally:
if 'session' in locals():
session.close()
def main():
app()
if __name__ == "__main__": if __name__ == "__main__":
app() main()

View File

@@ -1,4 +1,5 @@
typer>=0.12.3,<0.13.0 typer==0.9.0
click==8.1.7
python-dotenv==1.0.0 python-dotenv==1.0.0
garminconnect==0.2.28 garminconnect==0.2.28
sqlalchemy==2.0.23 sqlalchemy==2.0.23