Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 90 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,104 @@
# Garth

> **Garth is deprecated and no longer maintained.** Garmin changed their auth
> flow, breaking the mobile auth approach that Garth depends on. I'm not in a
> position to dedicate the time to adapt to these changes. See the
> [announcement](https://github.com/matin/garth/discussions/222) for details.
> Anyone is welcome to fork Garth as a starting point for a new library.

[![CI](https://github.com/matin/garth/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/matin/garth/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain+workflow%3ACI)
[![codecov](https://codecov.io/gh/matin/garth/branch/main/graph/badge.svg?token=0EFFYJNFIL)](https://codecov.io/gh/matin/garth)
[![PyPI version](https://img.shields.io/pypi/v/garth.svg?logo=python&logoColor=brightgreen&color=brightgreen)](https://pypi.org/project/garth/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/garth)](https://pypistats.org/packages/garth)
[![Documentation](https://img.shields.io/badge/docs-readthedocs-blue)](https://garth.readthedocs.io)

Garmin SSO auth + Connect Python client

## About
## Features

- OAuth1/OAuth2 authentication (OAuth1 token lasts ~1 year)
- MFA support with custom handlers
- Auto-refresh of OAuth2 token
- Auto-resume from `GARTH_HOME` or `GARTH_TOKEN` environment variables
- Works on Google Colab
- Pydantic dataclasses for validated data
- Built-in telemetry for diagnosing auth issues
- Full test coverage

## Installation

```bash
pip install garth
```

## Quick Start

### Authenticate and save session

```python
import garth
from getpass import getpass

garth.login(input("Email: "), getpass("Password: "))
garth.save("~/.garth")
```

### Resume session

```python
import garth
from garth.exc import GarthException

Garth was a Python library for Garmin Connect API access with OAuth
authentication. It reached 350k+ downloads per month and was translated into
multiple programming languages.
garth.resume("~/.garth")
try:
garth.client.username
except GarthException:
# Session is expired. You'll need to log in again
pass
```

Garmin recently changed their auth flow, breaking the mobile auth approach
that Garth and other libraries depend on
([#217](https://github.com/matin/garth/issues/217)). This is the final
release.
Or use environment variables for automatic session restoration:

## For existing users
```bash
export GARTH_HOME=~/.garth
# or
export GARTH_TOKEN="eyJvYXV0aF90b2tlbi..." # from `uvx garth login`
```

If you already have a saved session with a valid OAuth1 token, Garth may
continue to work until that token expires (~1 year from when it was issued).
New logins will not work.
```python
import garth
# Session is automatically loaded
garth.client.username
```

### Fetch data

```python
# Get daily stress
garth.DailyStress.list("2023-07-23", 7)

# Get sleep data
garth.SleepData.get("2023-07-20")

# Get weight
garth.WeightData.list("2025-06-01", 30)

# Direct API calls
garth.connectapi("/usersummary-service/stats/stress/weekly/2023-07-05/52")
```

## Documentation

Documentation is still available at
**[garth.readthedocs.io](https://garth.readthedocs.io)** for reference.
Full documentation at **[garth.readthedocs.io](https://garth.readthedocs.io)**

## MCP Server

[`garth-mcp-server`](https://github.com/matin/garth-mcp-server) is in early development.

To generate your `GARTH_TOKEN`, use `uvx garth login`.

## Star History

<!-- markdownlint-disable MD013 -->
<a href="https://www.star-history.com/#matin/garth&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=matin/garth&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=matin/garth&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=matin/garth&type=Date" />
</picture>
</a>
<!-- markdownlint-enable MD013 -->
5 changes: 0 additions & 5 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
# Getting Started

!!! warning "Deprecated"
**Garth is deprecated and no longer maintained.** New logins will not work
due to changes in Garmin's auth flow. See the
[announcement](https://github.com/matin/garth/discussions/222) for details.

## Installation

### From PyPI
Expand Down
73 changes: 52 additions & 21 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,65 @@

Garmin SSO auth + Connect Python client

!!! warning "Deprecated"
**Garth is deprecated and no longer maintained.** Garmin changed their auth
flow, breaking the mobile auth approach that Garth depends on. See the
[announcement](https://github.com/matin/garth/discussions/222) for details.
Anyone is welcome to fork Garth as a starting point for a new library.
[![CI](https://github.com/matin/garth/actions/workflows/ci.yml/badge.svg?branch=main&event=push)](https://github.com/matin/garth/actions/workflows/ci.yml?query=event%3Apush+branch%3Amain+workflow%3ACI)
[![codecov](https://codecov.io/gh/matin/garth/branch/main/graph/badge.svg?token=0EFFYJNFIL)](https://codecov.io/gh/matin/garth)
[![PyPI version](https://img.shields.io/pypi/v/garth.svg?logo=python&logoColor=brightgreen&color=brightgreen)](https://pypi.org/project/garth/)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/garth)](https://pypistats.org/packages/garth)

## About
## Why Garth?

Garth was a Python library for Garmin Connect API access with OAuth
authentication. It reached 350k+ downloads per month and was translated into
multiple programming languages.
Garth is meant for personal use and follows the philosophy that your data is
your data. You should be able to download it and analyze it in the way that
you'd like. In my case, that means processing with Google Colab, Pandas,
Matplotlib, etc.

Garmin recently changed their auth flow, breaking the mobile auth approach
that Garth and other libraries depend on
([#217](https://github.com/matin/garth/issues/217)). This is the final
release.
There are already a few Garmin Connect libraries. Why write another?

## For existing users
### Authentication and stability

If you already have a saved session with a valid OAuth1 token, Garth may
continue to work until that token expires (~1 year from when it was issued).
New logins will not work.
The most important reasoning is to build a library with authentication that
works on [Google Colab](https://colab.research.google.com/) and doesn't require
tools like Cloudscraper. Garth, in comparison:

## Documentation
1. Uses OAuth1 and OAuth2 token authentication after initial login
1. OAuth1 token survives for a year
1. Supports MFA
1. Auto-refresh of OAuth2 token when expired
1. Works on Google Colab
1. Uses Pydantic dataclasses to validate and simplify use of data
1. Full test coverage

The rest of this documentation is preserved for reference.
### JSON vs HTML

- [Getting Started](getting-started.md) - Authentication and session management
Using `garth.connectapi()` allows you to make requests to the Connect API
and receive JSON vs needing to parse HTML. You can use the same endpoints the
mobile app uses.

This also goes back to authentication. Garth manages the necessary Bearer
Authentication (along with auto-refresh) necessary to make requests routed to
the Connect API.

## Quick Start

```bash
pip install garth
```

```python
import garth
from getpass import getpass

# Login and save session
garth.login(input("Email: "), getpass("Password: "))
garth.save("~/.garth")

# Later, resume the session
garth.resume("~/.garth")
```

## Next Steps

- [Getting Started](getting-started.md) - Detailed authentication and session management
- [Configuration](configuration.md) - Domain and proxy settings
- [API Reference](api/stats.md) - Available data types
- [API Reference](api/stats.md) - Explore available data types
- [Examples](examples.md) - Google Colab notebooks
29 changes: 25 additions & 4 deletions docs/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,29 @@

Garth includes built-in telemetry using
[Pydantic Logfire](https://pydantic.dev/logfire) for logging and observability.
Telemetry is **disabled by default** as of v0.8.0 since Garth is no longer
actively maintained.
Telemetry is **enabled by default** to help diagnose authentication issues. It
is **isolated to Garth's requests only** — it won't affect other HTTP clients
in your application.

## Why telemetry is on by default

Garmin occasionally changes their authentication endpoints in ways that only
affect a subset of users — for example, the
[SSO migration](https://github.com/cyberjunky/python-garminconnect/issues/332)
that caused 403 errors for some users while others were unaffected. Without
telemetry, these issues are nearly impossible to reproduce or diagnose. With
default-on telemetry, maintainers can look up the exact request/response
sequence for a failing session using the session ID.

Each session generates a unique `session_id` that is printed to stdout when
garth is imported:

```text
Garth session: a01e3fc1d5ac4c9a
```

When reporting issues, include your session ID so maintainers can look up your
request logs.

## Disable telemetry

Expand Down Expand Up @@ -45,8 +66,8 @@ Telemetry settings can be configured via environment variables with the

| Environment Variable | Default | Description |
|---|---|---|
| `GARTH_TELEMETRY_ENABLED` | `false` | Enable/disable telemetry |
| `GARTH_TELEMETRY_SEND_TO_LOGFIRE` | `false` | Send to Logfire Cloud |
| `GARTH_TELEMETRY_ENABLED` | `true` | Enable/disable telemetry |
| `GARTH_TELEMETRY_SEND_TO_LOGFIRE` | `true` | Send to Logfire Cloud |
| `GARTH_TELEMETRY_TOKEN` | *(built-in)* | Logfire write token |

## What gets logged
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ requires-python = ">=3.10"
readme = "README.md"
license = {text = "MIT"}
classifiers = [
"Development Status :: 7 - Inactive",
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
Expand Down
10 changes: 0 additions & 10 deletions src/garth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import warnings

from .data import (
Activity,
BodyBatteryData,
Expand Down Expand Up @@ -34,14 +32,6 @@
from .version import __version__


warnings.warn(
"Garth is deprecated and no longer maintained. "
"See https://github.com/matin/garth/discussions/222",
DeprecationWarning,
stacklevel=2,
)


__all__ = [
"Activity",
"BodyBatteryData",
Expand Down
5 changes: 3 additions & 2 deletions src/garth/sso.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@
# The SSO endpoints run in a WebView, so requests must look like a
# browser — not a Python HTTP client.
_SSO_UA = (
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/131.0.0.0 Safari/537.36"
)
SSO_PAGE_HEADERS = {
"User-Agent": _SSO_UA,
Expand Down
12 changes: 6 additions & 6 deletions src/garth/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

LOGFIRE_AVAILABLE = True
except ImportError: # pragma: no cover
logfire = None # type: ignore[assignment] # ty: ignore[invalid-assignment]
logfire = None # type: ignore[assignment]
LOGFIRE_AVAILABLE = False


Expand Down Expand Up @@ -109,8 +109,8 @@ class Telemetry(BaseSettings):
extra="ignore",
)

enabled: bool = False
send_to_logfire: bool = False
enabled: bool = True
send_to_logfire: bool = True
Comment on lines +112 to +113
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Breaking change: telemetry now enabled by default.

Changing defaults from False to True for both enabled and send_to_logfire is a significant behavioral change. Per the context snippet from http.py:67-68, this means:

  1. All users will see print(f"Garth session: {self.telemetry.session_id}") output at import time
  2. HTTP request/response data (sanitized) will be sent to Logfire by default

While the data is sanitized, this is an opt-out change that may surprise existing users. Consider:

  • Documenting this prominently in release notes
  • Keeping enabled=False as default and requiring explicit opt-in for telemetry collection
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/garth/telemetry.py` around lines 112 - 113, The change flips telemetry
defaults to opt-out-breaking behavior; revert the default booleans back to
disabled by setting enabled: bool = False and send_to_logfire: bool = False (or
make them driven by an explicit opt-in like an environment variable) so
telemetry.Session creation (which triggers print(f"Garth session:
{self.telemetry.session_id}") in http.py) and automatic Logfire sends do not
occur at import time; update the configuration initialization in telemetry.py
(the enabled and send_to_logfire symbols) to require explicit opt-in and ensure
any env/config flag (e.g., GARTH_TELEMETRY_ENABLED) is respected when
constructing the telemetry object.

token: str = DEFAULT_TOKEN
callback: Callable[[dict], None] | None = Field(default=None, exclude=True)
session_id: str = Field(
Expand Down Expand Up @@ -176,11 +176,11 @@ def configure(
callback: Callable[[dict], None] | None = None,
):
"""
Configure telemetry. Disabled by default.
Configure telemetry. Enabled by default.

Args:
enabled: Enable/disable telemetry (default: False)
send_to_logfire: Send to Logfire Cloud (default: False)
enabled: Enable/disable telemetry (default: True)
send_to_logfire: Send to Logfire Cloud (default: True)
token: Logfire write token
callback: Custom callback for telemetry data. If provided,
logfire will not be configured and data will be passed
Expand Down
2 changes: 1 addition & 1 deletion src/garth/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.8.0"
__version__ = "0.7.12"
4 changes: 2 additions & 2 deletions tests/test_telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ def test_telemetry_defaults(monkeypatch):
monkeypatch.delenv("GARTH_TELEMETRY_SEND_TO_LOGFIRE", raising=False)

t = Telemetry()
assert t.enabled is False
assert t.send_to_logfire is False
assert t.enabled is True
assert t.send_to_logfire is True
assert t.token == DEFAULT_TOKEN
assert t.callback is None
assert t.session_id # non-empty
Expand Down
Loading