Skip to content

Commit edfe8df

Browse files
committed
Refactor pagination to use 'next' URL
- Completely redesigned pagination mechanism to use the 'next' URL provided by the Fitbit API - Added ID tracking to prevent duplicate data - Fixed hanging tests in test_error_handling.py - Added comprehensive tests for all paginated endpoints - Implemented support for all four paginated endpoints: sleep logs, activity logs, ECG logs, and IRN alerts - Created pagination documentation in docs/PAGINATION.md
1 parent cebacf1 commit edfe8df

File tree

18 files changed

+1589
-137
lines changed

18 files changed

+1589
-137
lines changed

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,54 @@ between sessions. If provided, the client will:
156156
3. Set Callback URL to "https://localhost:8080" (or your preferred local URL)
157157
4. Copy your Client ID and Client Secret
158158

159+
## Pagination
160+
161+
Some Fitbit API endpoints support pagination for large result sets. With this
162+
client, you can work with paginated endpoints in two ways:
163+
164+
```python
165+
# Standard way - get a single page of results
166+
sleep_logs = client.get_sleep_log_list(before_date="2025-01-01")
167+
168+
# Iterator way - get an iterator that fetches all pages automatically
169+
for page in client.get_sleep_log_list(before_date="2025-01-01", as_iterator=True):
170+
for sleep_entry in page["sleep"]:
171+
print(sleep_entry["logId"])
172+
```
173+
174+
Endpoints that support pagination:
175+
176+
- `get_sleep_log_list()`
177+
- `get_activity_log_list()`
178+
- `get_ecg_log_list()`
179+
- `get_irn_alerts_list()`
180+
181+
For more details, see [PAGINATION.md](docs/PAGINATION.md).
182+
183+
## Rate Limiting
184+
185+
The client includes automatic retry handling for rate-limited requests. When a
186+
rate limit is encountered, the client will:
187+
188+
1. Log the rate limit event
189+
2. Wait using an exponential backoff strategy
190+
3. Automatically retry the request
191+
192+
You can configure rate limiting behavior:
193+
194+
```python
195+
client = FitbitClient(
196+
client_id="YOUR_CLIENT_ID",
197+
client_secret="YOUR_CLIENT_SECRET",
198+
redirect_uri="https://localhost:8080",
199+
max_retries=5, # Maximum number of retry attempts (default: 3)
200+
retry_after_seconds=30, # Base wait time in seconds (default: 60)
201+
retry_backoff_factor=2.0 # Multiplier for successive waits (default: 1.5)
202+
)
203+
```
204+
205+
For more details, see [RATE_LIMITING.md](docs/RATE_LIMITING.md).
206+
159207
## Additional Documentation
160208

161209
### For API Library Users
@@ -165,6 +213,9 @@ between sessions. If provided, the client will:
165213
- [NAMING.md](docs/NAMING.md): API method naming conventions
166214
- [VALIDATIONS.md](docs/VALIDATIONS.md): Input parameter validation
167215
- [ERROR_HANDLING.md](docs/ERROR_HANDLING.md): Exception hierarchy and handling
216+
- [PAGINATION.md](docs/PAGINATION.md): Working with paginated endpoints
217+
- [RATE_LIMITING.md](docs/RATE_LIMITING.md): Rate limit handling and
218+
configuration
168219

169220
It's also worth reviewing
170221
[Fitbit's Best Practices](https://dev.fitbit.com/build/reference/web-api/developer-guide/best-practices/)

docs/PAGINATION.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Pagination
2+
3+
Some Fitbit API endpoints return potentially large result sets and support
4+
pagination. This library provides an easy way to work with these paginated
5+
endpoints.
6+
7+
## Supported Endpoints
8+
9+
The following endpoints support pagination:
10+
11+
- `client.get_sleep_log_list()`
12+
- `client.get_activity_log_list()`
13+
- `client.get_ecg_log_list()`
14+
- `client.get_irn_alerts_list()`
15+
16+
## Usage
17+
18+
### Standard Mode
19+
20+
By default, all endpoints return a single page of results with pagination
21+
metadata:
22+
23+
```python
24+
# Get a single page of sleep logs
25+
sleep_data = client.get_sleep_log_list(
26+
before_date="2025-01-01",
27+
sort=SortDirection.DESCENDING,
28+
limit=10
29+
)
30+
31+
# Access the pagination metadata
32+
pagination_info = sleep_data["pagination"]
33+
has_next_page = "next" in pagination_info and bool(pagination_info["next"])
34+
35+
# Process the page data
36+
for sleep_entry in sleep_data["sleep"]:
37+
print(f"Sleep log ID: {sleep_entry['logId']}")
38+
```
39+
40+
### Iterator Mode
41+
42+
When you need to process multiple pages of data, use iterator mode:
43+
44+
```python
45+
iterator = client.get_sleep_log_list(
46+
before_date="2025-01-01",
47+
sort=SortDirection.DESCENDING,
48+
limit=10,
49+
as_iterator=True # Creates an iterator for all pages
50+
)
51+
52+
# Process all pages - the iterator fetches new pages as needed
53+
for page in iterator:
54+
# Each page has the same structure as the standard response
55+
for sleep_entry in page["sleep"]:
56+
print(f"Sleep log ID: {sleep_entry['logId']}")
57+
```
58+
59+
## Pagination Parameters
60+
61+
Different endpoints support different pagination parameters, but they generally
62+
follow these patterns:
63+
64+
| Parameter | Description | Constraints |
65+
| ------------- | ------------------------------- | ----------------------------------------------------------- |
66+
| `before_date` | Return entries before this date | Must use with `sort=SortDirection.DESCENDING` |
67+
| `after_date` | Return entries after this date | Must use with `sort=SortDirection.ASCENDING` |
68+
| `limit` | Maximum items per page | Varies by endpoint (10-100) |
69+
| `offset` | Starting position | Usually only `0` is supported |
70+
| `sort` | Sort direction | Use `SortDirection.ASCENDING` or `SortDirection.DESCENDING` |
71+
72+
## Endpoint-Specific Notes
73+
74+
Each paginated endpoint has specific constraints:
75+
76+
### `get_sleep_log_list`
77+
78+
- Max limit: 100 entries per page
79+
- Date filtering: `before_date` or `after_date` (must specify one but not both)
80+
81+
### `get_activity_log_list`
82+
83+
- Max limit: 100 entries per page
84+
- Date filtering: `before_date` or `after_date` (must specify one but not both)
85+
86+
### `get_ecg_log_list` and `get_irn_alerts_list`
87+
88+
- Max limit: 10 entries per page
89+
- Only supports `offset=0`

docs/RATE_LIMITING.md

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Rate Limiting
2+
3+
The Fitbit API enforces rate limits to prevent overuse. When you exceed these
4+
limits, the API returns a `429 Too Many Requests` error. We attempt to include
5+
retry mechanisms to handle rate limiting gracefully.
6+
7+
## How It Works
8+
9+
When a rate limit is encountered, the client will:
10+
11+
1. Log a warning message with details about the rate limit
12+
2. Wait for an appropriate amount of time using exponential backoff
13+
3. Automatically retry the request (up to a configurable maximm)
14+
4. Either return the successful response or raise an exceptione after exhausting
15+
retries
16+
17+
All of this should happen transparently and without manual intervention.
18+
19+
## Configuration
20+
21+
Configure rate limiting behavior when creating the client:
22+
23+
```python
24+
from fitbit_client import FitbitClient
25+
26+
client = FitbitClient(
27+
client_id="YOUR_CLIENT_ID",
28+
client_secret="YOUR_CLIENT_SECRET",
29+
redirect_uri="https://localhost:8080",
30+
31+
# Rate limiting options (all optional)
32+
max_retries=5, # Maximum retry attempts (default: 3)
33+
retry_after_seconds=10, # Base wait time in seconds (default: 5)
34+
retry_backoff_factor=2.0 # Multiplier for successive waits (default: 1.5)
35+
)
36+
```
37+
38+
## Exponential Backoff
39+
40+
The wait time between retries increases exponentially to avoid overwhelming the
41+
API. The formula is:
42+
43+
```
44+
retry_time = retry_after_seconds * (retry_backoff_factor ^ retry_count)
45+
```
46+
47+
With the default settings:
48+
49+
- First retry: Wait 60 seconds
50+
- Second retry: Wait 90 seconds (60 * 1.5)
51+
- Third retry: Wait 135 seconds (60 * 1.5²)
52+
53+
## Fitbit API Rate Limits
54+
55+
The Fitbit API has different rate limits for different endpoints:
56+
57+
- **User-level rate limits**: Up to 150 API calls per hour per user
58+
- **Client-level rate limits**: For applications with many users
59+
60+
Refer to the
61+
[Fitbit API Rate Limits documentation](https://dev.fitbit.com/build/reference/web-api/developer-guide/application-design/#Rate-Limits)
62+
for the most current information.
63+
64+
## Logging
65+
66+
Rate limit events are logged to the standard application logger. To capture
67+
these logs:
68+
69+
```python
70+
import logging
71+
72+
# Set up logging
73+
logging.basicConfig(level=logging.INFO)
74+
75+
# Use the client
76+
client = FitbitClient(...)
77+
```
78+
79+
You'll see log messages like:
80+
81+
```
82+
WARNING:fitbit_client.SleepResource:Rate limit exceeded for get_sleep_log_list to sleep/list.json. Retrying in 60 seconds. (2 retries remaining)
83+
```
84+
85+
## Handling Unrecoverable Rate Limits
86+
87+
If all retry attempts are exhausted, the client will raise a
88+
`RateLimitExceededException`. You can catch this exception to implement your own
89+
fallback logic:
90+
91+
```python
92+
from fitbit_client import FitbitClient
93+
from fitbit_client.exceptions import RateLimitExceededException
94+
95+
client = FitbitClient(...)
96+
97+
try:
98+
# Make API request
99+
data = client.get_activity_log_list(before_date="2025-01-01")
100+
# Process data
101+
except RateLimitExceededException as e:
102+
# Handle unrecoverable rate limit
103+
print(f"Rate limit exceeded even after retries: {e}")
104+
# Implement fallback logic
105+
```

0 commit comments

Comments
 (0)