-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsync_weights.py
More file actions
158 lines (128 loc) · 4.82 KB
/
sync_weights.py
File metadata and controls
158 lines (128 loc) · 4.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""
Historical weight measurements sync script for MyFitnessPal to Postgres.
This script syncs weight measurements from MyFitnessPal to Postgres database.
Use this for initial historical backfill. After that, regular sync.py will
include weight measurements automatically.
"""
import sys
from datetime import date, timedelta
try:
import config
except ImportError:
print("\n" + "=" * 60)
print("ERROR: config.py not found!")
print("=" * 60)
print("\nPlease follow these steps:")
print("1. Copy config.example.py to config.py")
print("2. Edit config.py with your Postgres credentials")
print("3. Run this script again")
sys.exit(1)
try:
import myfitnesspal
except ImportError:
print("\n" + "=" * 60)
print("ERROR: myfitnesspal library not installed")
print("=" * 60)
print("\nPlease install it:")
print(" pip install myfitnesspal")
print("\nOr install all dependencies:")
print(" pip install -r requirements.txt")
sys.exit(1)
from auth import ensure_logged_in
from db_schema import upsert_weight_measurement
from models import parse_weight_measurement
def get_date_value(date_config):
"""
Get actual date value from config (handles callables, date objects, and strings).
Args:
date_config: Can be date object, callable, or string
Returns:
date object
"""
if callable(date_config):
return date_config()
elif isinstance(date_config, date):
return date_config
elif isinstance(date_config, str):
# Parse string format: YYYY-MM-DD
return date.fromisoformat(date_config)
else:
raise ValueError(f"Invalid date configuration: {date_config}")
def main():
"""Main weight sync function. Returns exit code for Task Scheduler."""
print("=" * 60)
print("MyFitnessPal Weight Measurements Sync")
print("=" * 60)
# Authenticate
print("\n" + "=" * 60)
print("AUTHENTICATION")
print("=" * 60)
try:
client = ensure_logged_in()
except TimeoutError as e:
print(f"\n[TIMEOUT] {e}")
return 2 # Exit code 2 = Timeout waiting for login
except Exception as e:
print(f"\n[ERROR] Authentication failed: {e}")
return 1 # Exit code 1 = Authentication error
# Get date range from config
start_date = get_date_value(config.START_DATE)
end_date = get_date_value(config.END_DATE)
# Validate date range
if start_date > end_date:
print(f"\nERROR: START_DATE ({start_date}) cannot be after END_DATE ({end_date})")
return 1
# Calculate days in range
days_in_range = (end_date - start_date).days + 1
print("\n" + "=" * 60)
print("SYNCING WEIGHT MEASUREMENTS")
print("=" * 60)
print(f"Database: {config.POSTGRES_CONFIG['database']}")
print(f"Date range: {start_date} to {end_date} ({days_in_range} days)")
print("=" * 60)
total_measurements = 0
errors = []
try:
print(f"\nFetching weight measurements... ", end='', flush=True)
# Get all weight measurements for the date range
# Returns OrderedDict with date as key, float as value
measurements = client.get_measurements('Weight', start_date, end_date)
print(f"Found {len(measurements)} measurements")
# Upsert each measurement
if measurements:
print("\nSyncing measurements:")
for measurement_date, weight_value in measurements.items():
try:
# Parse the measurement
data = parse_weight_measurement(measurement_date, weight_value)
# Upsert to database
upsert_weight_measurement(data)
total_measurements += 1
print(f" {measurement_date}: {weight_value} lbs")
except Exception as e:
error_msg = f"{measurement_date}: {e}"
errors.append(error_msg)
print(f" ERROR: {error_msg}")
else:
print("\nNo weight measurements found in date range")
except Exception as e:
print(f"\nERROR: Failed to fetch measurements: {e}")
return 1
# Summary
print("\n" + "=" * 60)
print("SYNC COMPLETE")
print("=" * 60)
print(f"Total weight measurements synced: {total_measurements}")
if errors:
print(f"\nWarning: {len(errors)} errors encountered")
print("\nErrors:")
for error in errors:
print(f" - {error}")
return 3 # Exit code 3 = Partial success (some errors)
else:
print("\nNo errors!")
return 0 # Exit code 0 = Success
print("\n" + "=" * 60)
if __name__ == "__main__":
exit_code = main()
sys.exit(exit_code)