diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 5c2c8d8..0e7b811 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1,41 +1,10 @@ # Conventions, Patterns, and Development Guide -## Contents - -- [Development Environment Setup](#development-environment-setup) - - [Prerequisites](#prerequisites) - - [Initial Setup](#initial-setup) -- [Project Structure](#project-structure) -- [Goals, Notes, and TODOs](#goals-notes-and-todos) -- [Development Tools and Standards](#development-tools-and-standards) - - [Code Formatting and Style](#code-formatting-and-style) - - [Import Style](#import-style) - - [Resource Implementation Guidelines](#resource-implementation-guidelines) - - [Error Handling](#error-handling) - - [Enum Usage](#enum-usage) -- [Logging System](#logging-system) - - [Application Logger](#application-logger) - - [Data Logger](#data-logger) -- [API Design](#api-design) - - [Resource-Based API](#resource-based-api) - - [Method Aliases](#method-aliases) -- [Testing](#testing) - - [Test Organization](#test-organization) - - [Standard Test Fixtures](#standard-test-fixtures) - - [Error Handling Tests](#error-handling-tests) - - [Response Mocking](#response-mocking) -- [OAuth Callback Implementation](#oauth-callback-implementation) - - [Implementation Flow](#implementation-flow) -- [Git Workflow](#git-workflow) -- [Release Process](#release-process) -- [Getting Help](#getting-help) -- [Scope and Limitations - Intraday Data Support](#scope-and-limitations---intraday-data-support) - ## Development Environment Setup ### Prerequisites -- Python 3.12+ +- Python 3.13+ - PDM - Git @@ -53,9 +22,9 @@ cd fitbit-client-python ```bash asdf plugin add python asdf plugin add pdm -asdf install python 3.12.0 +asdf install python 3.13.0 asdf install pdm latest -asdf local python 3.12.0 +asdf local python 3.13.0 asdf local pdm latest ``` @@ -79,7 +48,8 @@ fitbit-client/ │ ├── client.py │ ├── resources/ │ │ ├── __init__.py -│ │ ├── [resource files] +│ │ ├── [resource modules] +│ │ ├── base.py │ │ └── constants.py │ ├── utils/ │ │ ├── __init__.py @@ -186,84 +156,29 @@ The codebase implements a comprehensive error handling system through ## Logging System -The project implements dual logging through the BaseResource class: application -logging for API interactions and data logging for tracking important response -fields. - -### Application Logger - -Each resource class inherits logging functionality from BaseResource, which -initializes a logger with the resource's class name: - -```python -self.logger = getLogger(f"fitbit_client.{self.__class__.__name__}") -``` - -The application logger handles: - -- Success responses at INFO level -- Error responses at ERROR level -- Debug information about requests and responses -- Internal server logging - -The BaseResource.\_log_response method standardizes log message formats: - -- For errors: "[error_type] field_name: message" (if field name available) -- For success: "method succeeded for endpoint (status code)" - -### Data Logger - -The data logger specifically tracks important fields from API responses. -BaseResource defines these fields in IMPORTANT_RESPONSE_FIELDS: - -```python -IMPORTANT_RESPONSE_FIELDS: Set[str] = { - "access", # PUBLIC/PRIVATE/SHARED - "date", # Dates - "dateTime", # Timestamps - "deviceId", # Device IDs - "foodId", # Food resource IDs - "logId", # Log entry IDs - "name", # Resource names - "subscriptionId" # Subscription IDs -} -``` - -The BaseResource.\_log_data method extracts and logs these fields when present -in API responses. - -Data log entries contain: - -- Timestamp (ISO format) -- Method name -- Important fields found in the response - -This logging system provides both operational visibility through the application -logger and structured data capture through the data logger. +The project implements two different logs in through the +[`BaseResource`](fitbit_client/resources/base.py) class: application logging for +API interactions and data logging for tracking important response fields. See +[LOGGING](docs/LOGGING.md) for details. ## API Design -The client implements a dual-level API design pattern that balances both -organization and ease-of-use. - ### Resource-Based API The primary API structure is resource-based, organizing related endpoints into -dedicated resource classes: +dedicated resource classes based on the structure and organzation of the +[Fibit Web API](https://dev.fitbit.com/build/reference/web-api/) itself, e.g. - `client.user` - User profile and badges endpoints - `client.activity` - Activity tracking, goals, and summaries - `client.sleep` - Sleep logs and goals - etc. -This organization provides a clean separation of concerns and makes the code -more maintainable by grouping related functionality. - ### Method Aliases -To improve developer experience, all resource methods are also available -directly from the client instance through aliases. This means developers can -choose between two equivalent approaches: +All resource methods are also available directly from the client instance +through aliases. This means developers can choose between two equivalent +approaches: ```python # Standard resource-based access @@ -275,8 +190,6 @@ client.get_profile() client.get_daily_activity_summary(date="2025-03-06") ``` -#### Rationale for Method Aliases - Method aliases were implemented for several important reasons: 1. **Reduced Verbosity**: Typing `client.resource_name.method_name(...)` with @@ -294,9 +207,9 @@ Method aliases were implemented for several important reasons: \- organization or conciseness. All method aliases are set up in the `_setup_method_aliases()` method in the -`FitbitClient` class, which is called during initialization. Each alias is a -direct reference to the corresponding resource method, ensuring consistent -behavior regardless of how the method is accessed. +[`FitbitClient`](fitbit_client/client.py) class, which is called during +initialization. Each alias is a direct reference to the corresponding resource +method, ensuring consistent behavior regardless of how the method is accessed. ## Testing @@ -305,8 +218,9 @@ across all components. ### Test Organization -The test directory mirrors the main package structure, with corresponding test -modules for each component: +The test directory mirrors the main package structure (except that the root is +named "test" rather than "fitbit_client"), with corresponding test modules for +each component: - auth/: Tests for authentication and OAuth functionality - client/: Tests for the main client implementation @@ -384,46 +298,19 @@ The OAuth callback mechanism is implemented using two main classes: ## Git Workflow 1. Create a new branch for your feature/fix -2. Make your changes, following the style guidelines -3. Run formatting checks (`pdm format`) +2. Make your changes, following the style guidelines (see also: + [LINTING](docs/LINTING.md)) +3. Run formatting checks (`pdm format`) and tests (`pdm run pytest`) 4. Submit a pull request with a clear description of changes ## Release Process This section will be documented as we near our first release. -## Getting Help - -- Check existing issues before creating new ones -- Use issue templates when reporting bugs -- Include Python version and environment details in bug reports - ## Intraday Data Support 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 (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 +through the `IntradayResource` class. These endpoints have some special +requirements if you're using them for anyone other that yourself. See the [Intraday API documentation](https://dev.fitbit.com/build/reference/web-api/intraday/) -for more details on available endpoints and access requirements. +for more details. diff --git a/docs/LINTING.md b/docs/LINTING.md index da42053..69a0d43 100644 --- a/docs/LINTING.md +++ b/docs/LINTING.md @@ -8,7 +8,7 @@ Linting and formatting are handled by [Black](https://github.com/psf/black), ## Running the Linters -You can run all formatters using: +Run all formatters using: ```bash pdm run format @@ -61,6 +61,7 @@ from fitbit_client.resources.base import BaseResource The test suite verifies that all public methods have comprehensive docstrings that follow the Google style format with specific sections: +- API Reference - Args - Returns - Raises diff --git a/docs/NAMING.md b/docs/NAMING.md index 18e3b10..a6ace8d 100644 --- a/docs/NAMING.md +++ b/docs/NAMING.md @@ -4,24 +4,10 @@ The method names in this library are designed to align with the official Fitbit Web API Documentation. When there are inconsistencies in the official -documentation, we follow these principles: - -1. The URL slug in the documentation is the primary reference -2. Method names always use underscores (snake_case), not camelCase -3. The HTTP verb is reflected in the method name prefix: - - `get_`: For HTTP GET operations - - `create_`: For HTTP POST operations that create new resources - - `update_`: For HTTP PUT operations - - `delete_`: For HTTP DELETE operations - - `add_`: Only used in specific contexts where "add" is more intuitive than - "create" - -## API Documentation Alignment - -For any discrepancies between different parts of the official documentation, we -prioritize the URL slug. For example, if the documentation page title says "Get -Time Series by Date" but the URL is ".../get-azm-timeseries-by-date/", our -method will be named `get_azm_timeseries_by_date()`. +documentation, we prioritize the URL slug. For example, if the documentation +page title says "Get **Time Series** by Date" but the URL is +".../get-azm-timeseries-by-date/", our method will be named +`get_azm_timeseries_by_date()`. (not `get_azm_time_series_by_date()`). This approach ensures consistent naming throughout the library and makes methods easier to find based on the official documentation. @@ -29,7 +15,7 @@ easier to find based on the official documentation. ## Inconsistencies in the API The Fitbit API contains several inconsistencies, which our method names and -implementation necessarily reflect. Understanding these inconsistencies can help +implementation dutifully reflect. Understanding these inconsistencies can help you navigate the API more effectively: ### Method Name vs. Functionality Inconsistencies @@ -37,42 +23,23 @@ you navigate the API more effectively: - `create_activity_goals` creates only one goal at a time, despite the plural name - `add_favorite_foods` adds one food at a time, while all other creation methods - use "create" prefix + start with "create". - `get_sleep_goals` returns a single goal, not multiple goals -- Some pluralized methods return lists, while others return dictionaries - containing lists +- Additionally, some pluralized methods return lists, while others return + dictionaries containing lists -### Parameter and Response Format Inconsistencies +For user convenience, these inconsistencies have aliases: -```python -# Sleep endpoint uses a different API version than most other endpoints -client.sleep.get_sleep_log_by_date(date="2025-01-01") # Uses API v1.2 -client.activity.get_daily_activity_summary(date="2025-01-01") # Uses API v1 - -# Sleep date parameters have inconsistent response behavior -# The request uses "2025-01-01" but response might contain data from "2025-01-02" -sleep_data = client.get_sleep_log_by_date(date="2025-01-01") -# A sleep log started on 2025-01-01 at 23:00 and ended on 2025-01-02 at 07:00 -# will be included in the response, but with dateOfSleep="2025-01-02" - -# Pagination parameters vary across endpoints -# Some endpoints require offset=0 -food_log = client.get_food_log(date="2025-01-01", offset=0) # Valid -# Others support arbitrary offsets -badges = client.get_badges(offset=20) # Also valid - -# Date range validations vary by endpoint -# Sleep endpoints allow up to 100 days -sleep_logs = client.get_sleep_log_by_date_range( - start_date="2025-01-01", - end_date="2025-04-10" # 100 days later, valid for sleep endpoint -) -# Activity endpoints allow only 31 days -activity_logs = client.get_activity_log_list( - before_date="2025-02-01", - after_date="2025-01-01" # 31 days earlier, valid for activity endpoint -) -``` +- (`alias` -> `target`) +- `create_activity_goal` -> `create_activity_goals` +- `add_favorite_food` -> `add_favorite_foods` +- `create_favorite_food` -> `add_favorite_foods` +- `delete_favorite_food` -> `delete_favorite_foods` +- `create_sleep_goal` -> `create_sleep_goals` +- `get_sleep_goal` -> `get_sleep_goals` + +These aliases help accommodate different expectations and ensure backward +compatibility. ### Response Structure Inconsistencies @@ -118,20 +85,6 @@ 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. -## Method Aliases - -For user convenience, some methods have aliases: - -- `create_activity_goal` -> `create_activity_goals` -- `add_favorite_food` -> `add_favorite_foods` -- `create_favorite_food` -> `add_favorite_foods` -- `delete_favorite_food` -> `delete_favorite_foods` -- `create_sleep_goal` -> `create_sleep_goals` -- `get_sleep_goal` -> `get_sleep_goals` - -These aliases help accommodate different expectations and ensure backward -compatibility. - ## Resource Structure The client organizes API endpoints into logical resource classes, each diff --git a/docs/TYPES.md b/docs/TYPES.md index e7b30ec..caa68ff 100644 --- a/docs/TYPES.md +++ b/docs/TYPES.md @@ -21,7 +21,7 @@ The base types represent the outermost wrapper of API responses: ```python # These are the actual definitions from utils/types.py JSONDict = Dict[str, JSONType] # A dictionary with string keys and JSON values -JSONList = List[JSONType] # A list of JSON values +JSONList = List[JSONType] # A list of JSON objects ``` Where `JSONType` is a recursive type that can be any valid JSON value: @@ -38,8 +38,9 @@ specify the inner details. The library standardizes response handling in these cases: - HTTP 204 responses (No Content): Return `None` -- DELETE operations: Return `None` (regardless of how the API responds) -- Special formats like TCX/XML: Return as raw strings (not JSON) +- DELETE operations: Return `None` (regardless of how the API responds - the + native response is always empty, but can vary between `""`, `null`, and `{}`). +- TCX/XML is returned as a string ## Response Inconsistencies @@ -64,8 +65,8 @@ For example, `get_activity_timeseries_by_date()` returns: } ``` -This is typed as a `JSONDict`, not a `JSONList`, despite containing a list of -items. +Accordingly, this is typed as a `JSONDict`, not a `JSONList`, despite containing +a list of items. In contrast, these methods do return direct lists (typed as `JSONList`): @@ -114,10 +115,6 @@ Below is a comprehensive list of all method return types by resource class: - `get_lifetime_stats -> JSONDict` - `get_activity_tcx -> str` (XML) -### BaseResource - -- `_make_request -> JSONType` (gets `cast` as a more specific type when called) - ### BodyTimeSeriesResource - `get_body_timeseries_by_date -> JSONDict`