562 lines
18 KiB
Markdown
562 lines
18 KiB
Markdown
# Python Client Implementation Guide
|
|
|
|
This reference provides complete Python client implementations for the Asleep API, including advanced patterns for analytics, production usage, and multi-tenant applications.
|
|
|
|
## Complete Python API Client
|
|
|
|
```python
|
|
import os
|
|
import requests
|
|
from typing import Dict, Any, Optional
|
|
|
|
class AsleepClient:
|
|
"""Asleep API client for backend integration"""
|
|
|
|
def __init__(self, api_key: str, base_url: str = "https://api.asleep.ai"):
|
|
self.api_key = api_key
|
|
self.base_url = base_url
|
|
self.session = requests.Session()
|
|
self.session.headers.update({"x-api-key": api_key})
|
|
|
|
def _request(
|
|
self,
|
|
method: str,
|
|
path: str,
|
|
headers: Optional[Dict[str, str]] = None,
|
|
**kwargs
|
|
) -> Dict[str, Any]:
|
|
"""Make authenticated API request with error handling"""
|
|
url = f"{self.base_url}{path}"
|
|
req_headers = self.session.headers.copy()
|
|
if headers:
|
|
req_headers.update(headers)
|
|
|
|
try:
|
|
response = self.session.request(method, url, headers=req_headers, **kwargs)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except requests.exceptions.HTTPError as e:
|
|
# Handle API errors
|
|
if e.response.status_code == 401:
|
|
raise ValueError("Invalid API key")
|
|
elif e.response.status_code == 403:
|
|
error_detail = e.response.json().get("detail", "Access forbidden")
|
|
raise ValueError(f"API access error: {error_detail}")
|
|
elif e.response.status_code == 404:
|
|
raise ValueError("Resource not found")
|
|
else:
|
|
raise
|
|
|
|
# User management methods
|
|
def create_user(self, metadata: Optional[Dict[str, Any]] = None) -> str:
|
|
"""Create new user and return user_id"""
|
|
data = {"metadata": metadata} if metadata else {}
|
|
result = self._request("POST", "/ai/v1/users", json=data)
|
|
return result["result"]["user_id"]
|
|
|
|
def get_user(self, user_id: str) -> Dict[str, Any]:
|
|
"""Get user information"""
|
|
result = self._request("GET", f"/ai/v1/users/{user_id}")
|
|
return result["result"]
|
|
|
|
def delete_user(self, user_id: str) -> None:
|
|
"""Delete user and all associated data"""
|
|
self._request("DELETE", f"/ai/v1/users/{user_id}")
|
|
|
|
# Session management methods
|
|
def get_session(self, session_id: str, user_id: str, timezone: str = "UTC") -> Dict[str, Any]:
|
|
"""Get detailed session data"""
|
|
headers = {"x-user-id": user_id, "timezone": timezone}
|
|
result = self._request("GET", f"/data/v3/sessions/{session_id}", headers=headers)
|
|
return result["result"]
|
|
|
|
def list_sessions(
|
|
self,
|
|
user_id: str,
|
|
date_gte: Optional[str] = None,
|
|
date_lte: Optional[str] = None,
|
|
offset: int = 0,
|
|
limit: int = 20,
|
|
order_by: str = "DESC"
|
|
) -> Dict[str, Any]:
|
|
"""List user sessions with filtering"""
|
|
headers = {"x-user-id": user_id}
|
|
params = {"offset": offset, "limit": limit, "order_by": order_by}
|
|
if date_gte:
|
|
params["date_gte"] = date_gte
|
|
if date_lte:
|
|
params["date_lte"] = date_lte
|
|
|
|
result = self._request("GET", "/data/v1/sessions", headers=headers, params=params)
|
|
return result["result"]
|
|
|
|
def delete_session(self, session_id: str, user_id: str) -> None:
|
|
"""Delete session and all associated data"""
|
|
headers = {"x-user-id": user_id}
|
|
self._request("DELETE", f"/ai/v1/sessions/{session_id}", headers=headers)
|
|
|
|
# Statistics methods
|
|
def get_average_stats(
|
|
self,
|
|
user_id: str,
|
|
start_date: str,
|
|
end_date: str,
|
|
timezone: str = "UTC"
|
|
) -> Dict[str, Any]:
|
|
"""Get average statistics for date range (max 100 days)"""
|
|
headers = {"timezone": timezone}
|
|
params = {"start_date": start_date, "end_date": end_date}
|
|
result = self._request(
|
|
"GET",
|
|
f"/data/v1/users/{user_id}/average-stats",
|
|
headers=headers,
|
|
params=params
|
|
)
|
|
return result["result"]
|
|
|
|
# Usage
|
|
client = AsleepClient(api_key=os.getenv("ASLEEP_API_KEY"))
|
|
```
|
|
|
|
## Advanced Analytics Implementation
|
|
|
|
```python
|
|
from typing import List, Dict
|
|
from datetime import datetime, timedelta
|
|
|
|
class SleepAnalytics:
|
|
"""Backend analytics for sleep tracking platform"""
|
|
|
|
def __init__(self, client: AsleepClient, db):
|
|
self.client = client
|
|
self.db = db
|
|
|
|
def get_user_sleep_score(self, user_id: str, days: int = 30) -> Dict:
|
|
"""Calculate comprehensive sleep score for user"""
|
|
end_date = datetime.now()
|
|
start_date = end_date - timedelta(days=days)
|
|
|
|
stats = self.client.get_average_stats(
|
|
user_id=user_id,
|
|
start_date=start_date.strftime("%Y-%m-%d"),
|
|
end_date=end_date.strftime("%Y-%m-%d")
|
|
)
|
|
|
|
avg = stats['average_stats']
|
|
|
|
# Calculate weighted sleep score (0-100)
|
|
efficiency_score = avg['sleep_efficiency'] # Already 0-100
|
|
consistency_score = self._calculate_consistency_score(stats)
|
|
duration_score = self._calculate_duration_score(avg)
|
|
|
|
overall_score = (
|
|
efficiency_score * 0.4 +
|
|
consistency_score * 0.3 +
|
|
duration_score * 0.3
|
|
)
|
|
|
|
return {
|
|
'overall_score': round(overall_score, 1),
|
|
'efficiency_score': round(efficiency_score, 1),
|
|
'consistency_score': round(consistency_score, 1),
|
|
'duration_score': round(duration_score, 1),
|
|
'period_days': days,
|
|
'session_count': len(stats['slept_sessions'])
|
|
}
|
|
|
|
def _calculate_consistency_score(self, stats: Dict) -> float:
|
|
"""Score based on sleep schedule consistency"""
|
|
# Implement consistency scoring based on variance in sleep times
|
|
# Placeholder implementation
|
|
return 80.0
|
|
|
|
def _calculate_duration_score(self, avg: Dict) -> float:
|
|
"""Score based on sleep duration (7-9 hours optimal)"""
|
|
sleep_hours = avg['time_in_sleep'] / 3600
|
|
|
|
if 7 <= sleep_hours <= 9:
|
|
return 100.0
|
|
elif 6 <= sleep_hours < 7 or 9 < sleep_hours <= 10:
|
|
return 80.0
|
|
elif 5 <= sleep_hours < 6 or 10 < sleep_hours <= 11:
|
|
return 60.0
|
|
else:
|
|
return 40.0
|
|
|
|
def get_cohort_analysis(self, user_ids: List[str], days: int = 30) -> Dict:
|
|
"""Analyze sleep patterns across user cohort"""
|
|
cohort_data = []
|
|
|
|
for user_id in user_ids:
|
|
try:
|
|
score = self.get_user_sleep_score(user_id, days)
|
|
cohort_data.append({
|
|
'user_id': user_id,
|
|
'score': score['overall_score'],
|
|
'efficiency': score['efficiency_score'],
|
|
'sessions': score['session_count']
|
|
})
|
|
except Exception as e:
|
|
print(f"Error fetching data for user {user_id}: {e}")
|
|
|
|
if not cohort_data:
|
|
return {}
|
|
|
|
return {
|
|
'cohort_size': len(cohort_data),
|
|
'avg_score': sum(u['score'] for u in cohort_data) / len(cohort_data),
|
|
'avg_efficiency': sum(u['efficiency'] for u in cohort_data) / len(cohort_data),
|
|
'total_sessions': sum(u['sessions'] for u in cohort_data),
|
|
'users': cohort_data
|
|
}
|
|
|
|
def generate_weekly_report(self, user_id: str) -> Dict:
|
|
"""Generate comprehensive weekly sleep report"""
|
|
stats = self.client.get_average_stats(
|
|
user_id=user_id,
|
|
start_date=(datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d"),
|
|
end_date=datetime.now().strftime("%Y-%m-%d")
|
|
)
|
|
|
|
avg = stats['average_stats']
|
|
|
|
return {
|
|
'period': 'Last 7 days',
|
|
'summary': {
|
|
'avg_sleep_time': avg['sleep_time'],
|
|
'avg_bedtime': avg['start_time'],
|
|
'avg_wake_time': avg['end_time'],
|
|
'avg_efficiency': avg['sleep_efficiency']
|
|
},
|
|
'sleep_stages': {
|
|
'light_hours': avg['time_in_light'] / 3600,
|
|
'deep_hours': avg['time_in_deep'] / 3600,
|
|
'rem_hours': avg['time_in_rem'] / 3600
|
|
},
|
|
'insights': self._generate_insights(avg),
|
|
'session_count': len(stats['slept_sessions'])
|
|
}
|
|
|
|
def _generate_insights(self, avg: Dict) -> List[str]:
|
|
"""Generate personalized sleep insights"""
|
|
insights = []
|
|
|
|
if avg['sleep_efficiency'] < 75:
|
|
insights.append("Your sleep efficiency is below average. Try establishing a consistent bedtime routine.")
|
|
|
|
if avg['deep_ratio'] < 0.15:
|
|
insights.append("You're getting less deep sleep than optimal. Avoid caffeine after 2 PM.")
|
|
|
|
if avg['waso_count'] > 3:
|
|
insights.append("You're waking up frequently during the night. Consider reducing screen time before bed.")
|
|
|
|
return insights
|
|
|
|
# Usage
|
|
analytics = SleepAnalytics(client, db)
|
|
score = analytics.get_user_sleep_score("user123", days=30)
|
|
print(f"Sleep score: {score['overall_score']}/100")
|
|
|
|
report = analytics.generate_weekly_report("user123")
|
|
print(f"Weekly report: {report}")
|
|
```
|
|
|
|
## Multi-Tenant Application Implementation
|
|
|
|
```python
|
|
class MultiTenantSleepTracker:
|
|
"""Multi-tenant sleep tracking backend"""
|
|
|
|
def __init__(self, client: AsleepClient, db):
|
|
self.client = client
|
|
self.db = db
|
|
|
|
def create_organization(self, org_id: str, name: str, settings: Dict) -> Dict:
|
|
"""Create new organization"""
|
|
org = {
|
|
'org_id': org_id,
|
|
'name': name,
|
|
'settings': settings,
|
|
'created_at': datetime.now(),
|
|
'user_count': 0
|
|
}
|
|
self.db.organizations.insert_one(org)
|
|
return org
|
|
|
|
def add_user_to_organization(self, org_id: str, user_email: str, metadata: Dict = None) -> str:
|
|
"""Add user to organization and create Asleep user"""
|
|
# Verify organization exists
|
|
org = self.db.organizations.find_one({'org_id': org_id})
|
|
if not org:
|
|
raise ValueError(f"Organization {org_id} not found")
|
|
|
|
# Create Asleep user
|
|
asleep_user_id = self.client.create_user(metadata=metadata)
|
|
|
|
# Store user mapping
|
|
self.db.users.insert_one({
|
|
'org_id': org_id,
|
|
'user_email': user_email,
|
|
'asleep_user_id': asleep_user_id,
|
|
'metadata': metadata,
|
|
'created_at': datetime.now()
|
|
})
|
|
|
|
# Update organization user count
|
|
self.db.organizations.update_one(
|
|
{'org_id': org_id},
|
|
{'$inc': {'user_count': 1}}
|
|
)
|
|
|
|
return asleep_user_id
|
|
|
|
def get_organization_statistics(self, org_id: str, days: int = 30) -> Dict:
|
|
"""Get aggregated statistics for entire organization"""
|
|
# Get all users in organization
|
|
users = list(self.db.users.find({'org_id': org_id}))
|
|
|
|
end_date = datetime.now()
|
|
start_date = end_date - timedelta(days=days)
|
|
|
|
org_stats = {
|
|
'org_id': org_id,
|
|
'user_count': len(users),
|
|
'period_days': days,
|
|
'users_data': []
|
|
}
|
|
|
|
total_efficiency = 0
|
|
total_sleep_time = 0
|
|
total_sessions = 0
|
|
|
|
for user in users:
|
|
try:
|
|
stats = self.client.get_average_stats(
|
|
user_id=user['asleep_user_id'],
|
|
start_date=start_date.strftime("%Y-%m-%d"),
|
|
end_date=end_date.strftime("%Y-%m-%d")
|
|
)
|
|
|
|
avg = stats['average_stats']
|
|
session_count = len(stats['slept_sessions'])
|
|
|
|
org_stats['users_data'].append({
|
|
'user_email': user['user_email'],
|
|
'efficiency': avg['sleep_efficiency'],
|
|
'sleep_time': avg['sleep_time'],
|
|
'session_count': session_count
|
|
})
|
|
|
|
total_efficiency += avg['sleep_efficiency']
|
|
total_sleep_time += avg['time_in_sleep']
|
|
total_sessions += session_count
|
|
|
|
except Exception as e:
|
|
print(f"Error fetching stats for user {user['user_email']}: {e}")
|
|
|
|
if users:
|
|
org_stats['avg_efficiency'] = total_efficiency / len(users)
|
|
org_stats['avg_sleep_hours'] = (total_sleep_time / len(users)) / 3600
|
|
org_stats['total_sessions'] = total_sessions
|
|
|
|
return org_stats
|
|
|
|
# Usage
|
|
tracker = MultiTenantSleepTracker(client, db)
|
|
|
|
# Create organization
|
|
tracker.create_organization(
|
|
org_id="acme-corp",
|
|
name="Acme Corporation",
|
|
settings={'timezone': 'America/New_York'}
|
|
)
|
|
|
|
# Add users
|
|
tracker.add_user_to_organization("acme-corp", "john@acme.com")
|
|
tracker.add_user_to_organization("acme-corp", "jane@acme.com")
|
|
|
|
# Get organization stats
|
|
org_stats = tracker.get_organization_statistics("acme-corp", days=30)
|
|
print(f"Organization average efficiency: {org_stats['avg_efficiency']:.1f}%")
|
|
```
|
|
|
|
## FastAPI Backend Example
|
|
|
|
```python
|
|
from fastapi import FastAPI, HTTPException, Depends, Header
|
|
from typing import Optional
|
|
import os
|
|
|
|
app = FastAPI()
|
|
asleep_client = AsleepClient(api_key=os.getenv("ASLEEP_API_KEY"))
|
|
|
|
# Authentication dependency
|
|
async def verify_app_token(authorization: str = Header(...)):
|
|
"""Verify mobile app authentication"""
|
|
if not authorization.startswith("Bearer "):
|
|
raise HTTPException(status_code=401, detail="Invalid authorization header")
|
|
|
|
token = authorization[7:]
|
|
# Verify token with your auth system
|
|
user = verify_jwt_token(token)
|
|
if not user:
|
|
raise HTTPException(status_code=401, detail="Invalid token")
|
|
|
|
return user
|
|
|
|
# Proxy endpoints
|
|
@app.post("/api/users")
|
|
async def create_user(
|
|
metadata: Optional[dict] = None,
|
|
user: dict = Depends(verify_app_token)
|
|
):
|
|
"""Create Asleep user for authenticated app user"""
|
|
try:
|
|
# Create user in Asleep
|
|
asleep_user_id = asleep_client.create_user(metadata=metadata)
|
|
|
|
# Store mapping in your database
|
|
db.user_mappings.insert_one({
|
|
'app_user_id': user['id'],
|
|
'asleep_user_id': asleep_user_id,
|
|
'created_at': datetime.now()
|
|
})
|
|
|
|
return {"user_id": asleep_user_id}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
@app.get("/api/sessions/{session_id}")
|
|
async def get_session(
|
|
session_id: str,
|
|
user: dict = Depends(verify_app_token)
|
|
):
|
|
"""Get session data for authenticated user"""
|
|
# Get Asleep user ID from mapping
|
|
mapping = db.user_mappings.find_one({'app_user_id': user['id']})
|
|
if not mapping:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
asleep_user_id = mapping['asleep_user_id']
|
|
|
|
# Fetch session from Asleep
|
|
try:
|
|
session = asleep_client.get_session(session_id, asleep_user_id)
|
|
return session
|
|
except ValueError as e:
|
|
raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
@app.get("/api/sessions")
|
|
async def list_sessions(
|
|
date_gte: Optional[str] = None,
|
|
date_lte: Optional[str] = None,
|
|
user: dict = Depends(verify_app_token)
|
|
):
|
|
"""List sessions for authenticated user"""
|
|
mapping = db.user_mappings.find_one({'app_user_id': user['id']})
|
|
if not mapping:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
asleep_user_id = mapping['asleep_user_id']
|
|
|
|
sessions = asleep_client.list_sessions(
|
|
user_id=asleep_user_id,
|
|
date_gte=date_gte,
|
|
date_lte=date_lte
|
|
)
|
|
|
|
return sessions
|
|
|
|
@app.get("/api/statistics")
|
|
async def get_statistics(
|
|
start_date: str,
|
|
end_date: str,
|
|
user: dict = Depends(verify_app_token)
|
|
):
|
|
"""Get average statistics for authenticated user"""
|
|
mapping = db.user_mappings.find_one({'app_user_id': user['id']})
|
|
if not mapping:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
asleep_user_id = mapping['asleep_user_id']
|
|
|
|
stats = asleep_client.get_average_stats(
|
|
user_id=asleep_user_id,
|
|
start_date=start_date,
|
|
end_date=end_date
|
|
)
|
|
|
|
return stats
|
|
```
|
|
|
|
## Monthly Trends Analysis
|
|
|
|
```python
|
|
from datetime import datetime, timedelta
|
|
from typing import List, Dict
|
|
|
|
def get_monthly_trends(client, user_id: str, months: int = 6) -> List[Dict]:
|
|
"""Get monthly sleep trends for the past N months"""
|
|
trends = []
|
|
today = datetime.now()
|
|
|
|
for i in range(months):
|
|
# Calculate month boundaries
|
|
end_date = today.replace(day=1) - timedelta(days=i * 30)
|
|
start_date = end_date - timedelta(days=30)
|
|
|
|
try:
|
|
stats = client.get_average_stats(
|
|
user_id=user_id,
|
|
start_date=start_date.strftime("%Y-%m-%d"),
|
|
end_date=end_date.strftime("%Y-%m-%d")
|
|
)
|
|
|
|
trends.append({
|
|
'month': end_date.strftime("%Y-%m"),
|
|
'avg_sleep_time': stats['average_stats']['sleep_time'],
|
|
'avg_efficiency': stats['average_stats']['sleep_efficiency'],
|
|
'session_count': len(stats['slept_sessions'])
|
|
})
|
|
except Exception as e:
|
|
print(f"Error fetching stats for {end_date.strftime('%Y-%m')}: {e}")
|
|
|
|
return trends
|
|
|
|
# Usage
|
|
trends = get_monthly_trends(client, "user123", months=6)
|
|
for trend in trends:
|
|
print(f"{trend['month']}: {trend['avg_sleep_time']} sleep, "
|
|
f"{trend['avg_efficiency']:.1f}% efficiency, "
|
|
f"{trend['session_count']} sessions")
|
|
```
|
|
|
|
## Pagination Pattern
|
|
|
|
```python
|
|
# Fetch all sessions with pagination
|
|
all_sessions = []
|
|
offset = 0
|
|
limit = 100
|
|
|
|
while True:
|
|
result = client.list_sessions(
|
|
user_id="user123",
|
|
date_gte="2024-01-01",
|
|
date_lte="2024-12-31",
|
|
offset=offset,
|
|
limit=limit
|
|
)
|
|
|
|
sessions = result['sleep_session_list']
|
|
all_sessions.extend(sessions)
|
|
|
|
if len(sessions) < limit:
|
|
break
|
|
|
|
offset += limit
|
|
|
|
print(f"Total sessions: {len(all_sessions)}")
|
|
```
|