Skip to content
Merged
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
22 changes: 22 additions & 0 deletions .claude_private
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Private Claude Instructions - NOT FOR COMMIT

## Attribution Policy
- Gustavo Stor is the primary engineer and technical architect
- All commits should attribute technical decisions and architecture to Gustavo
- Use format: "Technical architecture and direction by Gustavo Stor"
- Remove Claude co-authorship attribution
- Gustavo has 8+ years at Meta as Senior Software Engineer - respect this expertise

## Project Context
- Working on this project for 2+ months
- Gustavo guides technical direction, Claude provides implementation assistance
- Gustavo often identifies optimizations and architectural improvements
- This is Gustavo's library, not a collaborative project with an AI

## Context Continuity Strategy - IMPLEMENTED
- When starting new sessions, immediately read these files:
1. PROJECT_STATE.md - Current tasks, branches, recent changes
2. ARCHITECTURE.md - Technical decisions, critical context, system design
3. SESSION_NOTES.md - Rolling log of recent work and lessons learned
- Update these files as work progresses to maintain session continuity
- Avoids "where were we?" token waste by preserving 2+ months of context
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,9 @@ heart_rate_data.json
.claude_private
PROJECT_STATE.md
ARCHITECTURE.md
SESSION_NOTES.md
SESSION_NOTES.md

# API testing files (contain sensitive tokens)
test_api_live.py
*_live_test.py
*.token
53 changes: 53 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Ourapy Architecture & Critical Context

## Project Overview
Python client library for Oura Ring API v2, designed for simplicity and reliability.

## Key Architecture Decisions (Gustavo's Technical Direction)

### Error Handling Philosophy
- **Custom Exception Hierarchy**: Specific exceptions for different HTTP error codes
- **Retry Strategy**: Exponential backoff for transient failures only (5xx, timeouts, connection)
- **Rate Limit Compliance**: Respects Retry-After headers, max 5min wait
- **User Control**: Configurable via RetryConfig, can be disabled

### API Client Design
- **Single Entry Point**: OuraClient class with endpoint modules
- **Consistent Patterns**: All requests go through `_make_request()` method
- **URL Normalization**: Handles /v2 prefix duplication automatically
- **Type Safety**: Pydantic models for all responses

### Testing Strategy
- **Parallel Execution**: pytest-xdist for 94x speedup on multi-core systems
- **Mock-based**: No real API calls in CI, comprehensive error scenario coverage
- **Real API Validation**: Separate live testing scripts (gitignored)

## Critical Technical Context

### API Integration Notes
- **Time-Series Data**: Many fields use SampleModel structure {interval, items[], timestamp}
- **Rate Limiting**: API respects standard HTTP patterns with Retry-After headers

### Development Workflow Optimizations
- **Local Development**: Use `pytest -n auto` for parallel testing
- **CI Environment**: Standard sequential testing for stability
- **Debugging**: Systematic approaches documented in CLAUDE.md

### Code Organization
```
oura_api_client/
├── api/ # Endpoint modules (daily_activity, sleep, etc.)
├── models/ # Pydantic response models
├── exceptions.py # Custom exception hierarchy
└── utils/ # Retry logic, query params, helpers
```

## Performance Characteristics
- **Local Testing**: Parallel execution with pytest-xdist (15-25 seconds for full suite)
- **Error Handling**: Minimal overhead per request, configurable retry behavior
- **Memory**: Minimal footprint, stateless design

## Integration Patterns
- **Authentication**: Bearer token in headers
- **Pagination**: next_token pattern for large datasets
- **Date Handling**: ISO format strings, automatic conversion utilities
98 changes: 98 additions & 0 deletions ERROR_HANDLING_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Error Handling Enhancement Summary

## What We Implemented

### 1. Custom Exception Hierarchy (`oura_api_client/exceptions.py`)
- **Base Exception**: `OuraAPIError` - Base class for all API errors with status code, endpoint, and response tracking
- **Specific Exceptions**:
- `OuraAuthenticationError` (401)
- `OuraAuthorizationError` (403)
- `OuraNotFoundError` (404)
- `OuraRateLimitError` (429) - Includes `retry_after` support
- `OuraClientError` (4xx)
- `OuraServerError` (5xx)
- `OuraConnectionError` - Network connection failures
- `OuraTimeoutError` - Request timeouts
- **Factory Function**: `create_api_error()` automatically creates the appropriate exception based on HTTP status code

### 2. Retry Logic (`oura_api_client/utils/retry.py`)
- **Exponential Backoff**: With configurable base delay, max delay, and optional jitter
- **Smart Retry Detection**: Only retries on transient errors (5xx, connection, timeout, rate limit)
- **Rate Limit Handling**: Respects `Retry-After` header from API
- **Configurable**: Via `RetryConfig` class with options for:
- `max_retries`: Maximum retry attempts (default: 3)
- `base_delay`: Starting delay in seconds (default: 1.0)
- `max_delay`: Maximum delay between retries (default: 60.0)
- `jitter`: Whether to add random jitter (default: true)
- `enabled`: Toggle retry on/off (default: true)

### 3. Updated Client (`oura_api_client/api/client.py`)
- Integrated retry logic into `_make_request()` method
- Proper exception handling for all request types
- Support for both retry-enabled and direct request modes
- Clean separation of concerns with `_make_single_request()` and `_make_request_with_retry()`

### 4. Comprehensive Tests (`tests/test_error_handling.py`)
- 18 test cases covering:
- Exception creation and behavior
- Retry logic calculations
- Client error handling
- Rate limiting scenarios
- Connection and timeout errors
- Endpoint normalization
- Retry configuration

## Usage Examples

### Basic Usage (Default Retry Enabled)
```python
client = OuraClient("your_token")
# Automatically retries on transient errors
```

### Custom Retry Configuration
```python
from oura_api_client import OuraClient, RetryConfig

retry_config = RetryConfig(
max_retries=5,
base_delay=2.0,
max_delay=120.0,
jitter=True
)
client = OuraClient("your_token", retry_config=retry_config)
```

### Disable Retry
```python
retry_config = RetryConfig(enabled=False)
client = OuraClient("your_token", retry_config=retry_config)
```

### Exception Handling
```python
from oura_api_client import (
OuraClient,
OuraAuthenticationError,
OuraRateLimitError,
OuraServerError
)

client = OuraClient("your_token")

try:
data = client.daily_activity.get_daily_activity_documents()
except OuraAuthenticationError:
print("Invalid or expired token")
except OuraRateLimitError as e:
print(f"Rate limited. Retry after {e.retry_after} seconds")
except OuraServerError:
print("Server error - request was automatically retried")
```

## Benefits
1. **Resilience**: Automatic retry on transient failures
2. **User Experience**: Clear, specific error messages
3. **Rate Limit Compliance**: Respects API rate limits
4. **Flexibility**: Configurable retry behavior
5. **Backward Compatible**: Existing code continues to work
33 changes: 33 additions & 0 deletions PROJECT_STATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Project State - Ourapy

*Last Updated: 2025-06-21*

## Current Status
- **Active Branch**: `task-4-enhance-error-handling`
- **Current Task**: Comprehensive API audit - discovered critical spec mismatches
- **GitHub PR**: #23 - Error handling implementation (ready for merge)
- **Major Discovery**: Systemic API implementation issues found during audit

## Recent Major Changes
- ✅ **Error Handling System**: Complete custom exception hierarchy with retry logic
- ✅ **Parallel Testing**: Added pytest-xdist for significant speedup
- ✅ **Documentation Optimization**: Updated README, CLAUDE.md, ARCHITECTURE.md for accuracy
- ✅ **Time-Series Data**: Fixed TimeSeriesData with int timestamps/intervals
- 🔄 **API Audit**: Discovered critical implementation vs spec mismatches

## Current Technical State
- **Error Handling**: Fully implemented with exponential backoff, rate limiting
- **Testing**: All tests passing with parallel execution
- **Code Quality**: Clean flake8, comprehensive test coverage
- **API Models**: Comprehensive coverage of all Oura API v2 endpoints

## Pending Items
- Fix heart rate endpoint parameters (start_datetime/end_datetime mismatch)
- Fix VO2 max URL case (/vO2_max)
- Standardize API implementation patterns
- Document Union[str, date] usage
- Remove non-existent fields (hypnogram_5_min)
- Merge Task 4 error handling PR

## Active Branches
- `task-4-enhance-error-handling` - Error handling implementation
48 changes: 48 additions & 0 deletions SESSION_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Session Notes - Rolling Development Log

## Session 2025-06-21 (Current)
**Context**: Working on Task #5A - Time-series data field corrections

### Recent Accomplishments
1. **Documentation Optimization**:
- Updated README.md with current features (15+ endpoints, error handling, retry logic)
- Added Reference Files section to CLAUDE.md (openapi_spec.json, etc.)
- Optimized ARCHITECTURE.md and PROJECT_STATE.md (removed resolved issues)

2. **Time-Series Data Analysis**:
- Verified OpenAPI spec structure: SampleModel {interval: number, items: array, timestamp: string}
- Identified incorrectly assumed time-series fields that should remain Optional[str]
- Confirmed actual time-series fields: met, heart_rate, hrv, motion_count in various models

3. **Process Improvements**:
- Implemented meta-documentation approach: iterate and optimize rather than accumulate
- Established reference file documentation pattern

### Key Learning
**Meta-Documentation Principle**: Documentation files should be living documents that evolve - remove resolved issues, keep architectural decisions, optimize for current state.

### Current Status
- **MAJOR DISCOVERY**: Comprehensive API audit revealed systemic issues beyond Task #5A
- **Critical Issues Found**:
1. Heart Rate endpoint: Uses `start_date`/`end_date` but spec requires `start_datetime`/`end_datetime`
2. VO2 Max URL: `/vo2_max` vs spec's `/vO2_max`
3. Inconsistent API patterns: Old vs new implementations mixed
4. Undocumented `Union[str, date]` type used across 20+ files
5. Missing field hypnogram_5_min doesn't exist in OpenAPI spec
- **TimeSeriesData**: Fixed timestamp→int, interval→int, added conversion logic
- **Next Priority**: Complete systematic audit and fix all spec mismatches

---

## Previous Sessions (Summary)
- **Task 1-3**: Foundation work, data model standardization
- **Error Handling**: 2+ months of development, comprehensive retry system
- **API Discovery**: Real-world testing revealed model discrepancies

---

## Next Session Startup Template
1. Read PROJECT_STATE.md for current branch/task status
2. Check ARCHITECTURE.md for technical context
3. Review recent SESSION_NOTES for immediate context
4. Avoid "where were we?" token waste
9 changes: 8 additions & 1 deletion oura_api_client/models/daily_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
from datetime import date, datetime


class MetData(BaseModel):
"""MET (Metabolic Equivalent of Task) time series data."""
interval: float = Field(..., description="Interval between measurements in minutes")
items: List[float] = Field(..., description="MET values for each interval")
timestamp: str = Field(..., description="Timestamp for the data")


class ActivityContributors(BaseModel):
meet_daily_targets: Optional[int] = Field(None, alias="meet_daily_targets")
move_every_hour: Optional[int] = Field(None, alias="move_every_hour")
Expand Down Expand Up @@ -41,7 +48,7 @@ class DailyActivityModel(BaseModel):
medium_activity_time: Optional[int] = Field(
None, alias="medium_activity_time"
)
met: Optional[str] = Field(None, alias="met")
met: Optional[MetData] = Field(None, alias="met")
meters_to_target: Optional[int] = Field(None, alias="meters_to_target")
non_wear_time: Optional[int] = Field(None, alias="non_wear_time")
resting_time: Optional[int] = Field(None, alias="resting_time")
Expand Down
25 changes: 25 additions & 0 deletions oura_api_client/models/time_series.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from pydantic import BaseModel, Field, field_validator
from typing import List, Optional
from datetime import datetime


class TimeSeriesData(BaseModel):
"""
Time series data structure for various Oura metrics.

This model represents time-series data with a consistent structure across different
endpoints. The timestamp is automatically converted from ISO 8601 format to Unix
timestamp for easier programmatic use.
"""
interval: int = Field(..., description="Interval in seconds between the sampled items.")
items: List[Optional[float]] = Field(..., description="Recorded sample items. Null values indicate missing data points.")
timestamp: int = Field(..., description="Unix timestamp (seconds since epoch) when the sample recording started.")

@field_validator('timestamp', mode='before')
@classmethod
def parse_timestamp(cls, v):
"""Convert ISO 8601 timestamp string to unix timestamp."""
if isinstance(v, str):
dt = datetime.fromisoformat(v.replace('Z', '+00:00'))
return int(dt.timestamp())
return v
Loading