From 052b86bd294dbb886f40649a6792f775cd8b99ca Mon Sep 17 00:00:00 2001 From: Daniel Velazco Date: Mon, 1 Dec 2025 13:57:56 -0800 Subject: [PATCH 1/3] Fix missing warning method in ProgressReporter Add warning() method to ProgressReporter class to fix AttributeError when activities_iterator.py calls self.progress.warning() during sync. --- src/garmy/localdb/progress.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/garmy/localdb/progress.py b/src/garmy/localdb/progress.py index 208530c..4f1125e 100644 --- a/src/garmy/localdb/progress.py +++ b/src/garmy/localdb/progress.py @@ -56,7 +56,11 @@ def task_failed(self, task: str, sync_date: date): def info(self, message: str): """Log info message.""" self.logger.info(message) - + + def warning(self, message: str): + """Log warning message.""" + self.logger.warning(message) + def error(self, message: str): """Log error message.""" self.logger.error(message) From aeba69d12ba0443d426f7aef5ae3ef457aad4e3d Mon Sep 17 00:00:00 2001 From: Daniel Velazco Date: Mon, 1 Dec 2025 13:58:46 -0800 Subject: [PATCH 2/3] Fix MFA prompt not triggering during sync login Pass prompt_mfa callback to AuthClient.login() so users with 2FA enabled are prompted for their MFA code. Previously, prompt_mfa defaulted to None which caused login to silently fail for 2FA accounts. --- src/garmy/localdb/sync.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/garmy/localdb/sync.py b/src/garmy/localdb/sync.py index e71bbe6..a388583 100644 --- a/src/garmy/localdb/sync.py +++ b/src/garmy/localdb/sync.py @@ -43,7 +43,12 @@ def initialize(self, email: str, password: str): from garmy import AuthClient, APIClient auth_client = AuthClient() - auth_client.login(email, password) + auth_client.login( + email, + password, + prompt_mfa=lambda: input("MFA code: "), + ) + self.api_client = APIClient(auth_client=auth_client) self.activities_iterator = ActivitiesIterator( From b6c97e4b7736beebf7fd467dcee5841fcc825dae Mon Sep 17 00:00:00 2001 From: Daniel Velazco Date: Mon, 1 Dec 2025 14:25:56 -0800 Subject: [PATCH 3/3] Fix NULL value constraint failure in timeseries storage --- src/garmy/localdb/db.py | 3 +++ src/garmy/localdb/extractors.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/garmy/localdb/db.py b/src/garmy/localdb/db.py index 46c3868..1e2c324 100644 --- a/src/garmy/localdb/db.py +++ b/src/garmy/localdb/db.py @@ -67,6 +67,9 @@ def store_timeseries_batch(self, user_id: int, metric_type: MetricType, data: Li """Store batch of timeseries data.""" with self.get_session() as session: for timestamp, value, metadata in data: + # Skip entries with None values (NOT NULL constraint) + if value is None: + continue timeseries = TimeSeries( user_id=user_id, metric_type=metric_type.value, diff --git a/src/garmy/localdb/extractors.py b/src/garmy/localdb/extractors.py index c867863..9bdff81 100644 --- a/src/garmy/localdb/extractors.py +++ b/src/garmy/localdb/extractors.py @@ -189,6 +189,8 @@ def extract_timeseries_data(self, data: Any, metric_type: MetricType) -> List[Tu if metric_type == MetricType.BODY_BATTERY: if hasattr(data, 'body_battery_readings') and data.body_battery_readings: for reading in data.body_battery_readings: + if reading.level is None: + continue metadata = { 'status': getattr(reading, 'status', None), 'version': getattr(reading, 'version', None) @@ -198,6 +200,8 @@ def extract_timeseries_data(self, data: Any, metric_type: MetricType) -> List[Tu elif metric_type == MetricType.STRESS: if hasattr(data, 'stress_readings') and data.stress_readings: for reading in data.stress_readings: + if reading.stress_level is None: + continue metadata = {} if hasattr(reading, 'stress_category'): metadata['stress_category'] = reading.stress_category @@ -208,7 +212,8 @@ def extract_timeseries_data(self, data: Any, metric_type: MetricType) -> List[Tu for reading in data.heart_rate_values_array: if isinstance(reading, (list, tuple)) and len(reading) >= 2: timestamp, heart_rate = reading[0], reading[1] - timeseries_data.append((timestamp, heart_rate, {})) + if heart_rate is not None: + timeseries_data.append((timestamp, heart_rate, {})) elif metric_type == MetricType.RESPIRATION: # Respiration might have different format - check if it has readings