Skip to content

Commit e7cfdec

Browse files
committed
continuing on with documentation review
1 parent 6efc808 commit e7cfdec

File tree

6 files changed

+235
-263
lines changed

6 files changed

+235
-263
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ for API usage.
173173
### Project Best Practices
174174

175175
- [DEVELOPMENT.md](docs/DEVELOPMENT.md): Development environment and guidelines
176-
- [STYLE.md](docs/STYLE.md): Code style and formatting standards
176+
- [LINTING.md](docs/LINTING.md): Code style and linting configuration
177177

178178
## Important Note - Subscription Support
179179

docs/DEVELOPMENT.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
- [Response Mocking](#response-mocking)
2727
- [OAuth Callback Implementation](#oauth-callback-implementation)
2828
- [Implementation Flow](#implementation-flow)
29-
- [Security Notes](#security-notes)
3029
- [Git Workflow](#git-workflow)
3130
- [Release Process](#release-process)
3231
- [Getting Help](#getting-help)
@@ -45,8 +44,8 @@
4544
1. Clone the repository:
4645

4746
```bash
48-
git clone https://github.com/yourusername/fitbit-client.git
49-
cd fitbit-client
47+
git clone https://github.com/jpstroop/fitbit-client-python.git
48+
cd fitbit-client-python
5049
```
5150

5251
2. Install asdf plugins and required versions:

docs/ERROR_HANDLING.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,92 @@ except FitbitAPIException as e:
163163
print(f"API error: {e.message}") # Catches all other API errors
164164
```
165165

166+
### Handling OAuth-Specific Exceptions
167+
168+
OAuth errors require special handling. Here's how to handle different OAuth exception subtypes:
169+
170+
```python
171+
from fitbit_client.exceptions import ExpiredTokenException, InvalidTokenException
172+
from fitbit_client.exceptions import InvalidClientException, InvalidGrantException
173+
174+
try:
175+
# Make an API call that requires authentication
176+
client.get_profile()
177+
except ExpiredTokenException as e:
178+
# Token has expired but couldn't be auto-refreshed
179+
print(f"Token expired: {e.message}")
180+
# Attempt to re-authenticate
181+
client.authenticate()
182+
except InvalidTokenException as e:
183+
# Token is invalid (e.g., revoked by user)
184+
print(f"Token invalid: {e.message}")
185+
# Re-authenticate from scratch
186+
client.authenticate()
187+
except InvalidClientException as e:
188+
# Client credentials are incorrect
189+
print(f"Client credentials error: {e.message}")
190+
# Check client_id and client_secret
191+
except InvalidGrantException as e:
192+
# Refresh token is invalid
193+
print(f"Invalid refresh token: {e.message}")
194+
# Re-authenticate to get a new refresh token
195+
client.authenticate()
196+
```
197+
198+
## Token Refresh Strategies
199+
200+
The client automatically handles token refresh when tokens expire. However, you may want to implement custom token refresh strategies for your application.
201+
202+
### Automatic Token Refresh
203+
204+
By default, the client refreshes tokens automatically:
205+
206+
1. When initializing the client with cached tokens
207+
2. During API calls when a token expires
208+
3. When explicitly calling a method that requires authentication
209+
210+
```python
211+
# Tokens are automatically refreshed
212+
client = FitbitClient(
213+
client_id="YOUR_CLIENT_ID",
214+
client_secret="YOUR_CLIENT_SECRET",
215+
redirect_uri="https://localhost:8080",
216+
token_cache_path="/path/to/tokens.json" # Enable persistent token caching
217+
)
218+
219+
# If cached tokens exist and are valid or can be refreshed, no browser prompt occurs
220+
client.authenticate()
221+
222+
# If the token expires during this call, it will be refreshed automatically
223+
client.get_profile()
224+
```
225+
226+
### Handling Failed Token Refresh
227+
228+
When automatic token refresh fails, the client raises an appropriate OAuth exception. Here's a complete error handling pattern:
229+
230+
```python
231+
from fitbit_client.exceptions import OAuthException, ExpiredTokenException
232+
233+
try:
234+
result = client.get_profile()
235+
# Process result normally
236+
except ExpiredTokenException:
237+
# Token expired and auto-refresh failed
238+
try:
239+
# Try to authenticate again
240+
client.authenticate()
241+
# Retry the original request
242+
result = client.get_profile()
243+
except OAuthException as oauth_error:
244+
# Handle authentication failure (e.g., user closed browser window)
245+
print(f"Authentication failed: {oauth_error.message}")
246+
# Log the user out or provide error message
247+
except OAuthException as e:
248+
# Handle other OAuth errors
249+
print(f"OAuth error: {e.message}")
250+
```
251+
166252
## Debugging APIs
167253

168254
Every method accepts a `debug` parameter that prints the equivalent cURL

docs/LINTING.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Code Style and Linting
2+
3+
Linting and formatting are handled by [Black](https://github.com/psf/black),
4+
[isort](https://github.com/pycqa/isort/),
5+
[mdformat](https://github.com/executablebooks/mdformat),
6+
[autoflake](https://github.com/PyCQA/autoflake) and a
7+
[small script that adds a path comment](../lint/add_file_headers.py).
8+
9+
## Running the Linters
10+
11+
You can run all formatters using:
12+
13+
```bash
14+
pdm run format
15+
```
16+
17+
This will:
18+
- Format Python code with Black
19+
- Sort imports with isort
20+
- Format Markdown files with mdformat
21+
- Remove unused imports with autoflake
22+
- Add file path headers to Python files
23+
24+
## Code Organization
25+
26+
Every Python file follows a precise organizational structure with three distinct import sections:
27+
28+
1. **Standard library imports** - marked with `# Standard library imports`
29+
2. **Third-party imports** - marked with `# Third party imports`
30+
3. **Project-specific imports** - marked with `# Local imports`
31+
32+
Each section should be alphabetically ordered and separated by exactly one blank line.
33+
34+
### Example
35+
36+
```python
37+
"""Module docstring explaining the purpose of this file."""
38+
39+
# Standard library imports
40+
from datetime import datetime
41+
from inspect import currentframe
42+
from json import JSONDecodeError
43+
from json import dumps
44+
from typing import Any
45+
from typing import Dict
46+
47+
# Third party imports
48+
from requests import Response
49+
from requests_oauthlib import OAuth2Session
50+
51+
# Local imports
52+
from fitbit_client.exceptions import FitbitAPIException
53+
from fitbit_client.resources.base import BaseResource
54+
```
55+
56+
## Documentation Requirements
57+
58+
The test suite verifies that all public methods have comprehensive docstrings that follow the Google style format with specific sections:
59+
- Args
60+
- Returns
61+
- Raises
62+
- Note (if applicable)
63+
64+
Our linting tools ensure consistent style throughout the codebase.

docs/NAMING.md

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,88 @@ easier to find based on the official documentation.
2828

2929
## Inconsistencies in the API
3030

31-
The Fitbit API contains several inconsistencies, which our method names
32-
necessarily reflect:
33-
34-
- `create_activity_goals` creates only one goal at a time
35-
- `add_favorite_foods` adds one food at a time, while all other creation methods
36-
use "create" prefix
37-
- Some pluralized methods return lists, while others return dictionaries
38-
containing lists
31+
The Fitbit API contains several inconsistencies, which our method names and implementation necessarily reflect. Understanding these inconsistencies can help you navigate the API more effectively:
32+
33+
### Method Name vs. Functionality Inconsistencies
34+
35+
- `create_activity_goals` creates only one goal at a time, despite the plural name
36+
- `add_favorite_foods` adds one food at a time, while all other creation methods use "create" prefix
37+
- `get_sleep_goals` returns a single goal, not multiple goals
38+
- Some pluralized methods return lists, while others return dictionaries containing lists
39+
40+
### Parameter and Response Format Inconsistencies
41+
42+
```python
43+
# Sleep endpoint uses a different API version than most other endpoints
44+
client.sleep.get_sleep_log_by_date(date="2025-01-01") # Uses API v1.2
45+
client.activity.get_daily_activity_summary(date="2025-01-01") # Uses API v1
46+
47+
# Sleep date parameters have inconsistent response behavior
48+
# The request uses "2025-01-01" but response might contain data from "2025-01-02"
49+
sleep_data = client.get_sleep_log_by_date(date="2025-01-01")
50+
# A sleep log started on 2025-01-01 at 23:00 and ended on 2025-01-02 at 07:00
51+
# will be included in the response, but with dateOfSleep="2025-01-02"
52+
53+
# Pagination parameters vary across endpoints
54+
# Some endpoints require offset=0
55+
food_log = client.get_food_log(date="2025-01-01", offset=0) # Valid
56+
# Others support arbitrary offsets
57+
badges = client.get_badges(offset=20) # Also valid
58+
59+
# Date range validations vary by endpoint
60+
# Sleep endpoints allow up to 100 days
61+
sleep_logs = client.get_sleep_log_by_date_range(
62+
start_date="2025-01-01",
63+
end_date="2025-04-10" # 100 days later, valid for sleep endpoint
64+
)
65+
# Activity endpoints allow only 31 days
66+
activity_logs = client.get_activity_log_list(
67+
before_date="2025-02-01",
68+
after_date="2025-01-01" # 31 days earlier, valid for activity endpoint
69+
)
70+
```
71+
72+
### Response Structure Inconsistencies
73+
74+
The structure of API responses varies widely across endpoints:
75+
76+
```python
77+
# Some endpoints return arrays directly
78+
activities = client.get_frequent_activities()
79+
# activities is a List[Dict[str, Any]] (array of activity objects)
80+
81+
# Others wrap arrays in a parent object with a named property
82+
sleep_logs = client.get_sleep_log_by_date(date="2025-01-01")
83+
# sleep_logs is a Dict[str, Any] with a "sleep" property containing the array
84+
85+
# Some endpoints use plural property names for lists
86+
weight_logs = client.get_weight_logs(date="2025-01-01")
87+
# weight_logs["weight"] is the list of weight logs
88+
89+
# Others use singular property names for lists
90+
food_logs = client.get_food_log(date="2025-01-01")
91+
# food_logs["foods"] is the list of food logs
92+
```
93+
94+
### Error Response Inconsistencies
95+
96+
Error responses can also vary in structure:
97+
98+
```python
99+
try:
100+
# Some validation errors include field names
101+
client.create_food_log(food_id="invalid", amount=100, meal_type_id=1)
102+
except ValidationException as e:
103+
print(e.field_name) # Might be "foodId"
104+
105+
try:
106+
# Other validation errors omit field names
107+
client.get_activity_tcx(log_id="invalid")
108+
except InvalidRequestException as e:
109+
print(e.field_name) # Might be None
110+
```
111+
112+
Our library handles these inconsistencies internally to provide a unified experience, but it's helpful to be aware of them when working with the raw API responses.
39113

40114
## Method Aliases
41115

0 commit comments

Comments
 (0)