otf-api 0.8.2__py3-none-any.whl → 0.9.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. otf_api/__init__.py +7 -4
  2. otf_api/api.py +699 -480
  3. otf_api/auth/__init__.py +4 -0
  4. otf_api/auth/auth.py +234 -0
  5. otf_api/auth/user.py +66 -0
  6. otf_api/auth/utils.py +129 -0
  7. otf_api/exceptions.py +38 -5
  8. otf_api/filters.py +97 -0
  9. otf_api/logging.py +19 -0
  10. otf_api/models/__init__.py +27 -38
  11. otf_api/models/body_composition_list.py +47 -50
  12. otf_api/models/bookings.py +63 -87
  13. otf_api/models/challenge_tracker_content.py +42 -21
  14. otf_api/models/challenge_tracker_detail.py +68 -48
  15. otf_api/models/classes.py +53 -62
  16. otf_api/models/enums.py +108 -30
  17. otf_api/models/lifetime_stats.py +59 -45
  18. otf_api/models/member_detail.py +95 -115
  19. otf_api/models/member_membership.py +18 -17
  20. otf_api/models/member_purchases.py +21 -127
  21. otf_api/models/mixins.py +37 -33
  22. otf_api/models/notifications.py +17 -0
  23. otf_api/models/out_of_studio_workout_history.py +22 -31
  24. otf_api/models/performance_summary_detail.py +47 -42
  25. otf_api/models/performance_summary_list.py +19 -37
  26. otf_api/models/studio_detail.py +51 -98
  27. otf_api/models/studio_services.py +27 -48
  28. otf_api/models/telemetry.py +14 -5
  29. otf_api/utils.py +134 -0
  30. {otf_api-0.8.2.dist-info → otf_api-0.9.1.dist-info}/METADATA +21 -10
  31. otf_api-0.9.1.dist-info/RECORD +35 -0
  32. {otf_api-0.8.2.dist-info → otf_api-0.9.1.dist-info}/WHEEL +1 -1
  33. otf_api/auth.py +0 -316
  34. otf_api/models/book_class.py +0 -89
  35. otf_api/models/cancel_booking.py +0 -49
  36. otf_api/models/favorite_studios.py +0 -106
  37. otf_api/models/latest_agreement.py +0 -21
  38. otf_api/models/telemetry_hr_history.py +0 -34
  39. otf_api/models/telemetry_max_hr.py +0 -13
  40. otf_api/models/total_classes.py +0 -8
  41. otf_api-0.8.2.dist-info/AUTHORS.md +0 -9
  42. otf_api-0.8.2.dist-info/RECORD +0 -36
  43. {otf_api-0.8.2.dist-info → otf_api-0.9.1.dist-info}/LICENSE +0 -0
@@ -4,65 +4,85 @@ from typing import Any
4
4
  from pydantic import Field
5
5
 
6
6
  from otf_api.models.base import OtfItemBase
7
+ from otf_api.models.enums import ChallengeCategory, EquipmentType
7
8
 
8
9
 
9
10
  class MetricEntry(OtfItemBase):
10
- title: str = Field(..., alias="Title")
11
- equipment_id: int = Field(..., alias="EquipmentId")
12
- entry_type: str = Field(..., alias="EntryType")
13
- metric_key: str = Field(..., alias="MetricKey")
14
- min_value: str = Field(..., alias="MinValue")
15
- max_value: str = Field(..., alias="MaxValue")
11
+ title: str | None = Field(None, alias="Title")
12
+ equipment_id: EquipmentType | None = Field(None, alias="EquipmentId")
13
+ entry_type: str | None = Field(None, alias="EntryType")
14
+ metric_key: str | None = Field(None, alias="MetricKey")
15
+ min_value: str | None = Field(None, alias="MinValue")
16
+ max_value: str | None = Field(None, alias="MaxValue")
16
17
 
17
18
 
18
19
  class BenchmarkHistory(OtfItemBase):
19
- studio_name: str = Field(..., alias="StudioName")
20
- equipment_id: int = Field(..., alias="EquipmentId")
21
- result: float | str = Field(..., alias="Result")
22
- date_created: datetime = Field(..., alias="DateCreated")
23
- date_updated: datetime = Field(..., alias="DateUpdated")
24
- class_time: datetime = Field(..., alias="ClassTime")
20
+ studio_name: str | None = Field(None, alias="StudioName")
21
+ equipment_id: EquipmentType | None = Field(None, alias="EquipmentId")
22
+ class_time: datetime | None = Field(None, alias="ClassTime")
25
23
  challenge_sub_category_id: int | None = Field(None, alias="ChallengeSubCategoryId")
26
- class_id: int = Field(..., alias="ClassId")
27
- substitute_id: int | None = Field(None, alias="SubstituteId")
28
- weight_lbs: int = Field(..., alias="WeightLBS")
29
- class_name: str = Field(..., alias="ClassName")
30
- coach_name: str = Field(..., alias="CoachName")
31
- coach_image_url: str | None = Field(None, alias="CoachImageUrl", exclude=True)
32
- workout_type_id: int | None = Field(None, alias="WorkoutTypeId")
33
- workout_id: int | None = Field(None, alias="WorkoutId")
34
- linked_challenges: list[Any] | None = Field(
35
- None, alias="LinkedChallenges", exclude=True
36
- ) # not sure what this will be, never seen it before
24
+ weight_lbs: int | None = Field(None, alias="WeightLBS")
25
+ class_name: str | None = Field(None, alias="ClassName")
26
+ coach_name: str | None = Field(None, alias="CoachName")
27
+ result: float | str | None = Field(None, alias="Result")
28
+ workout_type_id: int | None = Field(None, alias="WorkoutTypeId", exclude=True, repr=False)
29
+ workout_id: int | None = Field(None, alias="WorkoutId", exclude=True, repr=False)
30
+ linked_challenges: list[Any] | None = Field(None, alias="LinkedChallenges", exclude=True, repr=False)
31
+
32
+ date_created: datetime | None = Field(
33
+ None,
34
+ alias="DateCreated",
35
+ exclude=True,
36
+ repr=False,
37
+ description="When the entry was created in database, not useful to users",
38
+ )
39
+ date_updated: datetime | None = Field(
40
+ None,
41
+ alias="DateUpdated",
42
+ exclude=True,
43
+ repr=False,
44
+ description="When the entry was updated in database, not useful to users",
45
+ )
46
+ class_id: int | None = Field(None, alias="ClassId", exclude=True, repr=False, description="Not used by API")
47
+ substitute_id: int | None = Field(
48
+ None, alias="SubstituteId", exclude=True, repr=False, description="Not used by API, also always seems to be 0"
49
+ )
37
50
 
38
51
 
39
52
  class ChallengeHistory(OtfItemBase):
40
- challenge_objective: str = Field(..., alias="ChallengeObjective")
41
- challenge_id: int = Field(..., alias="ChallengeId")
42
- studio_id: int = Field(..., alias="StudioId")
43
- studio_name: str = Field(..., alias="StudioName")
44
- start_date: datetime = Field(..., alias="StartDate")
45
- end_date: datetime = Field(..., alias="EndDate")
46
- total_result: float | str = Field(..., alias="TotalResult")
47
- is_finished: bool = Field(..., alias="IsFinished")
48
- benchmark_histories: list[BenchmarkHistory] = Field(..., alias="BenchmarkHistories")
53
+ studio_name: str | None = Field(None, alias="StudioName")
54
+ start_date: datetime | None = Field(None, alias="StartDate")
55
+ end_date: datetime | None = Field(None, alias="EndDate")
56
+ total_result: float | str | None = Field(None, alias="TotalResult")
57
+ is_finished: bool | None = Field(None, alias="IsFinished")
58
+ benchmark_histories: list[BenchmarkHistory] = Field(default_factory=list, alias="BenchmarkHistories")
49
59
 
60
+ challenge_id: int | None = Field(None, alias="ChallengeId", exclude=True, repr=False, description="Not used by API")
61
+ studio_id: int | None = Field(None, alias="StudioId", exclude=True, repr=False, description="Not used by API")
62
+ challenge_objective: str | None = Field(
63
+ None, alias="ChallengeObjective", exclude=True, repr=False, description="Always the string 'None'"
64
+ )
50
65
 
51
- class ChallengeTrackerDetail(OtfItemBase):
52
- challenge_category_id: int = Field(..., alias="ChallengeCategoryId")
53
- challenge_sub_category_id: int | None = Field(None, alias="ChallengeSubCategoryId")
54
- equipment_id: int = Field(..., alias="EquipmentId")
55
- equipment_name: str = Field(..., alias="EquipmentName")
56
- metric_entry: MetricEntry = Field(..., alias="MetricEntry")
57
- challenge_name: str = Field(..., alias="ChallengeName")
58
- logo_url: str = Field(..., alias="LogoUrl")
59
- best_record: float | str = Field(..., alias="BestRecord")
60
- last_record: float | str = Field(..., alias="LastRecord")
61
- previous_record: float | str = Field(..., alias="PreviousRecord")
62
- unit: str | None = Field(None, alias="Unit")
63
- goals: None = Field(..., alias="Goals")
64
- challenge_histories: list[ChallengeHistory] = Field(..., alias="ChallengeHistories")
65
66
 
67
+ class Goal(OtfItemBase):
68
+ goal: int | None = Field(None, alias="Goal")
69
+ goal_period: str | None = Field(None, alias="GoalPeriod")
70
+ overall_goal: int | None = Field(None, alias="OverallGoal")
71
+ overall_goal_period: str | None = Field(None, alias="OverallGoalPeriod")
72
+ min_overall: int | None = Field(None, alias="MinOverall")
73
+ min_overall_period: str | None = Field(None, alias="MinOverallPeriod")
66
74
 
67
- class ChallengeTrackerDetailList(OtfItemBase):
68
- details: list[ChallengeTrackerDetail]
75
+
76
+ class FitnessBenchmark(OtfItemBase):
77
+ challenge_category_id: ChallengeCategory | None = Field(None, alias="ChallengeCategoryId")
78
+ challenge_sub_category_id: int | None = Field(None, alias="ChallengeSubCategoryId")
79
+ equipment_id: EquipmentType = Field(None, alias="EquipmentId")
80
+ equipment_name: str | None = Field(None, alias="EquipmentName")
81
+ metric_entry: MetricEntry | None = Field(None, alias="MetricEntry")
82
+ challenge_name: str | None = Field(None, alias="ChallengeName")
83
+ best_record: float | str | None = Field(None, alias="BestRecord")
84
+ last_record: float | str | None = Field(None, alias="LastRecord")
85
+ previous_record: float | str | None = Field(None, alias="PreviousRecord")
86
+ unit: str | None = Field(None, alias="Unit")
87
+ goals: Goal | None = Field(None, alias="Goals")
88
+ challenge_histories: list[ChallengeHistory] = Field(default_factory=list, alias="ChallengeHistories")
otf_api/models/classes.py CHANGED
@@ -1,65 +1,65 @@
1
1
  from datetime import datetime
2
2
 
3
- from pydantic import Field
3
+ from pydantic import AliasPath, Field
4
4
 
5
5
  from otf_api.models.base import OtfItemBase
6
- from otf_api.models.enums import DoW
7
- from otf_api.models.mixins import OtfClassTimeMixin
6
+ from otf_api.models.enums import ClassType, DoW
7
+ from otf_api.models.studio_detail import StudioDetail
8
8
 
9
9
 
10
- class Address(OtfItemBase):
11
- line1: str
12
- city: str
13
- state: str
14
- country: str
15
- postal_code: str
10
+ class OtfClass(OtfItemBase):
11
+ class_uuid: str = Field(alias="ot_base_class_uuid", description="The OTF class UUID")
12
+ name: str | None = Field(None, description="The name of the class")
13
+ class_type: ClassType = Field(alias="type")
14
+ coach: str | None = Field(None, alias=AliasPath("coach", "first_name"))
15
+ ends_at: datetime = Field(
16
+ alias="ends_at_local",
17
+ description="The end time of the class. Reflects local time, but the object does not have a timezone.",
18
+ )
19
+ starts_at: datetime = Field(
20
+ alias="starts_at_local",
21
+ description="The start time of the class. Reflects local time, but the object does not have a timezone.",
22
+ )
23
+ studio: StudioDetail
16
24
 
25
+ # capacity/status fields
26
+ booking_capacity: int | None = None
27
+ full: bool | None = None
28
+ max_capacity: int | None = None
29
+ waitlist_available: bool | None = None
30
+ waitlist_size: int | None = None
31
+ is_booked: bool | None = Field(None, description="Custom helper field to determine if class is already booked")
32
+ is_cancelled: bool | None = Field(None, alias="canceled")
33
+ is_home_studio: bool | None = Field(None, description="Custom helper field to determine if at home studio")
17
34
 
18
- class Studio(OtfItemBase):
19
- id: str
20
- name: str
21
- mbo_studio_id: str
22
- time_zone: str
23
- currency_code: str
24
- address: Address
25
- phone_number: str
26
- latitude: float
27
- longitude: float
35
+ # unused fields
36
+ class_id: str | None = Field(None, alias="id", exclude=True, repr=False, description="Not used by API")
28
37
 
38
+ created_at: datetime | None = Field(None, exclude=True, repr=False)
39
+ ends_at_utc: datetime | None = Field(None, alias="ends_at", exclude=True, repr=False)
40
+ mbo_class_description_id: str | None = Field(None, exclude=True, repr=False, description="MindBody attr")
41
+ mbo_class_id: str | None = Field(None, exclude=True, repr=False, description="MindBody attr")
42
+ mbo_class_schedule_id: str | None = Field(None, exclude=True, repr=False, description="MindBody attr")
43
+ starts_at_utc: datetime | None = Field(None, alias="starts_at", exclude=True, repr=False)
44
+ updated_at: datetime | None = Field(None, exclude=True, repr=False)
29
45
 
30
- class Coach(OtfItemBase):
31
- mbo_staff_id: str
32
- first_name: str
33
- image_url: str | None = None
46
+ @property
47
+ def day_of_week(self) -> DoW:
48
+ dow = self.starts_at.strftime("%A")
49
+ return DoW(dow)
34
50
 
35
-
36
- class OtfClass(OtfItemBase, OtfClassTimeMixin):
37
- id: str
38
- ot_class_uuid: str = Field(
39
- alias="ot_base_class_uuid",
40
- description="The OTF class UUID, this is what shows in a booking response and how you can book a class.",
41
- )
42
- starts_at: datetime
43
- starts_at_local: datetime
44
- ends_at: datetime
45
- ends_at_local: datetime
46
- name: str
47
- type: str
48
- studio: Studio
49
- coach: Coach
50
- max_capacity: int
51
- booking_capacity: int
52
- waitlist_size: int
53
- full: bool
54
- waitlist_available: bool
55
- canceled: bool
56
- mbo_class_id: str
57
- mbo_class_schedule_id: str
58
- mbo_class_description_id: str
59
- created_at: datetime
60
- updated_at: datetime
61
- is_home_studio: bool | None = Field(None, description="Custom helper field to determine if at home studio")
62
- is_booked: bool | None = Field(None, description="Custom helper field to determine if class is already booked")
51
+ def __str__(self) -> str:
52
+ starts_at_str = self.starts_at.strftime("%a %b %d, %I:%M %p")
53
+ booked_str = ""
54
+ if self.is_booked:
55
+ booked_str = "Booked"
56
+ elif self.has_availability:
57
+ booked_str = "Available"
58
+ elif self.waitlist_available:
59
+ booked_str = "Waitlist Available"
60
+ else:
61
+ booked_str = "Full"
62
+ return f"Class: {starts_at_str} {self.name} - {self.coach} ({booked_str})"
63
63
 
64
64
  @property
65
65
  def has_availability(self) -> bool:
@@ -67,14 +67,5 @@ class OtfClass(OtfItemBase, OtfClassTimeMixin):
67
67
 
68
68
  @property
69
69
  def day_of_week_enum(self) -> DoW:
70
- dow = self.starts_at_local.strftime("%A")
71
- return DoW.get_case_insensitive(dow)
72
-
73
- @property
74
- def actual_class_uuid(self) -> str:
75
- """The UUID used to book the class"""
76
- return self.ot_class_uuid
77
-
78
-
79
- class OtfClassList(OtfItemBase):
80
- classes: list[OtfClass]
70
+ dow = self.starts_at.strftime("%A").upper()
71
+ return DoW(dow)
otf_api/models/enums.py CHANGED
@@ -1,7 +1,7 @@
1
- from enum import Enum
1
+ from enum import IntEnum, StrEnum
2
2
 
3
3
 
4
- class StudioStatus(str, Enum):
4
+ class StudioStatus(StrEnum):
5
5
  OTHER = "OTHER"
6
6
  ACTIVE = "Active"
7
7
  INACTIVE = "Inactive"
@@ -10,7 +10,7 @@ class StudioStatus(str, Enum):
10
10
  PERM_CLOSED = "Permanently Closed"
11
11
 
12
12
 
13
- class BookingStatus(str, Enum):
13
+ class BookingStatus(StrEnum):
14
14
  CheckedIn = "Checked In"
15
15
  CancelCheckinPending = "Cancel Checkin Pending"
16
16
  CancelCheckinRequested = "Cancel Checkin Requested"
@@ -23,39 +23,84 @@ class BookingStatus(str, Enum):
23
23
  CheckinCancelled = "Checkin Cancelled"
24
24
 
25
25
 
26
- class DoW(str, Enum):
27
- MONDAY = "monday"
28
- TUESDAY = "tuesday"
29
- WEDNESDAY = "wednesday"
30
- THURSDAY = "thursday"
31
- FRIDAY = "friday"
32
- SATURDAY = "saturday"
33
- SUNDAY = "sunday"
26
+ class DoW(StrEnum):
27
+ MONDAY = "Monday"
28
+ TUESDAY = "Tuesday"
29
+ WEDNESDAY = "Wednesday"
30
+ THURSDAY = "Thursday"
31
+ FRIDAY = "Friday"
32
+ SATURDAY = "Saturday"
33
+ SUNDAY = "Sunday"
34
34
 
35
- @classmethod
36
- def get_case_insensitive(cls, value: str) -> "DoW":
37
- lcase_to_actual = {item.value.lower(): item for item in cls}
38
- return lcase_to_actual[value.lower()]
35
+
36
+ class Orange60ClassType(StrEnum):
37
+ Enterprise60 = "Enterprise 60"
38
+ ExplicitOrange60 = "Explicit Orange 60"
39
+ OpenStudio60_3G = "Open Studio 60 3G"
40
+ Orange3G = "Orange 3G"
41
+ Orange3Group = "Orange 3 Group"
42
+ Orange60 = "Orange 60"
43
+ Orange60Min2G = "Orange 60 Min 2G"
44
+ Orange60Min2GMaskOptional = "Orange 60 Min 2G Mask Optional"
45
+ Orange60Min3G = "Orange 60 Min 3G"
46
+ Orange60Tornado = "Orange 60 - Tornado"
47
+ Tornado60Minute = "Tornado 60 Minute"
48
+
49
+
50
+ class Strength50ClassType(StrEnum):
51
+ Strength50Lower = "Strength 50 (Lower)"
52
+ Strength50Total = "Strength 50 (Total)"
53
+ Strength50Upper = "Strength 50 (Upper)"
54
+
55
+
56
+ class Tread50ClassType(StrEnum):
57
+ Tread50 = "Tread 50"
58
+
59
+
60
+ class OtherClassType(StrEnum):
61
+ InterpretingInbody = "Interpreting Inbody"
62
+ OpenStudio60 = "Open Studio 60"
63
+ Orangetheory101Workshop = "Orangetheory 101 Workshop"
64
+ OrangeTornado = "Orange Tornado"
65
+ OTFPopUp = "OTF Pop-Up"
66
+ PrivateClass = "Private Class"
67
+ RowingClinic = "Rowing Clinic"
68
+ Tornado = "Tornado"
69
+ VIPClass = "VIP Class"
70
+
71
+
72
+ class Orange90ClassType(StrEnum):
73
+ Orange90Min3G = "Orange 90 Min 3G"
74
+ Orange90Min2G = "Orange 90 Min 2G"
75
+ LifeIsWhyWeGive90 = "Life is Why We Give 90"
39
76
 
40
77
 
41
- class ClassType(str, Enum):
42
- ORANGE_60_MIN_2G = "Orange 60 Min 2G"
43
- TREAD_50 = "Tread 50"
44
- STRENGTH_50 = "Strength 50"
45
- ORANGE_3G = "Orange 3G"
46
- ORANGE_60_TORNADO = "Orange 60 - Tornado"
47
- ORANGE_TORNADO = "Orange Tornado"
48
- ORANGE_90_MIN_3G = "Orange 90 Min 3G"
49
- VIP_CLASS = "VIP Class"
50
- OTHER = "Other"
78
+ class ClassType(StrEnum):
79
+ ORANGE_60 = "ORANGE_60"
80
+ ORANGE_90 = "ORANGE_90"
81
+ OTHER = "OTHER"
82
+ STRENGTH_50 = "STRENGTH_50"
83
+ TREAD_50 = "TREAD_50"
51
84
 
52
85
  @classmethod
53
86
  def get_case_insensitive(cls, value: str) -> str:
87
+ value = (value or "").strip()
88
+ value = value.replace(" ", "_")
54
89
  lcase_to_actual = {item.value.lower(): item.value for item in cls}
55
90
  return lcase_to_actual[value.lower()]
56
91
 
92
+ @staticmethod
93
+ def get_standard_class_types() -> list["ClassType"]:
94
+ """Returns 2G/3G/Tornado - 60/90 minute classes"""
95
+ return [ClassType.ORANGE_60, ClassType.ORANGE_90]
96
+
97
+ @staticmethod
98
+ def get_tread_strength_class_types() -> list["ClassType"]:
99
+ """Returns Tread/Strength 50 minute classes"""
100
+ return [ClassType.TREAD_50, ClassType.STRENGTH_50]
101
+
57
102
 
58
- class StatsTime(str, Enum):
103
+ class StatsTime(StrEnum):
59
104
  LastYear = "lastYear"
60
105
  ThisYear = "thisYear"
61
106
  LastMonth = "lastMonth"
@@ -65,7 +110,7 @@ class StatsTime(str, Enum):
65
110
  AllTime = "allTime"
66
111
 
67
112
 
68
- class EquipmentType(int, Enum):
113
+ class EquipmentType(IntEnum):
69
114
  Treadmill = 2
70
115
  Strider = 3
71
116
  Rower = 4
@@ -74,14 +119,47 @@ class EquipmentType(int, Enum):
74
119
  PowerWalker = 7
75
120
 
76
121
 
77
- class ChallengeType(int, Enum):
122
+ class ChallengeCategory(IntEnum):
78
123
  Other = 0
79
124
  DriTri = 2
125
+ Infinity = 3
80
126
  MarathonMonth = 5
127
+ OrangeEverest = 9
128
+ CatchMeIfYouCan = 10
129
+ TwoHundredMeterRow = 15
130
+ FiveHundredMeterRow = 16
131
+ TwoThousandMeterRow = 17
132
+ TwelveMinuteTreadmill = 18
133
+ OneMileTreadmill = 19
134
+ TenMinuteRow = 20
81
135
  HellWeek = 52
136
+ Inferno = 55
82
137
  Mayhem = 58
138
+ BackAtIt = 60
139
+ FourteenMinuteRow = 61
83
140
  TwelveDaysOfFitness = 63
84
- Transformation = 64
141
+ TransformationChallenge = 64
85
142
  RemixInSix = 65
86
143
  Push = 66
87
- BackAtIt = 84
144
+ QuarterMileTreadmill = 69
145
+
146
+
147
+ class DriTriChallengeSubCategory(IntEnum):
148
+ FullRun = 1
149
+ SprintRun = 3
150
+ Relay = 4
151
+ StrengthRun = 1500
152
+
153
+
154
+ class MarathonMonthChallengeSubCategory(IntEnum):
155
+ Original = 1
156
+ Full = 14
157
+ Half = 15
158
+ Ultra = 16
159
+
160
+
161
+ # only Other, DriTri, and MarathonMonth have subcategories
162
+
163
+ # BackAtIt and Transformation are multi-week challenges
164
+
165
+ # RemixInSix, Mayhem, HellWeek, Push, and TwelveDaysOfFitness are multi-day challenges
@@ -1,31 +1,36 @@
1
- from pydantic import Field
1
+ from typing import Generic, TypeVar
2
+
3
+ from pydantic import BaseModel, Field
2
4
 
3
5
  from otf_api.models.base import OtfItemBase
6
+ from otf_api.models.enums import StatsTime
7
+
8
+ T = TypeVar("T", bound=BaseModel)
4
9
 
5
10
 
6
11
  class OutStudioMixin(OtfItemBase):
7
- walking_distance: float = Field(..., alias="walkingDistance")
8
- running_distance: float = Field(..., alias="runningDistance")
9
- cycling_distance: float = Field(..., alias="cyclingDistance")
12
+ walking_distance: float | None = Field(None, alias="walkingDistance")
13
+ running_distance: float | None = Field(None, alias="runningDistance")
14
+ cycling_distance: float | None = Field(None, alias="cyclingDistance")
10
15
 
11
16
 
12
17
  class InStudioMixin(OtfItemBase):
13
- treadmill_distance: float = Field(..., alias="treadmillDistance")
14
- treadmill_elevation_gained: float = Field(..., alias="treadmillElevationGained")
15
- rower_distance: float = Field(..., alias="rowerDistance")
16
- rower_watt: float = Field(..., alias="rowerWatt")
18
+ treadmill_distance: float | None = Field(None, alias="treadmillDistance")
19
+ treadmill_elevation_gained: float | None = Field(None, alias="treadmillElevationGained")
20
+ rower_distance: float | None = Field(None, alias="rowerDistance")
21
+ rower_watt: float | None = Field(None, alias="rowerWatt")
17
22
 
18
23
 
19
24
  class BaseStatsData(OtfItemBase):
20
- calories: float
21
- splat_point: float = Field(..., alias="splatPoint")
22
- total_black_zone: float = Field(..., alias="totalBlackZone")
23
- total_blue_zone: float = Field(..., alias="totalBlueZone")
24
- total_green_zone: float = Field(..., alias="totalGreenZone")
25
- total_orange_zone: float = Field(..., alias="totalOrangeZone")
26
- total_red_zone: float = Field(..., alias="totalRedZone")
27
- workout_duration: float = Field(..., alias="workoutDuration")
28
- step_count: float = Field(..., alias="stepCount")
25
+ calories: float | None = None
26
+ splat_point: float | None = Field(None, alias="splatPoint")
27
+ total_black_zone: float | None = Field(None, alias="totalBlackZone")
28
+ total_blue_zone: float | None = Field(None, alias="totalBlueZone")
29
+ total_green_zone: float | None = Field(None, alias="totalGreenZone")
30
+ total_orange_zone: float | None = Field(None, alias="totalOrangeZone")
31
+ total_red_zone: float | None = Field(None, alias="totalRedZone")
32
+ workout_duration: float | None = Field(None, alias="workoutDuration")
33
+ step_count: float | None = Field(None, alias="stepCount")
29
34
 
30
35
 
31
36
  class InStudioStatsData(InStudioMixin, BaseStatsData):
@@ -40,34 +45,43 @@ class AllStatsData(OutStudioMixin, InStudioMixin, BaseStatsData):
40
45
  pass
41
46
 
42
47
 
43
- class OutStudioTimeStats(OtfItemBase):
44
- last_year: OutStudioStatsData = Field(..., alias="lastYear")
45
- this_year: OutStudioStatsData = Field(..., alias="thisYear")
46
- last_month: OutStudioStatsData = Field(..., alias="lastMonth")
47
- this_month: OutStudioStatsData = Field(..., alias="thisMonth")
48
- last_week: OutStudioStatsData = Field(..., alias="lastWeek")
49
- this_week: OutStudioStatsData = Field(..., alias="thisWeek")
50
- all_time: OutStudioStatsData = Field(..., alias="allTime")
51
-
52
-
53
- class InStudioTimeStats(OtfItemBase):
54
- last_year: InStudioStatsData = Field(..., alias="lastYear")
55
- this_year: InStudioStatsData = Field(..., alias="thisYear")
56
- last_month: InStudioStatsData = Field(..., alias="lastMonth")
57
- this_month: InStudioStatsData = Field(..., alias="thisMonth")
58
- last_week: InStudioStatsData = Field(..., alias="lastWeek")
59
- this_week: InStudioStatsData = Field(..., alias="thisWeek")
60
- all_time: InStudioStatsData = Field(..., alias="allTime")
61
-
62
-
63
- class AllStatsTimeStats(OtfItemBase):
64
- last_year: AllStatsData = Field(..., alias="lastYear")
65
- this_year: AllStatsData = Field(..., alias="thisYear")
66
- last_month: AllStatsData = Field(..., alias="lastMonth")
67
- this_month: AllStatsData = Field(..., alias="thisMonth")
68
- last_week: AllStatsData = Field(..., alias="lastWeek")
69
- this_week: AllStatsData = Field(..., alias="thisWeek")
70
- all_time: AllStatsData = Field(..., alias="allTime")
48
+ class TimeStats(OtfItemBase, Generic[T]):
49
+ last_year: T = Field(..., alias="lastYear")
50
+ this_year: T = Field(..., alias="thisYear")
51
+ last_month: T = Field(..., alias="lastMonth")
52
+ this_month: T = Field(..., alias="thisMonth")
53
+ last_week: T = Field(..., alias="lastWeek")
54
+ this_week: T = Field(..., alias="thisWeek")
55
+ all_time: T = Field(..., alias="allTime")
56
+
57
+ def get_by_time(self, stats_time: StatsTime) -> T:
58
+ match stats_time:
59
+ case StatsTime.LastYear:
60
+ return self.last_year
61
+ case StatsTime.ThisYear:
62
+ return self.this_year
63
+ case StatsTime.LastMonth:
64
+ return self.last_month
65
+ case StatsTime.ThisMonth:
66
+ return self.this_month
67
+ case StatsTime.LastWeek:
68
+ return self.last_week
69
+ case StatsTime.ThisWeek:
70
+ return self.this_week
71
+ case StatsTime.AllTime:
72
+ return self.all_time
73
+
74
+
75
+ class OutStudioTimeStats(TimeStats[OutStudioStatsData]):
76
+ pass
77
+
78
+
79
+ class InStudioTimeStats(TimeStats[InStudioStatsData]):
80
+ pass
81
+
82
+
83
+ class AllStatsTimeStats(TimeStats[AllStatsData]):
84
+ pass
71
85
 
72
86
 
73
87
  class StatsResponse(OtfItemBase):