Function bodies 373 total
_do_both method · python · L285-L288 (4 LOC)garmin_extract/gui/screens/automation.py
def _do_both(self) -> None:
self._set_buttons_enabled(False)
self._feedback.setText("Uploading CSVs and syncing Sheets...")
Thread(target=self._run_both, daemon=True).start()_run_drive method · python · L290-L301 (12 LOC)garmin_extract/gui/screens/automation.py
def _run_drive(self) -> None:
try:
from garmin_extract._google_drive import upload_csvs_to_drive
result = upload_csvs_to_drive()
if result["ok"]:
names = ", ".join(f["name"] for f in result["files"])
self._signals.drive_op_done.emit(f"\u2713 Uploaded: {names}")
else:
self._signals.drive_op_done.emit(f"Error: {result['error']}")
except Exception as exc:
self._signals.drive_op_done.emit(f"Error: {exc}")_run_sheets method · python · L303-L313 (11 LOC)garmin_extract/gui/screens/automation.py
def _run_sheets(self) -> None:
try:
from garmin_extract._google_drive import sync_to_sheets
result = sync_to_sheets()
if result["ok"]:
self._signals.drive_op_done.emit("\u2713 Google Sheet updated")
else:
self._signals.drive_op_done.emit(f"Error: {result['error']}")
except Exception as exc:
self._signals.drive_op_done.emit(f"Error: {exc}")_run_both method · python · L315-L335 (21 LOC)garmin_extract/gui/screens/automation.py
def _run_both(self) -> None:
try:
from garmin_extract._google_drive import sync_to_sheets, upload_csvs_to_drive
lines = []
drive_result = upload_csvs_to_drive()
if drive_result["ok"]:
names = ", ".join(f["name"] for f in drive_result["files"])
lines.append(f"\u2713 Drive: {names}")
else:
lines.append(f"Drive error: {drive_result['error']}")
sheets_result = sync_to_sheets()
if sheets_result["ok"]:
lines.append("\u2713 Sheets updated")
else:
lines.append(f"Sheets error: {sheets_result['error']}")
self._signals.drive_op_done.emit(" | ".join(lines))
except Exception as exc:
self._signals.drive_op_done.emit(f"Error: {exc}")_on_drive_op_done method · python · L337-L340 (4 LOC)garmin_extract/gui/screens/automation.py
def _on_drive_op_done(self, text: str) -> None:
self._feedback.setText(text)
self._set_buttons_enabled(True)
self.refresh_status()_date_range_label function · python · L26-L30 (5 LOC)garmin_extract/gui/screens/pull_data.py
def _date_range_label(days: int) -> str:
today = date.today()
start = today - timedelta(days=days)
end = today - timedelta(days=1)
return f"{start.isoformat()} \u2192 {end.isoformat()}"_parse_date function · python · L33-L39 (7 LOC)garmin_extract/gui/screens/pull_data.py
def _parse_date(s: str) -> str | None:
for fmt in ("%Y-%m-%d", "%m/%d/%Y", "%m/%d/%y", "%m-%d-%Y", "%m-%d-%y"):
try:
return datetime.strptime(s.strip(), fmt).strftime("%Y-%m-%d")
except ValueError:
continue
return NoneCitation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
_find_fetch_new_range function · python · L42-L58 (17 LOC)garmin_extract/gui/screens/pull_data.py
def _find_fetch_new_range() -> tuple[str, int] | None:
"""Returns (start_iso, days) or None if up to date. Raises FileNotFoundError."""
import re
data_dir = Path(__file__).parent.parent.parent.parent / "data" / "garmin"
if not data_dir.exists():
raise FileNotFoundError("no data directory")
date_re = re.compile(r"^\d{4}-\d{2}-\d{2}\.json$")
dates = sorted(f.stem for f in data_dir.glob("*.json") if date_re.match(f.name))
if not dates:
raise FileNotFoundError("no date files")
latest = date.fromisoformat(dates[-1])
yesterday = date.today() - timedelta(days=1)
start = latest + timedelta(days=1)
if start > yesterday:
return None
return start.isoformat(), (yesterday - start).days + 1_SectionHeader class · python · L64-L73 (10 LOC)garmin_extract/gui/screens/pull_data.py
class _SectionHeader(QLabel):
"""A dim section header label (SYNC, RECENT, CUSTOM, etc.)."""
def __init__(self, text: str, parent: QWidget | None = None) -> None:
super().__init__(text, parent)
self.setStyleSheet(
"font-size: 12px; font-weight: bold; color: #6c7086;"
" letter-spacing: 1px; background: transparent;"
" border-bottom: 1px solid #45475a; padding-bottom: 4px;"
)__init__ method · python · L67-L73 (7 LOC)garmin_extract/gui/screens/pull_data.py
def __init__(self, text: str, parent: QWidget | None = None) -> None:
super().__init__(text, parent)
self.setStyleSheet(
"font-size: 12px; font-weight: bold; color: #6c7086;"
" letter-spacing: 1px; background: transparent;"
" border-bottom: 1px solid #45475a; padding-bottom: 4px;"
)_ActionButton class · python · L79-L103 (25 LOC)garmin_extract/gui/screens/pull_data.py
class _ActionButton(QPushButton):
"""A menu-style button matching the TUI's pull options."""
def __init__(self, label: str, hint: str = "", parent: QWidget | None = None) -> None:
display = f"{label} {hint}" if hint else label
super().__init__(display, parent)
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.setStyleSheet("""
QPushButton {
text-align: left;
padding: 10px 16px;
background-color: transparent;
border: 1px solid transparent;
border-radius: 6px;
font-size: 14px;
color: #cdd6f4;
}
QPushButton:hover {
background-color: #313244;
border-color: #45475a;
}
QPushButton:pressed {
background-color: #45475a;
}
""")__init__ method · python · L82-L103 (22 LOC)garmin_extract/gui/screens/pull_data.py
def __init__(self, label: str, hint: str = "", parent: QWidget | None = None) -> None:
display = f"{label} {hint}" if hint else label
super().__init__(display, parent)
self.setCursor(Qt.CursorShape.PointingHandCursor)
self.setStyleSheet("""
QPushButton {
text-align: left;
padding: 10px 16px;
background-color: transparent;
border: 1px solid transparent;
border-radius: 6px;
font-size: 14px;
color: #cdd6f4;
}
QPushButton:hover {
background-color: #313244;
border-color: #45475a;
}
QPushButton:pressed {
background-color: #45475a;
}
""")_DateDialog class · python · L109-L188 (80 LOC)garmin_extract/gui/screens/pull_data.py
class _DateDialog(QDialog):
"""Dialog for entering a start date and optional day count."""
def __init__(
self,
title: str,
show_days: bool = False,
hint: str = "",
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
self.setWindowTitle(title)
self.setMinimumWidth(400)
self.setModal(True)
self.result_start: str = ""
self.result_days: int = 1
layout = QVBoxLayout(self)
layout.setSpacing(10)
if hint:
hint_label = QLabel(hint)
hint_label.setWordWrap(True)
hint_label.setStyleSheet("color: #6c7086; font-size: 13px;")
layout.addWidget(hint_label)
layout.addWidget(QLabel("Start date"))
self._date_input = QLineEdit()
self._date_input.setPlaceholderText("YYYY-MM-DD or MM/DD/YYYY")
layout.addWidget(self._date_input)
if show_days:
layout.addWidget(QLabel(__init__ method · python · L112-L164 (53 LOC)garmin_extract/gui/screens/pull_data.py
def __init__(
self,
title: str,
show_days: bool = False,
hint: str = "",
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
self.setWindowTitle(title)
self.setMinimumWidth(400)
self.setModal(True)
self.result_start: str = ""
self.result_days: int = 1
layout = QVBoxLayout(self)
layout.setSpacing(10)
if hint:
hint_label = QLabel(hint)
hint_label.setWordWrap(True)
hint_label.setStyleSheet("color: #6c7086; font-size: 13px;")
layout.addWidget(hint_label)
layout.addWidget(QLabel("Start date"))
self._date_input = QLineEdit()
self._date_input.setPlaceholderText("YYYY-MM-DD or MM/DD/YYYY")
layout.addWidget(self._date_input)
if show_days:
layout.addWidget(QLabel("Number of days"))
self._days_input = QLineEdit()
self._days_input.setPl_validate method · python · L166-L188 (23 LOC)garmin_extract/gui/screens/pull_data.py
def _validate(self) -> None:
raw = self._date_input.text().strip()
parsed = _parse_date(raw)
if not parsed:
self._error.setText(f"Cannot parse '{raw}' — try 2025-04-07")
self._error.show()
return
self.result_start = parsed
if self._days_input:
days_raw = self._days_input.text().strip()
self.result_days = int(days_raw) if days_raw.isdigit() and int(days_raw) > 0 else 1
else:
start_dt = datetime.strptime(parsed, "%Y-%m-%d").date()
yesterday = date.today() - timedelta(days=1)
self.result_days = (yesterday - start_dt).days + 1
if self.result_days <= 0:
self._error.setText("Start date must be before today.")
self._error.show()
return
self.accept()All rows scored by the Repobility analyzer (https://repobility.com)
PullDataPage class · python · L194-L366 (173 LOC)garmin_extract/gui/screens/pull_data.py
class PullDataPage(QWidget):
"""The Pull Data page shown in the main window's stacked widget."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(40, 32, 40, 32)
layout.setSpacing(4)
heading = QLabel("Pull Data")
heading.setObjectName("heading")
layout.addWidget(heading)
sub = QLabel("Download your Garmin health metrics")
sub.setObjectName("subheading")
layout.addWidget(sub)
layout.addSpacing(16)
# ── SYNC ──────────────────────────────────────
layout.addWidget(_SectionHeader("SYNC"))
btn = _ActionButton("Fetch new", "pull all dates not yet in local data")
btn.clicked.connect(self._fetch_new)
layout.addWidget(btn)
layout.addSpacing(8)
# ── RECENT ────────────────────────────────────
layout.addWidget(_SectionHeader("RECENT"))
b__init__ method · python · L197-L264 (68 LOC)garmin_extract/gui/screens/pull_data.py
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(40, 32, 40, 32)
layout.setSpacing(4)
heading = QLabel("Pull Data")
heading.setObjectName("heading")
layout.addWidget(heading)
sub = QLabel("Download your Garmin health metrics")
sub.setObjectName("subheading")
layout.addWidget(sub)
layout.addSpacing(16)
# ── SYNC ──────────────────────────────────────
layout.addWidget(_SectionHeader("SYNC"))
btn = _ActionButton("Fetch new", "pull all dates not yet in local data")
btn.clicked.connect(self._fetch_new)
layout.addWidget(btn)
layout.addSpacing(8)
# ── RECENT ────────────────────────────────────
layout.addWidget(_SectionHeader("RECENT"))
btn = _ActionButton("Yesterday", _yesterday())
btn.clicked.connect(self._pull_yesterday)
_start_pull method · python · L268-L281 (14 LOC)garmin_extract/gui/screens/pull_data.py
def _start_pull(self, start_date: str, days: int, label: str, **kwargs: object) -> None:
"""Open the pull progress dialog."""
from garmin_extract.gui.screens.pull_progress import PullProgressDialog
dlg = PullProgressDialog(
start_date=start_date,
days=days,
label=label,
no_skip=bool(kwargs.get("no_skip")),
rebuild_only=bool(kwargs.get("rebuild_only")),
zip_path=str(kwargs.get("zip_path", "")),
parent=self,
)
dlg.exec()_fetch_new method · python · L283-L309 (27 LOC)garmin_extract/gui/screens/pull_data.py
def _fetch_new(self) -> None:
try:
result = _find_fetch_new_range()
except FileNotFoundError:
dlg = _DateDialog(
"Full History Pull",
hint="No local data found. Choose a start date to begin pulling.",
parent=self,
)
if dlg.exec() == QDialog.DialogCode.Accepted:
end = date.fromisoformat(dlg.result_start) + timedelta(days=dlg.result_days - 1)
self._start_pull(
dlg.result_start,
dlg.result_days,
f"Full history ({dlg.result_start} \u2192 {end.isoformat()})",
)
return
if result is None:
QMessageBox.information(
self, "Fetch New", "Already up to date \u2014 no new dates to pull."
)
return
start, days = result
end = (date.fromisoformat(start) + timedelta(days=days - 1)).isoformat()
_pull_yesterday method · python · L311-L313 (3 LOC)garmin_extract/gui/screens/pull_data.py
def _pull_yesterday(self) -> None:
yest = _yesterday()
self._start_pull(yest, 1, f"Yesterday ({yest})")_pull_7 method · python · L315-L317 (3 LOC)garmin_extract/gui/screens/pull_data.py
def _pull_7(self) -> None:
start = (date.today() - timedelta(days=7)).isoformat()
self._start_pull(start, 7, f"Last 7 days ({_date_range_label(7)})")_pull_30 method · python · L319-L321 (3 LOC)garmin_extract/gui/screens/pull_data.py
def _pull_30(self) -> None:
start = (date.today() - timedelta(days=30)).isoformat()
self._start_pull(start, 30, f"Last 30 days ({_date_range_label(30)})")_pull_custom method · python · L323-L335 (13 LOC)garmin_extract/gui/screens/pull_data.py
def _pull_custom(self) -> None:
dlg = _DateDialog(
"Custom Date Pull",
show_days=True,
hint="Formats: YYYY-MM-DD \u00b7 MM/DD/YYYY \u00b7 MM/DD/YY",
parent=self,
)
if dlg.exec() == QDialog.DialogCode.Accepted:
self._start_pull(
dlg.result_start,
dlg.result_days,
f"Custom ({dlg.result_start}, {dlg.result_days}d)",
)Open data scored by Repobility · https://repobility.com
_pull_history method · python · L337-L352 (16 LOC)garmin_extract/gui/screens/pull_data.py
def _pull_history(self) -> None:
dlg = _DateDialog(
"Full History Pull",
hint=(
"Pulls every day from your chosen start date through yesterday.\n"
"Formats: YYYY-MM-DD \u00b7 MM/DD/YYYY \u00b7 MM/DD/YY"
),
parent=self,
)
if dlg.exec() == QDialog.DialogCode.Accepted:
yesterday = (date.today() - timedelta(days=1)).isoformat()
self._start_pull(
dlg.result_start,
dlg.result_days,
f"Full history ({dlg.result_start} \u2192 {yesterday})",
)_import_zip method · python · L354-L363 (10 LOC)garmin_extract/gui/screens/pull_data.py
def _import_zip(self) -> None:
path, _ = QFileDialog.getOpenFileName(
self,
"Select Garmin Export",
"",
"Zip files (*.zip);;All files (*)",
)
if not path:
return
self._start_pull("", 0, "Garmin bulk export import", zip_path=path)_PullSignals class · python · L36-L43 (8 LOC)garmin_extract/gui/screens/pull_progress.py
class _PullSignals(QObject):
log_line = Signal(str)
day_start = Signal(str, int) # date_str, n_metrics
day_skipped = Signal(str) # date_str
metric_done = Signal(str, bool) # name, is_ok
mfa_needed = Signal()
finished = Signal(int) # return code
error = Signal(str)_MfaDialog class · python · L49-L97 (49 LOC)garmin_extract/gui/screens/pull_progress.py
class _MfaDialog(QDialog):
"""Modal dialog to capture an MFA code."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("MFA Code Required")
self.setMinimumWidth(400)
self.setModal(True)
layout = QVBoxLayout(self)
layout.setSpacing(10)
title = QLabel("MFA Code Required")
title.setStyleSheet("font-size: 16px; font-weight: bold; color: #f9e2af;")
layout.addWidget(title)
hint = QLabel("Check your email for a 6-digit code and enter it below.")
hint.setStyleSheet("color: #6c7086;")
layout.addWidget(hint)
self._code_input = QLineEdit()
self._code_input.setPlaceholderText("000000")
self._code_input.setMaxLength(6)
self._code_input.returnPressed.connect(self._submit)
layout.addWidget(self._code_input)
self._error = QLabel()
self._error.setStyleSheet("color: #f38ba8;")
s__init__ method · python · L52-L88 (37 LOC)garmin_extract/gui/screens/pull_progress.py
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("MFA Code Required")
self.setMinimumWidth(400)
self.setModal(True)
layout = QVBoxLayout(self)
layout.setSpacing(10)
title = QLabel("MFA Code Required")
title.setStyleSheet("font-size: 16px; font-weight: bold; color: #f9e2af;")
layout.addWidget(title)
hint = QLabel("Check your email for a 6-digit code and enter it below.")
hint.setStyleSheet("color: #6c7086;")
layout.addWidget(hint)
self._code_input = QLineEdit()
self._code_input.setPlaceholderText("000000")
self._code_input.setMaxLength(6)
self._code_input.returnPressed.connect(self._submit)
layout.addWidget(self._code_input)
self._error = QLabel()
self._error.setStyleSheet("color: #f38ba8;")
self._error.hide()
layout.addWidget(self._error)
btn_layout_submit method · python · L90-L97 (8 LOC)garmin_extract/gui/screens/pull_progress.py
def _submit(self) -> None:
code = self._code_input.text().strip()
if not code:
self._error.setText("Please enter the 6-digit code.")
self._error.show()
return
MFA_FILE.write_text(code + "\n")
self.accept()_RuntimeCredsDialog class · python · L103-L164 (62 LOC)garmin_extract/gui/screens/pull_progress.py
class _RuntimeCredsDialog(QDialog):
"""Dialog for entering credentials at runtime when none are saved."""
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("Enter Garmin Connect Credentials")
self.setMinimumWidth(450)
self.setModal(True)
self.result_email = ""
self.result_password = ""
layout = QVBoxLayout(self)
layout.setSpacing(10)
hint = QLabel("No saved credentials found. These will not be stored.")
hint.setStyleSheet("color: #6c7086;")
layout.addWidget(hint)
layout.addWidget(QLabel("Email"))
self._email = QLineEdit()
self._email.setPlaceholderText("[email protected]")
layout.addWidget(self._email)
layout.addWidget(QLabel("Password"))
self._password = QLineEdit()
self._password.setEchoMode(QLineEdit.EchoMode.Password)
self._password.setPlaceholderText("Garmin Connect __init__ method · python · L106-L147 (42 LOC)garmin_extract/gui/screens/pull_progress.py
def __init__(self, parent: QWidget | None = None) -> None:
super().__init__(parent)
self.setWindowTitle("Enter Garmin Connect Credentials")
self.setMinimumWidth(450)
self.setModal(True)
self.result_email = ""
self.result_password = ""
layout = QVBoxLayout(self)
layout.setSpacing(10)
hint = QLabel("No saved credentials found. These will not be stored.")
hint.setStyleSheet("color: #6c7086;")
layout.addWidget(hint)
layout.addWidget(QLabel("Email"))
self._email = QLineEdit()
self._email.setPlaceholderText("[email protected]")
layout.addWidget(self._email)
layout.addWidget(QLabel("Password"))
self._password = QLineEdit()
self._password.setEchoMode(QLineEdit.EchoMode.Password)
self._password.setPlaceholderText("Garmin Connect password")
self._password.returnPressed.connect(self._submit)
layout.addWidget(self._password)
All rows above produced by Repobility · https://repobility.com
_submit method · python · L149-L164 (16 LOC)garmin_extract/gui/screens/pull_progress.py
def _submit(self) -> None:
email = self._email.text().strip()
password = self._password.text().strip()
if not email:
self._error.setText("Email is required.")
self._error.show()
self._email.setFocus()
return
if not password:
self._error.setText("Password is required.")
self._error.show()
self._password.setFocus()
return
self.result_email = email
self.result_password = password
self.accept()_DayState class · python · L171-L176 (6 LOC)garmin_extract/gui/screens/pull_progress.py
class _DayState:
date_str: str
total: int = 0
done: int = 0
failed: int = 0
status: str = "pending" # pending | active | done | skipped__init__ method · python · L185-L236 (52 LOC)garmin_extract/gui/screens/pull_progress.py
def __init__(
self,
start_date: str,
days: int,
label: str,
no_skip: bool = False,
rebuild_only: bool = False,
zip_path: str = "",
parent: QWidget | None = None,
) -> None:
super().__init__(parent)
self.setWindowTitle(label)
self.setMinimumSize(900, 600)
self.resize(1000, 700)
self.setModal(True)
self._start_date = start_date
self._days = days
self._label = label
self._no_skip = no_skip
self._rebuild_only = rebuild_only
self._zip_path = zip_path
self._proc: subprocess.Popen | None = None
self._mfa_shown = False
self._email = ""
self._password = ""
# Display mode
if days > 1:
self._mode = "day"
self._day_states = self._init_day_states()
self._current_day_index = -1
self._metrics_per_day = 0
elif days == 1:
self._m_build_ui method · python · L238-L331 (94 LOC)garmin_extract/gui/screens/pull_progress.py
def _build_ui(self) -> None:
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# ── Split view: log + metrics ─────────────────
splitter = QSplitter(Qt.Orientation.Horizontal)
# Log panel (left)
self._log = QTextEdit()
self._log.setReadOnly(True)
self._log.setStyleSheet(
"background-color: #1e1e2e; color: #cdd6f4; font-family: monospace;"
" font-size: 13px; border: none; border-right: 1px solid #45475a;"
)
splitter.addWidget(self._log)
# Metrics panel (right)
right = QWidget()
right_layout = QVBoxLayout(right)
right_layout.setContentsMargins(12, 12, 12, 12)
self._header = QLabel(self._label)
self._header.setStyleSheet(
"font-size: 16px; font-weight: bold; color: #89b4fa; background: transparent;"
)
right_layout.addWidget(self._header)
self._subheade_wire_signals method · python · L333-L341 (9 LOC)garmin_extract/gui/screens/pull_progress.py
def _wire_signals(self) -> None:
self._signals = _PullSignals()
self._signals.log_line.connect(self._on_log_line)
self._signals.day_start.connect(self._on_day_start)
self._signals.day_skipped.connect(self._on_day_skipped)
self._signals.metric_done.connect(self._on_metric_done)
self._signals.mfa_needed.connect(self._on_mfa_needed)
self._signals.finished.connect(self._on_finished)
self._signals.error.connect(self._on_error)_check_creds_and_start method · python · L345-L360 (16 LOC)garmin_extract/gui/screens/pull_progress.py
def _check_creds_and_start(self) -> None:
from garmin_extract._credentials import load_credentials
email, password = load_credentials()
if password:
self._email = email
self._password = password
self._start_pull()
else:
dlg = _RuntimeCredsDialog(self)
if dlg.exec() == QDialog.DialogCode.Accepted:
self._email = dlg.result_email
self._password = dlg.result_password
self._start_pull()
else:
self.reject()_start_pull method · python · L364-L366 (3 LOC)garmin_extract/gui/screens/pull_progress.py
def _start_pull(self) -> None:
cmd = self._build_cmd()
Thread(target=self._run_pull, args=(cmd,), daemon=True).start()_build_cmd method · python · L368-L390 (23 LOC)garmin_extract/gui/screens/pull_progress.py
def _build_cmd(self) -> list[str]:
if self._rebuild_only:
return [sys.executable, "-u", str(SCRIPTS_ROOT / "reports" / "build_garmin_csvs.py")]
if self._zip_path:
return [
sys.executable,
"-u",
str(SCRIPTS_ROOT / "pullers" / "garmin_import_export.py"),
"--zip",
self._zip_path,
]
cmd = [
sys.executable,
"-u",
str(SCRIPTS_ROOT / "pullers" / "garmin.py"),
"--date",
self._start_date,
"--days",
str(self._days),
]
if self._no_skip:
cmd.append("--no-skip")
return cmdCitation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
_run_pull method · python · L392-L421 (30 LOC)garmin_extract/gui/screens/pull_progress.py
def _run_pull(self, cmd: list[str]) -> None:
env = os.environ.copy()
env["PYTHONUTF8"] = "1" # force UTF-8 I/O in subprocess (Windows cp1252 can't encode ✓)
if self._email:
env["GARMIN_EMAIL"] = self._email
if self._password:
env["GARMIN_PASSWORD"] = self._password
try:
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
stdin=subprocess.DEVNULL,
encoding="utf-8",
cwd=str(ROOT),
env=env,
)
self._proc = proc
assert proc.stdout is not None
for raw_line in iter(proc.stdout.readline, ""):
line = raw_line.rstrip("\n")
self._signals.log_line.emit(line)
self._parse_line(line)
proc.wait()
self._proc = None
self._signals.finished.emit(proc.returnc_parse_line method · python · L423-L457 (35 LOC)garmin_extract/gui/screens/pull_progress.py
def _parse_line(self, line: str) -> None:
"""Parse subprocess output and emit appropriate signals."""
stripped = line.strip()
# "[2025-04-06] Pulling N metrics..."
if stripped.startswith("[") and "] Pulling " in stripped and "metrics" in stripped:
try:
date_str = stripped[1 : stripped.index("]")]
parts = stripped.split()
idx = next((i for i, p in enumerate(parts) if p == "Pulling"), -1)
n = int(parts[idx + 1]) if idx >= 0 else 0
self._signals.day_start.emit(date_str, n)
except (ValueError, IndexError):
pass
return
# "[2025-04-06] Already pulled — skipping"
if "Already pulled" in stripped and "skipping" in stripped:
try:
date_str = stripped[1 : stripped.index("]")]
self._signals.day_skipped.emit(date_str)
except ValueError:
pass
_init_day_states method · python · L461-L469 (9 LOC)garmin_extract/gui/screens/pull_progress.py
def _init_day_states(self) -> list[_DayState]:
states: list[_DayState] = []
try:
start = date.fromisoformat(self._start_date)
for i in range(self._days):
states.append(_DayState((start + timedelta(days=i)).isoformat()))
except ValueError:
pass
return states_initial_panel_body method · python · L471-L476 (6 LOC)garmin_extract/gui/screens/pull_progress.py
def _initial_panel_body(self) -> str:
if self._mode == "day":
return self._render_day_list()
if self._mode == "metric":
return "Waiting for first metric..."
return "Starting..."_on_day_start method · python · L483-L512 (30 LOC)garmin_extract/gui/screens/pull_progress.py
def _on_day_start(self, date_str: str, n_metrics: int) -> None:
if self._mode == "day":
idx = next(
(i for i, s in enumerate(self._day_states) if s.date_str == date_str),
-1,
)
if idx == -1:
self._day_states.append(_DayState(date_str))
idx = len(self._day_states) - 1
self._current_day_index = idx
self._day_states[idx].status = "active"
self._day_states[idx].total = n_metrics
if self._metrics_per_day == 0 and n_metrics > 0:
self._metrics_per_day = n_metrics
self._overall_total = self._days * n_metrics
self._progress.setMaximum(self._overall_total)
self._refresh_panel()
self._status.setText(f"Day {idx + 1} of {self._days} \u00b7 {date_str}")
elif self._mode == "metric":
self._day_total = n_metrics
self._day_done = 0
_on_day_skipped method · python · L514-L526 (13 LOC)garmin_extract/gui/screens/pull_progress.py
def _on_day_skipped(self, date_str: str) -> None:
if self._mode == "day":
idx = next(
(i for i, s in enumerate(self._day_states) if s.date_str == date_str),
-1,
)
if idx >= 0:
self._day_states[idx].status = "skipped"
advance = self._metrics_per_day if self._metrics_per_day else 1
self._overall_done += advance
self._progress.setValue(self._overall_done)
self._refresh_panel()
self._status.setText(f"Skipped {date_str} (already pulled)")_on_metric_done method · python · L528-L556 (29 LOC)garmin_extract/gui/screens/pull_progress.py
def _on_metric_done(self, name: str, is_ok: bool) -> None:
if self._mode == "metric":
self._live_metrics.append((name, "done" if is_ok else "fail"))
self._day_done += 1
self._progress.setValue(self._day_done)
self._refresh_panel()
self._status.setText(f"{self._day_done} / {self._day_total or '?'} metrics")
elif self._mode == "day" and self._current_day_index >= 0:
ds = self._day_states[self._current_day_index]
if is_ok:
ds.done += 1
else:
ds.failed += 1
tally = ds.done + ds.failed
if ds.total == 0 or tally <= ds.total:
self._overall_done += 1
self._progress.setValue(self._overall_done)
if ds.total > 0 and tally >= ds.total:
ds.status = "done"
self._refresh_panel()
self._status.setText(
f"Day {self._current_day_inde_on_mfa_needed method · python · L558-L561 (4 LOC)garmin_extract/gui/screens/pull_progress.py
def _on_mfa_needed(self) -> None:
if not self._mfa_shown:
self._mfa_shown = True
_MfaDialog(self).exec()All rows scored by the Repobility analyzer (https://repobility.com)
_on_finished method · python · L563-L581 (19 LOC)garmin_extract/gui/screens/pull_progress.py
def _on_finished(self, returncode: int) -> None:
if self._mode == "simple":
self._progress.setMaximum(1)
self._progress.setValue(1)
if returncode == 0:
self._metric_list.setText("\u2713 Complete")
else:
self._metric_list.setText("\u2717 Failed")
if returncode == 0:
self._status.setText("Complete \u2713")
self._log.append("\nDone.")
else:
self._status.setText(f"Finished with errors (exit {returncode})")
self._log.append(f"\nProcess exited with code {returncode}.")
self._cancel_btn.setText("Close")
self._cancel_btn.clicked.disconnect()
self._cancel_btn.clicked.connect(self.accept)_on_error method · python · L583-L588 (6 LOC)garmin_extract/gui/screens/pull_progress.py
def _on_error(self, msg: str) -> None:
self._log.append(f"\nError: {msg}")
self._status.setText("Error")
self._cancel_btn.setText("Close")
self._cancel_btn.clicked.disconnect()
self._cancel_btn.clicked.connect(self.accept)_refresh_panel method · python · L592-L596 (5 LOC)garmin_extract/gui/screens/pull_progress.py
def _refresh_panel(self) -> None:
if self._mode == "day":
self._metric_list.setText(self._render_day_list())
elif self._mode == "metric":
self._metric_list.setText(self._render_metric_list())