Skip to content
Merged

Docs #14

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
71 changes: 33 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,63 +59,54 @@ The response will always be the body of the API response, and is almost always a
`Dict`, `List` or `None`. `nutrition.get_activity_tcx` is the exception. It
returns XML (as a `str`).

## Authentication Methods

### 1. Automatic (Recommended)
## Authentication

Uses a local callback server to automatically handle the OAuth2 flow:

```python
client = FitbitClient(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
redirect_uri="https://localhost:8080",
use_callback_server=True # default is True
redirect_uri="YOUR_REGISTERED_REDIRECT_URI",
token_cache_path="/tmp/fb_tokens.json" # Optional: saves tokens between sessions
)

# Will open browser and handle callback automatically
client.authenticate()
```

### 2. Manual URL Copy/Paste

If you prefer not to use a local server:
The `token_cache_path` parameter allows you to persist authentication tokens
between sessions. If provided, the client will:

```python
client = FitbitClient(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
redirect_uri="YOUR_REGISTERED_REDIRECT_URI",
token_cache_path="/tmp/fb_tokens.json",
use_callback_server=True
)

# Will open a browser and start a server to complete the flow (default), or
# prompt you on the command line to copy/paste the callback URL from the
# browser (see `use_callback_server`)
client.authenticate()
```
1. Load existing tokens from this file if available (avoiding re-authentication)
2. Save new or refreshed tokens to this file automatically
3. Handle token refresh when expired tokens are detected

## Setting Up Your Fitbit App

1. Go to dev.fitbit.com and create a new application
2. Set OAuth 2.0 Application Type to "Personal"
3. Set Callback URL to:
- For automatic method: "https://localhost:8080"
- For manual method: Your preferred redirect URI
3. Set Callback URL to "https://localhost:8080" (or your preferred local URL)
4. Copy your Client ID and Client Secret

Additional documentation:
## Additional Documentation

### For API Library Users

- To understand the logging implemementation, see [LOGGING](docs/LOGGING.md)
- To understand validations and the exception hierarchy, see
[VALIDATIONS_AND_EXCEPTIONS](docs/VALIDATIONS_AND_EXCEPTIONS.md)
- It's work checking out
[Fitbit's Best Practices](https://dev.fitbit.com/build/reference/web-api/developer-guide/best-practices/)
- For some general development guidelines, see
[DEVELOPMENT](docs/DEVELOPMENT.md).
- For style guidelines (mostly enforced through varius linters and formatters)
see [STYLE](docs/STYLE.md).
- [LOGGING.md](docs/LOGGING.md): Understanding the dual-logger system
- [TYPES.md](docs/TYPES.md): JSON type system and method return types
- [NAMING.md](docs/NAMING.md): API method naming conventions
- [VALIDATIONS.md](docs/VALIDATIONS.md): Input parameter validation
- [ERROR_HANDLING.md](docs/ERROR_HANDLING.md): Exception hierarchy and handling

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

### Project Best Practices

- [DEVELOPMENT.md](docs/DEVELOPMENT.md): Development environment and guidelines
- [STYLE.md](docs/STYLE.md): Code style and formatting standards

## Important Note - Subscription Support

Expand All @@ -124,9 +115,13 @@ This client does not currently support the
and
[deletion](https://dev.fitbit.com/build/reference/web-api/subscription/delete-subscription/)
of
[webhook subscrptions](https://dev.fitbit.com/build/reference/web-api/developer-guide/using-subscriptions/).
The methods are implemented in comments and _should_ work, but I have not had a
chance to verify a user confirm this.
[webhook subscriptions](https://dev.fitbit.com/build/reference/web-api/developer-guide/using-subscriptions/).
The methods are implemented in comments and should work, but I have not had a
chance to verify them since this requires a publicly accessible server to
receive webhook notifications.

If you're using this library with subscriptions and would like to help test and
implement this functionality, please open an issue or pull request!

## License

Expand Down
26 changes: 21 additions & 5 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@

## TODOs:

- Create and Test that all methods have an alias in `Client` and that the
signatures match

- Improve README for end users:

- Add more common use cases examples beyond basic profile retrieval
- Explain token persistence between sessions (DONE)
- Provide overview of available resources/endpoints
- Verify correct callback URI guidance (check if "https://localhost:8080" is
actually the correct/optimal value to recommend)

- ✅ Review and improve all documentation files in docs/ from an end-user
perspective

- ✅ Split NAMING_AND_TYPING.md into TYPES.md and NAMING.md
- ✅ Split VALIDATIONS_AND_EXCEPTIONS.md into VALIDATIONS.md and
ERROR_HANDLING.md
- ✅ Update cross-references between documentation files
- ✅ Fix intraday data support information in DEVELOPMENT.md
- ✅ Add information about disabling data logging to LOGGING.md

- PyPi deployment

- For all `create_...`methods, add the ID from the response to logs and maybe
Expand All @@ -17,11 +38,6 @@
- Rename to `_base`? Files it first, makes it clearer that everything in it is
private

- client.py:

- Creat and Test that all methods have an alias in `Client` and that the
signatures match

- CI:

* Read and implement:
Expand Down
34 changes: 24 additions & 10 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,18 +339,32 @@ TODO
- Use issue templates when reporting bugs
- Include Python version and environment details in bug reports

## Scope and Limitations - Intraday Data Support
## Intraday Data Support

This client explicitly does not implement intraday data endpoints (detailed
heart rate, steps, etc). These endpoints:
This client implements intraday data endpoints (detailed heart rate, steps, etc)
through the `IntradayResource` class. These endpoints:

- Require special access from Fitbit (typically limited to research
applications)
- Have different rate limits than standard endpoints
- Need additional OAuth2 scopes
- Often require institutional review board (IRB) approval

If you need intraday data access:

1. Apply through Fitbit's developer portal
2. Pull requests welcome!
- Need additional OAuth2 scopes (specifically the 'activity' and 'heartrate'
scopes)
- Often require institutional review board (IRB) approval for research
applications

To use intraday data:

1. Apply for intraday access through the
[Fitbit developer portal](https://dev.fitbit.com/)
2. Ensure your application requests the appropriate scopes
3. Use the intraday methods with appropriate detail level parameters:
```python
client.intraday.get_heartrate_intraday_by_date(
date="today",
detail_level="1min" # or "1sec" depending on your access level
)
```

See the
[Intraday API documentation](https://dev.fitbit.com/build/reference/web-api/intraday/)
for more details on available endpoints and access requirements.
181 changes: 181 additions & 0 deletions docs/ERROR_HANDLING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Exception Handling

The library implements a comprehensive exception system to help you handle
errors effectively. Understanding these exceptions will help you write more
robust code.

## Exception Hierarchy

```
Exception
├── ValueError
│ └── ClientValidationException # Superclass for validations that take place before
│ │ # making a request
│ ├── InvalidDateException # Raised when a date string is not in the correct
│ │ # format or not a valid calendar date
│ ├── InvalidDateRangeException # Raised when a date range is invalid (e.g., end is
│ │ # before start, exceeds max days)
│ ├── PaginationException # Raised when pagination parameters are invalid
│ ├── IntradayValidationException # Raised when intraday request parameters are invalid
│ ├── ParameterValidationException # Raised when a parameter value is invalid
│ │ # (e.g., negative when positive required)
│ └── MissingParameterException # Raised when required parameters are missing or
│ # parameter combinations are invalid
└── FitbitAPIException # Base exception for all Fitbit API errors
├── OAuthException # Superclass for all authentication flow exceptions
│ ├── ExpiredTokenException # Raised when the OAuth token has expired
│ ├── InvalidGrantException # Raised when the grant_type value is invalid
│ ├── InvalidTokenException # Raised when the OAuth token is invalid
│ └── InvalidClientException # Raised when the client_id is invalid
└── RequestException # Superclass for all API request exceptions
├── InvalidRequestException # Raised when the request syntax is invalid
├── AuthorizationException # Raised when there are authorization-related errors
├── InsufficientPermissionsException # Raised when the application has insufficient permissions
├── InsufficientScopeException # Raised when the application is missing a required scope
├── NotFoundException # Raised when the requested resource does not exist
├── RateLimitExceededException # Raised when the application hits rate limiting quotas
├── SystemException # Raised when there is a system-level failure
└── ValidationException # Raised when a request parameter is invalid or missing
```

## Client Validation Exceptions

Client validation exceptions (`ClientValidationException` and its subclasses)
are raised *before* any API call is made:

1. They reflect problems with your input parameters that can be detected locally
2. No network requests have been initiated when these exceptions occur
3. They help you fix issues before consuming API rate limits

```python
from fitbit_client.exceptions import InvalidDateException, InvalidDateRangeException

try:
client.sleep.get_sleep_log_by_date_range(
start_date="2024-01-01",
end_date="2023-12-31" # End date before start date
)
except InvalidDateRangeException as e:
print(f"Date range error: {e.message}")
print(f"Start date: {e.start_date}, End date: {e.end_date}")
print(f"Resource: {e.resource_name}, Max days: {e.max_days}")
```

### Common Client Validation Exceptions

- **InvalidDateException**: Raised when a date string is not valid
- **InvalidDateRangeException**: Raised when a date range is invalid
- **ParameterValidationException**: Raised when a parameter value is invalid
- **MissingParameterException**: Raised when required parameters are missing
- **PaginationException**: Raised when pagination parameters are invalid
- **IntradayValidationException**: Raised when intraday request parameters are
invalid

## API Exceptions

API exceptions (`FitbitAPIException` and its subclasses) are raised in response
to errors returned by the Fitbit API:

```python
from fitbit_client.exceptions import AuthorizationException, RateLimitExceededException

try:
client.activity.get_lifetime_stats()
except AuthorizationException as e:
print(f"Auth error ({e.status_code}): {e.message}")
# Handle authentication error (e.g., refresh token, prompt for re-auth)
except RateLimitExceededException as e:
retry_after = int(e.headers.get("Retry-After", 60))
print(f"Rate limit exceeded. Retry after {retry_after} seconds")
# Implement backoff strategy
```

### Common API Exceptions

- **AuthorizationException**: Authentication or authorization issues
- **InvalidRequestException**: Invalid request syntax or parameters
- **RateLimitExceededException**: API rate limits exceeded
- **NotFoundException**: Requested resource doesn't exist
- **SystemException**: Fitbit API server-side errors

## Exception Properties

### Client Validation Exceptions

All client validation exceptions have these properties:

- `message`: Human-readable error description
- `field_name`: Name of the invalid field (if applicable)

Specific validation exception types add additional properties:

- **InvalidDateException**: `date_str` (the invalid date string)
- **InvalidDateRangeException**: `start_date`, `end_date`, `max_days`,
`resource_name`
- **IntradayValidationException**: `allowed_values`, `resource_name`

### API Exceptions

All API exceptions have these properties:

- `message`: Human-readable error description
- `status_code`: HTTP status code (if applicable)
- `error_type`: Type of error from the API
- `field_name`: Name of the invalid field (for validation errors)
- `headers`: Response headers (useful for rate limiting info)

## Usage Patterns

### Catching Specific Exceptions

Target specific exceptions for tailored error handling:

```python
try:
client.activity.create_activity_goals(
period=ActivityGoalPeriod.DAILY,
type=ActivityGoalType.STEPS,
value=-1000
)
except ParameterValidationException as e:
print(f"Invalid value for {e.field_name}: {e.message}")
except AuthorizationException as e:
print(f"Authorization error: {e.message}")
except RateLimitExceededException as e:
print(f"Rate limit error: {e.message}")
```

### Catching Base Exception Classes

Catch base classes to handle related exceptions together:

```python
try:
client.activity.get_daily_activity_summary("today")
except ClientValidationException as e:
print(f"Invalid input: {e.message}") # Catches all input validation errors
except OAuthException as e:
print(f"OAuth error: {e.message}") # Catches all OAuth-related errors
except FitbitAPIException as e:
print(f"API error: {e.message}") # Catches all other API errors
```

## Debugging APIs

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

```python
client.activity.get_daily_activity_summary(
date="today",
debug=True
)
# Prints:
# curl -X GET -H "Authorization: Bearer <token>" ...
```

This helps troubleshoot API interactions by showing the exact request being
made.
Loading