Skip to content

Commit 8aaa295

Browse files
committed
Doc updates and remove unused imports
1 parent 03bed90 commit 8aaa295

27 files changed

+49
-206
lines changed

docs/DEVELOPMENT.md

Lines changed: 35 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Python 3.13+
88
- PDM
99
- Git
10+
- ASDF (recommended)
1011

1112
### Download and Install the Source Code
1213

@@ -49,8 +50,9 @@ fitbit-client/
4950
│ ├── resources/
5051
│ │ ├── __init__.py
5152
│ │ ├── [resource modules]
52-
│ │ ├── base.py
53-
│ │ └── constants.py
53+
│ │ ├── _base.py
54+
│ │ ├── _pagination.py
55+
│ │ └── _constants.py
5456
│ ├── utils/
5557
│ │ ├── __init__.py
5658
│ │ ├── curl_debug_mixin.py
@@ -59,26 +61,22 @@ fitbit-client/
5961
│ │ ├── pagination_validation.py
6062
│ │ └── types.py
6163
│ └── exceptions.py
62-
├── tests/
63-
│ ├── auth/
64-
│ ├── resources/
65-
│ └── utils/
66-
└── [project files]
64+
└── tests/
65+
│ ├── fitbit_client/
66+
│ ├── auth/
67+
│ ├── resources/
68+
│ └── utils/
69+
└── [project files]
6770
```
6871

69-
## Goals, Notes, and TODOs
70-
71-
For now these are just in [TODO.md](TODO.md); bigger work will eventually move
72-
to Github tickets.
73-
7472
## Development Tools and Standards
7573

7674
### Code Formatting and Style
7775

7876
- Black for code formatting (100 character line length)
7977
- isort for import sorting
80-
- Type hints required for all code
81-
- Docstrings required for all public methods
78+
- Type hints required for all code (enforced by `mypy`)
79+
- Docstrings required for all public methods (enforced by `test_docscrings.py`)
8280

8381
### Import Style
8482

@@ -102,7 +100,7 @@ import typing
102100
import datetime
103101
```
104102

105-
The one exception to this rule is when an entire module needs to be `mock`ed for
103+
The one exception to this rule is when an entire module needs to be mocked for
106104
testing, in which case, at least for the `json` package from the standard
107105
library, the entire package has to be imported. So `import json` is ok when that
108106
circumstance arises.
@@ -120,46 +118,29 @@ Follow your nose from `client.py` and the structure should be very clear.
120118
#### Method Structure
121119

122120
- Include comprehensive docstrings with Args sections
123-
- Keep parameter naming consistent across methods
124-
- Use "-" as default for user_id parameters
125-
- Return Dict[str, Any] for most methods that return data
126-
- Return None for delete operations
121+
- Keep parameter naming consistent across methods (see [Naming](docs/NAMING.md))
122+
- Return `JSONDict` for `JSONList` for most methods (`get_activity_tcx` returns
123+
XML as a string)
124+
- Return `None` for delete operations
127125

128126
### Error Handling
129127

130-
The codebase implements a comprehensive error handling system through
131-
[`exceptions.py`](fitbit_client/exceptions.py):
132-
133-
1. A base FitbitAPIException that captures:
134-
135-
- HTTP status code
136-
- Error type
137-
- Error message
138-
- Field name (when applicable)
139-
140-
2. Specialized exceptions for different error scenarios:
141-
142-
- InvalidRequestException for malformed requests
143-
- ValidationException for parameter validation failures
144-
- AuthorizationException for authentication issues
145-
- RateLimitExceededException for API throttling
146-
- SystemException for server-side errors
147-
148-
3. Mapping from HTTP status codes and API error types to appropriate exception
149-
classes
128+
The codebase implements a comprehensive error handling system. See
129+
[ERROR_HANDLING](docs/ERROR_HANDLING.md) and
130+
[`exceptions.py`](fitbit_client/exceptions.py).
150131

151132
### Enum Usage
152133

153134
- Only use enums for validating request parameters, not responses
154-
- Place all enums in constants.py
135+
- Place all enums in [`constants.py`](fitbit_client/resources/_constants.py)
155136
- Only import enums that are actively used in the class
156137

157138
## Logging System
158139

159140
The project implements two different logs in through the
160-
[`BaseResource`](fitbit_client/resources/base.py) class: application logging for
161-
API interactions and data logging for tracking important response fields. See
162-
[LOGGING](docs/LOGGING.md) for details.
141+
[`BaseResource`](fitbit_client/resources/_base.py) class: application logging
142+
for API interactions and data logging for tracking important response fields.
143+
See [LOGGING](docs/LOGGING.md) for details.
163144

164145
## API Design
165146

@@ -190,77 +171,27 @@ client.get_profile()
190171
client.get_daily_activity_summary(date="2025-03-06")
191172
```
192173

193-
Method aliases were implemented for several important reasons:
194-
195-
1. **Reduced Verbosity**: Typing `client.resource_name.method_name(...)` with
196-
many parameters can be tedious, especially when used frequently.
197-
198-
2. **Flatter API Surface**: Many modern APIs prefer a flatter design that avoids
199-
deep nesting, making the API more straightforward to use.
200-
201-
3. **Method Name Uniqueness**: All resource methods in the Fitbit API have
202-
unique names (e.g., there's only one `get_profile()` method), making it safe
203-
to expose these methods directly on the client.
204-
205-
4. **Preserve Both Options**: By maintaining both the resource-based access and
206-
direct aliases, developers can choose the approach that best fits their needs
207-
\- organization or conciseness.
208-
209-
All method aliases are set up in the `_set_up_method_aliases()` method in the
174+
Method aliases were implemented because yyping
175+
`client.resource_name.method_name(...)` with many parameters can be tedious,
176+
especially when used frequently. All method aliases are set up in the
177+
`_set_up_method_aliases()` method in the
210178
[`FitbitClient`](fitbit_client/client.py) class, which is called during
211179
initialization. Each alias is a direct reference to the corresponding resource
212180
method, ensuring consistent behavior regardless of how the method is accessed.
213181

214182
## Testing
215183

216184
The project uses pytest for testing and follows a consistent testing approach
217-
across all components.
185+
across all components. 100% coverage is expected.
218186

219187
### Test Organization
220188

221-
The test directory mirrors the main package structure (except that the root is
222-
named "test" rather than "fitbit_client"), with corresponding test modules for
223-
each component:
224-
225-
- auth/: Tests for authentication and OAuth functionality
226-
- client/: Tests for the main client implementation
227-
- resources/: Tests for individual API resource implementations
228-
229-
### Standard Test Fixtures
230-
231-
The test suite provides several standard fixtures for use across test modules:
189+
The test directory mirrors the main package structure within the `test`
190+
directory. For the most part, the naming is 1:1 (`test_blah.py`) or otherwise
191+
obvious--many tests modules were getting quite long and broken out either into
192+
directories or with names that make it obvious as to hwat they are testing.
232193

233-
```python
234-
@fixture
235-
def mock_oauth_session():
236-
"""Provides a mock OAuth session for testing resources"""
237-
return Mock()
238-
239-
@fixture
240-
def mock_logger():
241-
"""Provides a mock logger for testing logging behavior"""
242-
return Mock()
243-
244-
@fixture
245-
def base_resource(mock_oauth_session, mock_logger):
246-
"""Creates a resource instance with mocked dependencies"""
247-
with patch("fitbit_client.resources._base.getLogger", return_value=mock_logger):
248-
return BaseResource(mock_oauth_session, "en_US", "en_US")
249-
```
250-
251-
### Error Handling Tests
252-
253-
Tests verify proper error handling across the codebase. Common patterns include:
254-
255-
```python
256-
def test_http_error_handling(resource):
257-
"""Tests that HTTP errors are properly converted to exceptions"""
258-
with raises(InvalidRequestException) as exc_info:
259-
# Test code that should raise the exception
260-
pass
261-
assert exc_info.value.status_code == 400
262-
assert exc_info.value.error_type == "validation"
263-
```
194+
All resource mocks are in the root [conftest.py](tests/conftest.py).
264195

265196
### Response Mocking
266197

@@ -332,39 +263,4 @@ git commit --no-verify -m "Your commit message"
332263

333264
## Release Process
334265

335-
This section will be documented as we near our first release.
336-
337-
## Pagination Implementation
338-
339-
The pagination implementation uses the following approach:
340-
341-
### Pagination Iterator
342-
343-
- Uses the `PaginatedIterator` class that implements the Python `Iterator`
344-
protocol
345-
- Automatically handles fetching the next page when needed using the `next` URL
346-
from pagination metadata
347-
- Properly handles edge cases like invalid responses, missing pagination data,
348-
and API errors
349-
350-
### Type Safety
351-
352-
- Uses `TYPE_CHECKING` from the typing module to avoid circular imports at
353-
runtime
354-
- Maintains complete type safety and mypy compatibility
355-
- All pagination-related code has 100% test coverage
356-
357-
### Resource Integration
358-
359-
Each endpoint that supports pagination has an `as_iterator` parameter that, when
360-
set to `True`, returns a `PaginatedIterator` instead of the raw API response.
361-
This makes it easy to iterate through all pages of results without manually
362-
handling pagination.
363-
364-
## Intraday Data Support
365-
366-
This client implements intraday data endpoints (detailed heart rate, steps, etc)
367-
through the `IntradayResource` class. These endpoints have some special
368-
requirements if you're using them for anyone other that yourself. See the
369-
[Intraday API documentation](https://dev.fitbit.com/build/reference/web-api/intraday/)
370-
for more details.
266+
_This section will be documented as we near our first release._

docs/RATE_LIMITING.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ client = FitbitClient(
4848
redirect_uri="https://localhost:8080",
4949

5050
# Rate limiting options (all optional)
51-
max_retries=5, # Maximum retry attempts (default: 3)
52-
retry_after_seconds=30, # Base wait time if headers missing (default: 60)
53-
retry_backoff_factor=2.0 # Multiplier for successive waits (default: 1.5)
51+
max_retries=3, # Maximum retry attempts (default: 5)
52+
retry_after_seconds=10, # Base wait time if headers missing (default: 30)
53+
retry_backoff_factor=1.5 # Multiplier for successive waits (default: 2.0)
5454
)
5555
```
5656

@@ -70,9 +70,10 @@ The client uses the following strategy for retries:
7070

7171
With the default settings and no headers:
7272

73-
- First retry: Wait 60 seconds
74-
- Second retry: Wait 90 seconds (60 * 1.5)
75-
- Third retry: Wait 135 seconds (60 * 1.5²)
73+
- First retry: Wait 30 seconds
74+
- Second retry: Wait 60 seconds (30 * 2.0)
75+
- Third retry: Wait 240 seconds (60 * 2.0²)
76+
- Fourth retry: Wait 240 seconds (240 * 2.0²)
7677

7778
## Logging
7879

docs/TYPES.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
## Overview
44

5-
Strong typing JSON is complicated. The primary goal for typing in this library
6-
is to help you at least understand at least the outermost data structure of the
7-
API responses. All resource methods (API endpoints) return one of three types:
5+
Strong typing JSON is complicated to do in any meaningful way. In our case, the
6+
primary goal for typing is to help you at least understand at least the
7+
outermost data structure of the API responses. All resource methods (API
8+
endpoints) return one of three types:
89

910
- `JSONDict`: A dictionary containing JSON data
1011
- `JSONList`: A list containing JSON data

fitbit_client/auth/callback_handler.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,9 @@
55
from http.server import HTTPServer
66
from logging import Logger
77
from logging import getLogger
8-
from socket import socket
9-
from typing import Any # Used only for type declarations, not in runtime code
10-
from typing import Callable
8+
from typing import Any
119
from typing import Dict
1210
from typing import List
13-
from typing import Tuple
14-
from typing import Type
1511
from typing import TypeVar
1612
from typing import Union
1713
from urllib.parse import parse_qs
@@ -20,7 +16,6 @@
2016
# Local imports
2117
from fitbit_client.exceptions import InvalidGrantException
2218
from fitbit_client.exceptions import InvalidRequestException
23-
from fitbit_client.utils.types import JSONDict
2419

2520
# Type variable for server
2621
T = TypeVar("T", bound=HTTPServer)

fitbit_client/auth/oauth.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,8 @@
2121

2222
# Local imports
2323
from fitbit_client.auth.callback_server import CallbackServer
24-
from fitbit_client.exceptions import ExpiredTokenException
25-
from fitbit_client.exceptions import InvalidClientException
2624
from fitbit_client.exceptions import InvalidGrantException
2725
from fitbit_client.exceptions import InvalidRequestException
28-
from fitbit_client.exceptions import InvalidTokenException
2926
from fitbit_client.utils.types import TokenDict
3027

3128

fitbit_client/client.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@
88
# isort: off
99
# Auth imports
1010
from fitbit_client.auth.oauth import FitbitOAuth2
11-
from fitbit_client.exceptions import ExpiredTokenException
12-
from fitbit_client.exceptions import InvalidClientException
13-
from fitbit_client.exceptions import InvalidGrantException
14-
from fitbit_client.exceptions import InvalidRequestException
15-
from fitbit_client.exceptions import InvalidTokenException
1611
from fitbit_client.exceptions import OAuthException
1712
from fitbit_client.exceptions import SystemException
1813

fitbit_client/exceptions.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# fitbit_client/exceptions.py
22

33
# Standard library imports
4-
from typing import Any
5-
from typing import Dict
64
from typing import List
75
from typing import Optional
86
from typing import TYPE_CHECKING

fitbit_client/resources/_base.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,15 @@
22

33
# Standard library imports
44
from datetime import datetime
5-
from datetime import timedelta
65
from inspect import currentframe
76
from json import JSONDecodeError
87
from json import dumps
98
from logging import getLogger
109
from time import sleep
11-
from typing import Any
1210
from typing import Dict
1311
from typing import Optional
1412
from typing import Set
15-
from typing import Tuple
16-
from typing import Union
1713
from typing import cast
18-
from typing import overload
19-
from urllib.parse import urlencode
2014

2115
# Third party imports
2216
from requests import Response
@@ -31,7 +25,6 @@
3125
from fitbit_client.utils.curl_debug_mixin import CurlDebugMixin
3226
from fitbit_client.utils.types import FormDataDict
3327
from fitbit_client.utils.types import JSONDict
34-
from fitbit_client.utils.types import JSONList
3528
from fitbit_client.utils.types import JSONType
3629
from fitbit_client.utils.types import ParamDict
3730

@@ -532,7 +525,6 @@ def _make_request(
532525

533526
retries_left = self.max_retries
534527
retry_count = 0
535-
last_exception = None
536528

537529
while True:
538530
try:
@@ -583,7 +575,7 @@ def _make_request(
583575
return None
584576

585577
except Exception as e:
586-
last_exception = e
578+
pass
587579

588580
# Decide whether to retry based on the exception
589581
if retries_left > 0 and self._should_retry_request(e):

0 commit comments

Comments
 (0)