otf-api 0.2.2__py3-none-any.whl → 0.4.0__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 (50) hide show
  1. otf_api/__init__.py +14 -69
  2. otf_api/api.py +873 -66
  3. otf_api/auth.py +314 -0
  4. otf_api/cli/__init__.py +4 -0
  5. otf_api/cli/_utilities.py +60 -0
  6. otf_api/cli/app.py +172 -0
  7. otf_api/cli/bookings.py +231 -0
  8. otf_api/cli/prompts.py +162 -0
  9. otf_api/models/__init__.py +30 -23
  10. otf_api/models/base.py +205 -2
  11. otf_api/models/responses/__init__.py +29 -29
  12. otf_api/models/responses/body_composition_list.py +304 -0
  13. otf_api/models/responses/book_class.py +405 -0
  14. otf_api/models/responses/bookings.py +211 -37
  15. otf_api/models/responses/cancel_booking.py +93 -0
  16. otf_api/models/responses/challenge_tracker_content.py +6 -6
  17. otf_api/models/responses/challenge_tracker_detail.py +6 -6
  18. otf_api/models/responses/classes.py +205 -7
  19. otf_api/models/responses/enums.py +0 -35
  20. otf_api/models/responses/favorite_studios.py +5 -5
  21. otf_api/models/responses/latest_agreement.py +2 -2
  22. otf_api/models/responses/lifetime_stats.py +92 -0
  23. otf_api/models/responses/member_detail.py +17 -12
  24. otf_api/models/responses/member_membership.py +2 -2
  25. otf_api/models/responses/member_purchases.py +9 -9
  26. otf_api/models/responses/out_of_studio_workout_history.py +4 -4
  27. otf_api/models/responses/performance_summary_detail.py +1 -1
  28. otf_api/models/responses/performance_summary_list.py +13 -13
  29. otf_api/models/responses/studio_detail.py +10 -10
  30. otf_api/models/responses/studio_services.py +8 -8
  31. otf_api/models/responses/telemetry.py +6 -6
  32. otf_api/models/responses/telemetry_hr_history.py +6 -6
  33. otf_api/models/responses/telemetry_max_hr.py +3 -3
  34. otf_api/models/responses/total_classes.py +2 -2
  35. otf_api/models/responses/workouts.py +4 -4
  36. otf_api-0.4.0.dist-info/METADATA +54 -0
  37. otf_api-0.4.0.dist-info/RECORD +42 -0
  38. otf_api-0.4.0.dist-info/entry_points.txt +3 -0
  39. otf_api/__version__.py +0 -1
  40. otf_api/classes_api.py +0 -44
  41. otf_api/member_api.py +0 -380
  42. otf_api/models/auth.py +0 -141
  43. otf_api/performance_api.py +0 -54
  44. otf_api/studios_api.py +0 -96
  45. otf_api/telemetry_api.py +0 -95
  46. otf_api-0.2.2.dist-info/METADATA +0 -284
  47. otf_api-0.2.2.dist-info/RECORD +0 -38
  48. {otf_api-0.2.2.dist-info → otf_api-0.4.0.dist-info}/AUTHORS.md +0 -0
  49. {otf_api-0.2.2.dist-info → otf_api-0.4.0.dist-info}/LICENSE +0 -0
  50. {otf_api-0.2.2.dist-info → otf_api-0.4.0.dist-info}/WHEEL +0 -0
@@ -1,26 +1,21 @@
1
- from .bookings import BookingList
1
+ from .body_composition_list import BodyCompositionList
2
+ from .book_class import BookClass
3
+ from .bookings import BookingList, BookingStatus
4
+ from .cancel_booking import CancelBooking
2
5
  from .challenge_tracker_content import ChallengeTrackerContent
3
6
  from .challenge_tracker_detail import ChallengeTrackerDetailList
4
- from .classes import OtfClassList
5
- from .enums import (
6
- ALL_CLASS_STATUS,
7
- ALL_HISTORY_CLASS_STATUS,
8
- ALL_STUDIO_STATUS,
9
- BookingStatus,
10
- ChallengeType,
11
- EquipmentType,
12
- HistoryClassStatus,
13
- StudioStatus,
14
- )
7
+ from .classes import ClassType, DoW, OtfClassList
8
+ from .enums import ChallengeType, EquipmentType, HistoryClassStatus
15
9
  from .favorite_studios import FavoriteStudioList
16
10
  from .latest_agreement import LatestAgreement
11
+ from .lifetime_stats import StatsResponse, StatsTime
17
12
  from .member_detail import MemberDetail
18
13
  from .member_membership import MemberMembership
19
14
  from .member_purchases import MemberPurchaseList
20
15
  from .out_of_studio_workout_history import OutOfStudioWorkoutHistoryList
21
16
  from .performance_summary_detail import PerformanceSummaryDetail
22
17
  from .performance_summary_list import PerformanceSummaryList
23
- from .studio_detail import StudioDetail, StudioDetailList
18
+ from .studio_detail import Pagination, StudioDetail, StudioDetailList
24
19
  from .studio_services import StudioServiceList
25
20
  from .telemetry import Telemetry
26
21
  from .telemetry_hr_history import TelemetryHrHistory
@@ -29,32 +24,37 @@ from .total_classes import TotalClasses
29
24
  from .workouts import WorkoutList
30
25
 
31
26
  __all__ = [
27
+ "BodyCompositionList",
28
+ "BookClass",
32
29
  "BookingList",
30
+ "BookingStatus",
31
+ "CancelBooking",
33
32
  "ChallengeTrackerContent",
34
33
  "ChallengeTrackerDetailList",
34
+ "ChallengeType",
35
+ "ClassType",
36
+ "DoW",
37
+ "EquipmentType",
38
+ "FavoriteStudioList",
39
+ "HistoryClassStatus",
35
40
  "LatestAgreement",
36
41
  "MemberDetail",
37
42
  "MemberMembership",
38
43
  "MemberPurchaseList",
44
+ "OtfClassList",
39
45
  "OutOfStudioWorkoutHistoryList",
46
+ "Pagination",
47
+ "PerformanceSummaryDetail",
48
+ "PerformanceSummaryList",
49
+ "StatsResponse",
50
+ "StatsTime",
51
+ "StudioDetail",
52
+ "StudioDetailList",
40
53
  "StudioServiceList",
41
- "TotalClasses",
42
- "WorkoutList",
43
- "ChallengeType",
44
- "BookingStatus",
45
- "EquipmentType",
46
- "HistoryClassStatus",
47
54
  "StudioStatus",
48
- "FavoriteStudioList",
49
- "OtfClassList",
50
- "TelemetryHrHistory",
51
55
  "Telemetry",
56
+ "TelemetryHrHistory",
52
57
  "TelemetryMaxHr",
53
- "StudioDetail",
54
- "StudioDetailList",
55
- "ALL_CLASS_STATUS",
56
- "ALL_HISTORY_CLASS_STATUS",
57
- "ALL_STUDIO_STATUS",
58
- "PerformanceSummaryDetail",
59
- "PerformanceSummaryList",
58
+ "TotalClasses",
59
+ "WorkoutList",
60
60
  ]
@@ -0,0 +1,304 @@
1
+ import inspect
2
+ from datetime import datetime
3
+ from enum import Enum
4
+
5
+ import pint
6
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
7
+
8
+ ureg = pint.UnitRegistry()
9
+
10
+ DEFAULT_WEIGHT_DIVIDERS = [55.0, 70.0, 85.0, 100.0, 115.0, 130.0, 145.0, 160.0, 175.0, 190.0, 205.0]
11
+ DEFAULT_SKELETAL_MUSCLE_MASS_DIVIDERS = [70.0, 80.0, 90.0, 100.0, 110.0, 120.0, 130.0, 140.0, 150.0, 160.0, 170.0]
12
+ DEFAULT_BODY_FAT_MASS_DIVIDERS = [40.0, 60.0, 80.0, 100.0, 160.0, 220.0, 280.0, 340.0, 400.0, 460.0, 520.0]
13
+
14
+
15
+ class AverageType(str, Enum):
16
+ BELOW_AVERAGE = "BELOW_AVERAGE"
17
+ AVERAGE = "AVERAGE"
18
+ ABOVE_AVERAGE = "ABOVE_AVERAGE"
19
+ MINIMUM = "MINIMUM" # unused
20
+
21
+
22
+ class BodyFatPercentIndicator(str, Enum):
23
+ NO_INDICATOR = "NO_INDICATOR"
24
+ MINIMUM_BODY_FAT = "MINIMUM_BODY_FAT" # unused
25
+ LOW_BODY_FAT = "LOW_BODY_FAT" # unused
26
+ HEALTHY_BODY_FAT = "HEALTHY_BODY_FAT"
27
+ GOAL_SETTING_FAT = "GOAL_SETTING_FAT"
28
+ HIGH_BODY_FAT = "HIGH_BODY_FAT"
29
+ OBESE_BODY_FAT = "OBESE_BODY_FAT" # unused
30
+
31
+
32
+ class Gender(str, Enum):
33
+ MALE = "M"
34
+ FEMALE = "F"
35
+
36
+
37
+ def get_percent_body_fat_descriptor(
38
+ percent_body_fat: float, body_fat_percent_dividers: list[float]
39
+ ) -> BodyFatPercentIndicator:
40
+ if not percent_body_fat or not body_fat_percent_dividers[3]:
41
+ return BodyFatPercentIndicator.NO_INDICATOR
42
+
43
+ if percent_body_fat < body_fat_percent_dividers[1]:
44
+ return BodyFatPercentIndicator.HEALTHY_BODY_FAT
45
+
46
+ if percent_body_fat < body_fat_percent_dividers[2]:
47
+ return BodyFatPercentIndicator.GOAL_SETTING_FAT
48
+
49
+ return BodyFatPercentIndicator.HIGH_BODY_FAT
50
+
51
+
52
+ def get_relative_descriptor(in_body_value: float, in_body_dividers: list[float]) -> AverageType:
53
+ if in_body_value <= in_body_dividers[2]:
54
+ return AverageType.BELOW_AVERAGE
55
+
56
+ if in_body_value <= in_body_dividers[4]:
57
+ return AverageType.AVERAGE
58
+
59
+ return AverageType.ABOVE_AVERAGE
60
+
61
+
62
+ def get_body_fat_percent_dividers(age: int, gender: Gender) -> list[float]:
63
+ if gender == Gender.MALE:
64
+ return get_body_fat_percent_dividers_male(age)
65
+
66
+ return get_body_fat_percent_dividers_female(age)
67
+
68
+
69
+ def get_body_fat_percent_dividers_male(age: int) -> list[float]:
70
+ match age:
71
+ case age if 0 <= age < 30:
72
+ return [0.0, 13.1, 21.1, 100.0]
73
+ case age if 30 <= age < 40:
74
+ return [0.0, 17.1, 23.1, 100.0]
75
+ case age if 40 <= age < 50:
76
+ return [0.0, 20.1, 25.1, 100.0]
77
+ case age if 50 <= age < 60:
78
+ return [0.0, 21.1, 26.1, 100.0]
79
+ case age if 60 <= age < 70:
80
+ return [0.0, 22.1, 27.1, 100.0]
81
+ case _:
82
+ return [0.0, 0.0, 0.0, 0.0]
83
+
84
+
85
+ def get_body_fat_percent_dividers_female(age: int) -> list[float]:
86
+ match age:
87
+ case age if 0 <= age < 30:
88
+ return [0.0, 19.1, 26.1, 100.0]
89
+ case age if 30 <= age < 40:
90
+ return [0.0, 20.1, 27.1, 100.0]
91
+ case age if 40 <= age < 50:
92
+ return [0.0, 22.1, 30.1, 100.0]
93
+ case age if 50 <= age < 60:
94
+ return [0.0, 25.1, 32.1, 100.0]
95
+ case age if 60 <= age < 70:
96
+ return [0.0, 26.1, 33.1, 100.0]
97
+ case _:
98
+ return [0.0, 0.0, 0.0, 0.0]
99
+
100
+
101
+ class LeanBodyMass(BaseModel):
102
+ model_config: ConfigDict = ConfigDict(extra="ignore")
103
+ left_arm: float = Field(..., alias="lbmOfLeftArm")
104
+ left_leg: float = Field(..., alias="lbmOfLeftLeg")
105
+ right_arm: float = Field(..., alias="lbmOfRightArm")
106
+ right_leg: float = Field(..., alias="lbmOfRightLeg")
107
+ trunk: float = Field(..., alias="lbmOfTrunk")
108
+
109
+
110
+ class LeanBodyMassPercent(BaseModel):
111
+ model_config: ConfigDict = ConfigDict(extra="ignore")
112
+ left_arm: float = Field(..., alias="lbmPercentOfLeftArm")
113
+ left_leg: float = Field(..., alias="lbmPercentOfLeftLeg")
114
+ right_arm: float = Field(..., alias="lbmPercentOfRightArm")
115
+ right_leg: float = Field(..., alias="lbmPercentOfRightLeg")
116
+ trunk: float = Field(..., alias="lbmPercentOfTrunk")
117
+
118
+
119
+ class BodyFatMass(BaseModel):
120
+ model_config: ConfigDict = ConfigDict(extra="ignore")
121
+ control: float = Field(..., alias="bfmControl")
122
+ left_arm: float = Field(..., alias="bfmOfLeftArm")
123
+ left_leg: float = Field(..., alias="bfmOfLeftLeg")
124
+ right_arm: float = Field(..., alias="bfmOfRightArm")
125
+ right_leg: float = Field(..., alias="bfmOfRightLeg")
126
+ trunk: float = Field(..., alias="bfmOfTrunk")
127
+
128
+
129
+ class BodyFatMassPercent(BaseModel):
130
+ model_config: ConfigDict = ConfigDict(extra="ignore")
131
+ left_arm: float = Field(..., alias="bfmPercentOfLeftArm")
132
+ left_leg: float = Field(..., alias="bfmPercentOfLeftLeg")
133
+ right_arm: float = Field(..., alias="bfmPercentOfRightArm")
134
+ right_leg: float = Field(..., alias="bfmPercentOfRightLeg")
135
+ trunk: float = Field(..., alias="bfmPercentOfTrunk")
136
+
137
+
138
+ class TotalBodyWeight(BaseModel):
139
+ model_config: ConfigDict = ConfigDict(extra="ignore")
140
+ right_arm: float = Field(..., alias="tbwOfRightArm")
141
+ left_arm: float = Field(..., alias="tbwOfLeftArm")
142
+ trunk: float = Field(..., alias="tbwOfTrunk")
143
+ right_leg: float = Field(..., alias="tbwOfRightLeg")
144
+ left_leg: float = Field(..., alias="tbwOfLeftLeg")
145
+
146
+
147
+ class IntraCellularWater(BaseModel):
148
+ model_config: ConfigDict = ConfigDict(extra="ignore")
149
+ right_arm: float = Field(..., alias="icwOfRightArm")
150
+ left_arm: float = Field(..., alias="icwOfLeftArm")
151
+ trunk: float = Field(..., alias="icwOfTrunk")
152
+ right_leg: float = Field(..., alias="icwOfRightLeg")
153
+ left_leg: float = Field(..., alias="icwOfLeftLeg")
154
+
155
+
156
+ class ExtraCellularWater(BaseModel):
157
+ model_config: ConfigDict = ConfigDict(extra="ignore")
158
+ right_arm: float = Field(..., alias="ecwOfRightArm")
159
+ left_arm: float = Field(..., alias="ecwOfLeftArm")
160
+ trunk: float = Field(..., alias="ecwOfTrunk")
161
+ right_leg: float = Field(..., alias="ecwOfRightLeg")
162
+ left_leg: float = Field(..., alias="ecwOfLeftLeg")
163
+
164
+
165
+ class ExtraCellularWaterOverTotalBodyWater(BaseModel):
166
+ model_config: ConfigDict = ConfigDict(extra="ignore")
167
+ right_arm: float = Field(..., alias="ecwOverTBWOfRightArm")
168
+ left_arm: float = Field(..., alias="ecwOverTBWOfLeftArm")
169
+ trunk: float = Field(..., alias="ecwOverTBWOfTrunk")
170
+ right_leg: float = Field(..., alias="ecwOverTBWOfRightLeg")
171
+ left_leg: float = Field(..., alias="ecwOverTBWOfLeftLeg")
172
+
173
+
174
+ class BodyCompositionData(BaseModel):
175
+ member_uuid: str = Field(..., alias="memberUUId")
176
+ member_id: str = Field(..., alias="memberId")
177
+ scan_result_uuid: str = Field(..., alias="scanResultUUId")
178
+ inbody_id: str = Field(..., alias="id", exclude=True, description="InBody ID, same as email address")
179
+ email: str
180
+ height: str = Field(..., description="Height in cm")
181
+ gender: Gender
182
+ age: int
183
+ scan_datetime: datetime = Field(..., alias="testDatetime")
184
+ provided_weight: float = Field(
185
+ ..., alias="weight", description="Weight in pounds, provided by member at time of scan"
186
+ )
187
+
188
+ lean_body_mass_details: LeanBodyMass
189
+ lean_body_mass_percent_details: LeanBodyMassPercent
190
+
191
+ total_body_weight: float = Field(..., alias="tbw", description="Total body weight in pounds, based on scan results")
192
+ dry_lean_mass: float = Field(..., alias="dlm")
193
+ body_fat_mass: float = Field(..., alias="bfm")
194
+ lean_body_mass: float = Field(..., alias="lbm")
195
+ skeletal_muscle_mass: float = Field(..., alias="smm")
196
+ body_mass_index: float = Field(..., alias="bmi")
197
+ percent_body_fat: float = Field(..., alias="pbf")
198
+ basal_metabolic_rate: float = Field(..., alias="bmr")
199
+ in_body_type: str = Field(..., alias="inBodyType")
200
+
201
+ body_fat_mass: float = Field(..., alias="bfm")
202
+ skeletal_muscle_mass: float = Field(..., alias="smm")
203
+
204
+ # excluded because they are only useful for end result of calculations
205
+ body_fat_mass_dividers: list[float] = Field(..., alias="bfmGraphScale", exclude=True)
206
+ body_fat_mass_plot_point: float = Field(..., alias="pfatnew", exclude=True)
207
+ skeletal_muscle_mass_dividers: list[float] = Field(..., alias="smmGraphScale", exclude=True)
208
+ skeletal_muscle_mass_plot_point: float = Field(..., alias="psmm", exclude=True)
209
+ weight_dividers: list[float] = Field(..., alias="wtGraphScale", exclude=True)
210
+ weight_plot_point: float = Field(..., alias="pwt", exclude=True)
211
+
212
+ # excluded due to 0 values
213
+ body_fat_mass_details: BodyFatMass = Field(..., exclude=True)
214
+ body_fat_mass_percent_details: BodyFatMassPercent = Field(..., exclude=True)
215
+ total_body_weight_details: TotalBodyWeight = Field(..., exclude=True)
216
+ intra_cellular_water_details: IntraCellularWater = Field(..., exclude=True)
217
+ extra_cellular_water_details: ExtraCellularWater = Field(..., exclude=True)
218
+ extra_cellular_water_over_total_body_water_details: ExtraCellularWaterOverTotalBodyWater = Field(..., exclude=True)
219
+ visceral_fat_level: float = Field(..., alias="vfl", exclude=True)
220
+ visceral_fat_area: float = Field(..., alias="vfa", exclude=True)
221
+ body_comp_measurement: float = Field(..., alias="bcm", exclude=True)
222
+ total_body_weight_over_lean_body_mass: float = Field(..., alias="tbwOverLBM", exclude=True)
223
+ intracellular_water: float = Field(..., alias="icw", exclude=True)
224
+ extracellular_water: float = Field(..., alias="ecw", exclude=True)
225
+ lean_body_mass_control: float = Field(..., alias="lbmControl", exclude=True)
226
+
227
+ def __init__(self, **data):
228
+ # populate child models
229
+ child_model_dict = {
230
+ k: v.annotation
231
+ for k, v in self.model_fields.items()
232
+ if inspect.isclass(v.annotation) and issubclass(v.annotation, BaseModel)
233
+ }
234
+ for k, v in child_model_dict.items():
235
+ data[k] = v(**data)
236
+
237
+ super().__init__(**data)
238
+
239
+ @field_validator("member_id", mode="before")
240
+ @classmethod
241
+ def int_to_str(cls, v: int):
242
+ return str(v)
243
+
244
+ @field_validator("skeletal_muscle_mass_dividers", "weight_dividers", "body_fat_mass_dividers", mode="before")
245
+ @classmethod
246
+ def convert_dividers_to_float_list(cls, v: str):
247
+ return [float(i) for i in v.split(";")]
248
+
249
+ @field_validator("total_body_weight", mode="before")
250
+ @classmethod
251
+ def convert_body_weight_from_kg_to_pounds(cls, v: float):
252
+ return ureg.Quantity(v, ureg.kilogram).to(ureg.pound).magnitude
253
+
254
+ @property
255
+ def body_fat_mass_relative_descriptor(self) -> AverageType:
256
+ """Get the relative descriptor for the body fat mass plot point.
257
+
258
+ For this item, a lower value is better.
259
+
260
+ Returns:
261
+ AverageType: The relative descriptor for the body fat mass plot point
262
+ """
263
+ dividers = self.body_fat_mass_dividers or DEFAULT_BODY_FAT_MASS_DIVIDERS
264
+ return get_relative_descriptor(self.body_fat_mass_plot_point, dividers)
265
+
266
+ @property
267
+ def skeletal_muscle_mass_relative_descriptor(self) -> AverageType:
268
+ """Get the relative descriptor for the skeletal muscle mass plot point.
269
+
270
+ For this item, a higher value is better.
271
+
272
+ Returns:
273
+ AverageType: The relative descriptor for the skeletal muscle mass plot point
274
+
275
+ """
276
+ dividers = self.skeletal_muscle_mass_dividers or DEFAULT_SKELETAL_MUSCLE_MASS_DIVIDERS
277
+ return get_relative_descriptor(self.skeletal_muscle_mass_plot_point, dividers)
278
+
279
+ @property
280
+ def weight_relative_descriptor(self) -> AverageType:
281
+ """Get the relative descriptor for the weight plot point.
282
+
283
+ For this item, a lower value is better.
284
+
285
+ Returns:
286
+ AverageType: The relative descriptor for the weight
287
+ """
288
+ dividers = self.weight_dividers or DEFAULT_WEIGHT_DIVIDERS
289
+ return get_relative_descriptor(self.weight_plot_point, dividers)
290
+
291
+ @property
292
+ def body_fat_percent_relative_descriptor(self) -> BodyFatPercentIndicator:
293
+ """Get the relative descriptor for the percent body fat.
294
+
295
+ Returns:
296
+ BodyFatPercentIndicator: The relative descriptor for the percent body fat
297
+ """
298
+ return get_percent_body_fat_descriptor(
299
+ self.percent_body_fat, get_body_fat_percent_dividers(self.age, self.gender)
300
+ )
301
+
302
+
303
+ class BodyCompositionList(BaseModel):
304
+ data: list[BodyCompositionData]