Initial commit
This commit is contained in:
561
skills/sleeptrack-be/references/python_client_implementation.md
Normal file
561
skills/sleeptrack-be/references/python_client_implementation.md
Normal file
@@ -0,0 +1,561 @@
|
||||
# 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)}")
|
||||
```
|
||||
Reference in New Issue
Block a user