diff --git a/oura_api_client/models/daily_sleep.py b/oura_api_client/models/daily_sleep.py index ed274b9..038c10f 100644 --- a/oura_api_client/models/daily_sleep.py +++ b/oura_api_client/models/daily_sleep.py @@ -1,14 +1,10 @@ from pydantic import BaseModel, Field from typing import List, Optional - from datetime import date, datetime - class SleepContributors(BaseModel): - deep_sleep: Optional[int] = Field( - None, alias="deep_sleep" - ) + deep_sleep: Optional[int] = Field(None, alias="deep_sleep") efficiency: Optional[int] = Field(None, alias="efficiency") latency: Optional[int] = Field(None, alias="latency") rem_sleep: Optional[int] = Field(None, alias="rem_sleep") # REM sleep in minutes @@ -17,112 +13,38 @@ class SleepContributors(BaseModel): total_sleep: Optional[int] = Field(None, alias="total_sleep") # Total sleep in minutes - - - - class DailySleepModel(BaseModel): id: str contributors: SleepContributors day: date timestamp: datetime - score: Optional[int] = Field( - - - - - None, alias="score" - - - - -) + score: Optional[int] = Field(None, alias="score") bedtime_end: Optional[datetime] = Field(None, alias="bedtime_end") bedtime_start: Optional[datetime] = Field(None, alias="bedtime_start") breath_average: Optional[float] = Field(None, alias="breath_average") - deep_sleep_duration: Optional[int] = Field( - - None, alias="deep_sleep_duration" - - ) + deep_sleep_duration: Optional[int] = Field(None, alias="deep_sleep_duration") efficiency: Optional[int] = Field(None, alias="efficiency") - heart_rate_average: Optional[float] = Field( - - None, alias="heart_rate_average" - - ) - heart_rate_lowest: Optional[float] = Field( - - None, alias="heart_rate_lowest" - - ) + heart_rate_average: Optional[float] = Field(None, alias="heart_rate_average") + heart_rate_lowest: Optional[float] = Field(None, alias="heart_rate_lowest") hypnogram_5_min: Optional[str] = Field(None, alias="hypnogram_5_min") latency: Optional[int] = Field(None, alias="latency") - light_sleep_duration: Optional[int] = Field( - - None, alias="light_sleep_duration" - - ) + light_sleep_duration: Optional[int] = Field(None, alias="light_sleep_duration") low_battery_alert: Optional[bool] = Field(None, alias="low_battery_alert") - readiness_score_delta: Optional[int] = Field( - - None, alias="readiness_score_delta" - - ) - rem_sleep_duration: Optional[int] = Field( - - None, alias="rem_sleep_duration" - - ) + readiness_score_delta: Optional[int] = Field(None, alias="readiness_score_delta") + rem_sleep_duration: Optional[int] = Field(None, alias="rem_sleep_duration") restless_periods: Optional[int] = Field(None, alias="restless_periods") - sleep_phase_5_min: Optional[str] = Field( - - None, alias="sleep_phase_5_min" - - ) # Deprecated + sleep_phase_5_min: Optional[str] = Field(None, alias="sleep_phase_5_min") # Deprecated time_in_bed: Optional[int] = Field(None, alias="time_in_bed") - total_sleep_duration: Optional[int] = Field( - - None, alias="total_sleep_duration" - - ) - type: Optional[str] = Field( - - None, alias="type" - - ) # Enum: "deleted", "long_sleep", "main_sleep", "nap", "rest" + total_sleep_duration: Optional[int] = Field(None, alias="total_sleep_duration") + type: Optional[str] = Field(None, alias="type") # Enum: "deleted", "long_sleep", "main_sleep", "nap", "rest" average_hrv: Optional[float] = Field(None, alias="average_hrv") awake_time: Optional[int] = Field(None, alias="awake_time") - hr_60_second_average: Optional[List[int]] = Field( - - None, alias="hr_60_second_average" - - ) # New in v2.10 - hrv_4_hour_average: Optional[List[float]] = Field( - - None, alias="hrv_4_hour_average" - - ) # New in v2.10 - readiness: Optional[str] = Field( - - None, alias="readiness" - - ) # New in v2.10, but type not specified, assuming string for now - temperature_delta: Optional[float] = Field( - - None, alias="temperature_delta" - - ) - temperature_deviation: Optional[float] = Field( - - None, alias="temperature_deviation" - - ) # Deprecated - temperature_trend_deviation: Optional[float] = Field( - - None, alias="temperature_trend_deviation" - - ) + hr_60_second_average: Optional[List[int]] = Field(None, alias="hr_60_second_average") # New in v2.10 + hrv_4_hour_average: Optional[List[float]] = Field(None, alias="hrv_4_hour_average") # New in v2.10 + readiness: Optional[str] = Field(None, alias="readiness") # New in v2.10, but type not specified, assuming string for now + temperature_delta: Optional[float] = Field(None, alias="temperature_delta") + temperature_deviation: Optional[float] = Field(None, alias="temperature_deviation") # Deprecated + temperature_trend_deviation: Optional[float] = Field(None, alias="temperature_trend_deviation") class DailySleepResponse(BaseModel): diff --git a/oura_api_client/models/heartrate.py b/oura_api_client/models/heartrate.py index a1c379f..ccd0fa2 100644 --- a/oura_api_client/models/heartrate.py +++ b/oura_api_client/models/heartrate.py @@ -1,15 +1,13 @@ """Models for heart rate data.""" -from dataclasses import dataclass +from pydantic import BaseModel from typing import List, Optional from datetime import datetime -@dataclass - -class HeartRateSample: +class HeartRateSample(BaseModel): """Represents a single heart rate data point.""" - + timestamp: datetime bpm: int source: str @@ -17,39 +15,36 @@ class HeartRateSample: @classmethod def from_dict(cls, data: dict) -> "HeartRateSample": """Create a HeartRateSample from API response dictionary. - + + Note: This method is kept for backward compatibility. + Pydantic can parse directly from dict using HeartRateSample(**data) + Args: data: Dictionary containing heart rate data - + Returns: HeartRateSample: Instantiated object """ - return cls( - timestamp=datetime.fromisoformat(data["timestamp"]), - bpm=data["bpm"], - source=data["source"], - ) + return cls(**data) -@dataclass - -class HeartRateResponse: +class HeartRateResponse(BaseModel): """Represents the full heart rate response.""" - + data: List[HeartRateSample] next_token: Optional[str] = None @classmethod def from_dict(cls, response: dict) -> "HeartRateResponse": """Create a HeartRateResponse from API response dictionary. - + + Note: This method is kept for backward compatibility. + Pydantic can parse directly from dict using HeartRateResponse(**response) + Args: response: Dictionary containing API response - + Returns: HeartRateResponse: Instantiated object """ - return cls( - data=[HeartRateSample.from_dict(item) for item in response.get("data", [])], - next_token=response.get("next_token"), - ) + return cls(**response) diff --git a/oura_api_client/models/personal.py b/oura_api_client/models/personal.py index 3ca6817..f00638e 100644 --- a/oura_api_client/models/personal.py +++ b/oura_api_client/models/personal.py @@ -1,15 +1,13 @@ """Models for personal information data.""" -from dataclasses import dataclass +from pydantic import BaseModel from typing import Optional from datetime import date -@dataclass - -class PersonalInfo: +class PersonalInfo(BaseModel): """Represents personal information for a user.""" - + id: str email: str age: int @@ -21,23 +19,14 @@ class PersonalInfo: @classmethod def from_dict(cls, data: dict) -> "PersonalInfo": """Create a PersonalInfo object from API response dictionary. - + + Note: This method is kept for backward compatibility. + Pydantic can parse directly from dict using PersonalInfo(**data) + Args: data: Dictionary containing personal info data - + Returns: PersonalInfo: Instantiated object """ - birth_date = None - if data.get("birth_date"): - birth_date = date.fromisoformat(data["birth_date"]) - - return cls( - id=data["id"], - email=data["email"], - age=data["age"], - weight=data.get("weight"), - height=data.get("height"), - biological_sex=data.get("biological_sex"), - birth_date=birth_date, - ) + return cls(**data) diff --git a/oura_api_client/models/sleep.py b/oura_api_client/models/sleep.py index f31ce8e..dd1350b 100644 --- a/oura_api_client/models/sleep.py +++ b/oura_api_client/models/sleep.py @@ -1,123 +1,64 @@ from pydantic import BaseModel, Field from typing import List, Optional -from datetime import date, datetime # Added date -from oura_api_client.models.daily_readiness import ReadinessContributors # Reusing ReadinessContributors -from oura_api_client.models.daily_sleep import SleepContributors # Reusing SleepContributors +from datetime import date, datetime -class SleepModel(BaseModel): - id: str - average_breath: Optional[float] = Field( - None, alias="average_breath" - ) # New based on common sleep metrics - average_heart_rate: Optional[float] = Field( +class SleepContributors(BaseModel): + """Sleep contributors model for sleep data.""" + deep_sleep: Optional[int] = Field(None, alias="deep_sleep") + efficiency: Optional[int] = Field(None, alias="efficiency") + latency: Optional[int] = Field(None, alias="latency") + rem_sleep: Optional[int] = Field(None, alias="rem_sleep") + restfulness: Optional[int] = Field(None, alias="restfulness") + timing: Optional[int] = Field(None, alias="timing") + total_sleep: Optional[int] = Field(None, alias="total_sleep") - None, alias="average_heart_rate" - ) - average_hrv: Optional[int] = Field( +class ReadinessContributors(BaseModel): + """Readiness contributors model for sleep data.""" + activity_balance: Optional[int] = Field(None, alias="activity_balance") + body_temperature: Optional[int] = Field(None, alias="body_temperature") + hrv_balance: Optional[int] = Field(None, alias="hrv_balance") + previous_day_activity: Optional[int] = Field(None, alias="previous_day_activity") + previous_night: Optional[int] = Field(None, alias="previous_night") + recovery_index: Optional[int] = Field(None, alias="recovery_index") + resting_heart_rate: Optional[int] = Field(None, alias="resting_heart_rate") + sleep_balance: Optional[int] = Field(None, alias="sleep_balance") - None, alias="average_hrv" - ) # Changed type to int based on typical HRV units +class SleepModel(BaseModel): + id: str + average_breath: Optional[float] = Field(None, alias="average_breath") + average_heart_rate: Optional[float] = Field(None, alias="average_heart_rate") + average_hrv: Optional[int] = Field(None, alias="average_hrv") awake_time: Optional[int] = Field(None, alias="awake_time") bedtime_end: Optional[datetime] = Field(None, alias="bedtime_end") bedtime_start: Optional[datetime] = Field(None, alias="bedtime_start") - day: date # Added day - deep_sleep_duration: Optional[int] = Field( - - None, alias="deep_sleep_duration" - - ) + day: date + deep_sleep_duration: Optional[int] = Field(None, alias="deep_sleep_duration") efficiency: Optional[int] = Field(None, alias="efficiency") - heart_rate: Optional[str] = Field( - - None, alias="heart_rate" - - ) # Assuming string for heart_rate, adjust if it's a more complex type - hrv: Optional[str] = Field( - - None, alias="hrv" - - ) # Assuming string for hrv, adjust if it's a more complex type + heart_rate: Optional[str] = Field(None, alias="heart_rate") + hrv: Optional[str] = Field(None, alias="hrv") latency: Optional[int] = Field(None, alias="latency") - light_sleep_duration: Optional[int] = Field( - - None, alias="light_sleep_duration" - - ) + light_sleep_duration: Optional[int] = Field(None, alias="light_sleep_duration") low_battery_alert: Optional[bool] = Field(None, alias="low_battery_alert") - lowest_heart_rate: Optional[int] = Field( - - None, alias="lowest_heart_rate" - - ) # Changed type to int + lowest_heart_rate: Optional[int] = Field(None, alias="lowest_heart_rate") movement_30_sec: Optional[str] = Field(None, alias="movement_30_sec") period: Optional[int] = Field(None, alias="period") - readiness: Optional[ReadinessContributors] = Field( - - None, alias="readiness" - - ) # Reused ReadinessContributors - readiness_score_delta: Optional[int] = Field( - - None, alias="readiness_score_delta" - - ) - rem_sleep_duration: Optional[int] = Field( - - None, alias="rem_sleep_duration" - - ) - restless_periods: Optional[int] = Field( - - None, alias="restless_periods" - - ) # Added from daily_sleep - # score is usually part of daily summaries, but can be part of a detailed sleep document - score: Optional[int] = Field( - - None, alias="score" - - ) + readiness: Optional[ReadinessContributors] = Field(None, alias="readiness") + readiness_score_delta: Optional[int] = Field(None, alias="readiness_score_delta") + rem_sleep_duration: Optional[int] = Field(None, alias="rem_sleep_duration") + restless_periods: Optional[int] = Field(None, alias="restless_periods") + score: Optional[int] = Field(None, alias="score") sleep_phase_5_min: Optional[str] = Field(None, alias="sleep_phase_5_min") - sleep_score_delta: Optional[int] = Field( - - None, alias="sleep_score_delta" - - ) # New, similar to readiness_score_delta - sleep_algorithm_version: Optional[str] = Field( - - None, alias="sleep_algorithm_version" - - ) # New - temperature_delta: Optional[float] = Field( - - None, alias="temperature_delta" - - ) - temperature_deviation: Optional[float] = Field( - - None, alias="temperature_deviation" - - ) # Deprecated in daily_readiness - temperature_trend_deviation: Optional[float] = Field( - - None, alias="temperature_trend_deviation" - - ) # From daily_readiness + sleep_score_delta: Optional[int] = Field(None, alias="sleep_score_delta") + sleep_algorithm_version: Optional[str] = Field(None, alias="sleep_algorithm_version") + temperature_delta: Optional[float] = Field(None, alias="temperature_delta") + temperature_deviation: Optional[float] = Field(None, alias="temperature_deviation") + temperature_trend_deviation: Optional[float] = Field(None, alias="temperature_trend_deviation") time_in_bed: Optional[int] = Field(None, alias="time_in_bed") - total_sleep_duration: Optional[int] = Field( - - None, alias="total_sleep_duration" - - ) - type: Optional[str] = Field( - - None, alias="type" - - ) # From daily_sleep (e.g. "main_sleep", "nap") - # contributors from daily_sleep.py, as requested by the task + total_sleep_duration: Optional[int] = Field(None, alias="total_sleep_duration") + type: Optional[str] = Field(None, alias="type") contributors: SleepContributors diff --git a/tests/test_client.py b/tests/test_client.py index 5c107fe..0b99622 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -10,12 +10,12 @@ DailyActivityResponse, DailyActivityModel, ActivityContributors ) from oura_api_client.models.daily_sleep import ( - DailySleepResponse, DailySleepModel, SleepContributors + DailySleepResponse, DailySleepModel, SleepContributors as DailySleepContributors ) from oura_api_client.models.daily_readiness import ( - DailyReadinessResponse, DailyReadinessModel, ReadinessContributors + DailyReadinessResponse, DailyReadinessModel, ReadinessContributors as DailyReadinessContributors ) -from oura_api_client.models.sleep import SleepResponse, SleepModel +from oura_api_client.models.sleep import SleepResponse, SleepModel, SleepContributors, ReadinessContributors from oura_api_client.models.session import SessionResponse, SessionModel from oura_api_client.models.tag import TagResponse, TagModel from oura_api_client.models.workout import WorkoutResponse, WorkoutModel @@ -357,7 +357,7 @@ def test_get_daily_sleep_documents(self, mock_get): self.assertIsInstance(daily_sleep_response.data[0], DailySleepModel) self.assertIsInstance( daily_sleep_response.data[0].contributors, - SleepContributors + DailySleepContributors ) self.assertEqual(daily_sleep_response.next_token, "next_sleep_token") @@ -443,7 +443,7 @@ def test_get_daily_sleep_document(self, mock_get): self.assertIsInstance(daily_sleep_document, DailySleepModel) self.assertEqual(daily_sleep_document.id, document_id) self.assertIsInstance( - daily_sleep_document.contributors, SleepContributors + daily_sleep_document.contributors, DailySleepContributors ) self.assertEqual(daily_sleep_document.score, 85) self.assertEqual( @@ -518,7 +518,7 @@ def test_get_daily_readiness_documents(self, mock_get): ) self.assertIsInstance( daily_readiness_response.data[0].contributors, - ReadinessContributors + DailyReadinessContributors ) self.assertEqual( daily_readiness_response.next_token, "next_readiness_token" @@ -612,7 +612,7 @@ def test_get_daily_readiness_document(self, mock_get): self.assertEqual(daily_readiness_document.id, document_id) self.assertIsInstance( daily_readiness_document.contributors, - ReadinessContributors + DailyReadinessContributors ) self.assertEqual(daily_readiness_document.score, 78) self.assertEqual(