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.
- otf_api/__init__.py +7 -4
- otf_api/api.py +699 -480
- otf_api/auth/__init__.py +4 -0
- otf_api/auth/auth.py +234 -0
- otf_api/auth/user.py +66 -0
- otf_api/auth/utils.py +129 -0
- otf_api/exceptions.py +38 -5
- otf_api/filters.py +97 -0
- otf_api/logging.py +19 -0
- otf_api/models/__init__.py +27 -38
- otf_api/models/body_composition_list.py +47 -50
- otf_api/models/bookings.py +63 -87
- otf_api/models/challenge_tracker_content.py +42 -21
- otf_api/models/challenge_tracker_detail.py +68 -48
- otf_api/models/classes.py +53 -62
- otf_api/models/enums.py +108 -30
- otf_api/models/lifetime_stats.py +59 -45
- otf_api/models/member_detail.py +95 -115
- otf_api/models/member_membership.py +18 -17
- otf_api/models/member_purchases.py +21 -127
- otf_api/models/mixins.py +37 -33
- otf_api/models/notifications.py +17 -0
- otf_api/models/out_of_studio_workout_history.py +22 -31
- otf_api/models/performance_summary_detail.py +47 -42
- otf_api/models/performance_summary_list.py +19 -37
- otf_api/models/studio_detail.py +51 -98
- otf_api/models/studio_services.py +27 -48
- otf_api/models/telemetry.py +14 -5
- otf_api/utils.py +134 -0
- {otf_api-0.8.2.dist-info → otf_api-0.9.1.dist-info}/METADATA +21 -10
- otf_api-0.9.1.dist-info/RECORD +35 -0
- {otf_api-0.8.2.dist-info → otf_api-0.9.1.dist-info}/WHEEL +1 -1
- otf_api/auth.py +0 -316
- otf_api/models/book_class.py +0 -89
- otf_api/models/cancel_booking.py +0 -49
- otf_api/models/favorite_studios.py +0 -106
- otf_api/models/latest_agreement.py +0 -21
- otf_api/models/telemetry_hr_history.py +0 -34
- otf_api/models/telemetry_max_hr.py +0 -13
- otf_api/models/total_classes.py +0 -8
- otf_api-0.8.2.dist-info/AUTHORS.md +0 -9
- otf_api-0.8.2.dist-info/RECORD +0 -36
- {otf_api-0.8.2.dist-info → otf_api-0.9.1.dist-info}/LICENSE +0 -0
otf_api/models/__init__.py
CHANGED
@@ -1,59 +1,48 @@
|
|
1
|
-
from .body_composition_list import
|
2
|
-
from .
|
3
|
-
from .
|
4
|
-
from .
|
5
|
-
from .
|
6
|
-
from .
|
7
|
-
from .
|
8
|
-
from .enums import BookingStatus, ChallengeType, ClassType, DoW, EquipmentType, StatsTime, StudioStatus
|
9
|
-
from .favorite_studios import FavoriteStudioList
|
10
|
-
from .latest_agreement import LatestAgreement
|
11
|
-
from .lifetime_stats import StatsResponse
|
1
|
+
from .body_composition_list import BodyCompositionData
|
2
|
+
from .bookings import Booking
|
3
|
+
from .challenge_tracker_content import ChallengeTracker
|
4
|
+
from .challenge_tracker_detail import FitnessBenchmark
|
5
|
+
from .classes import OtfClass
|
6
|
+
from .enums import BookingStatus, ChallengeCategory, ClassType, DoW, EquipmentType, StatsTime, StudioStatus
|
7
|
+
from .lifetime_stats import StatsResponse, TimeStats
|
12
8
|
from .member_detail import MemberDetail
|
13
9
|
from .member_membership import MemberMembership
|
14
|
-
from .member_purchases import
|
15
|
-
from .
|
10
|
+
from .member_purchases import MemberPurchase
|
11
|
+
from .notifications import EmailNotificationSettings, SmsNotificationSettings
|
12
|
+
from .out_of_studio_workout_history import OutOfStudioWorkoutHistory
|
16
13
|
from .performance_summary_detail import PerformanceSummaryDetail
|
17
|
-
from .performance_summary_list import
|
18
|
-
from .studio_detail import
|
19
|
-
from .studio_services import
|
20
|
-
from .telemetry import Telemetry
|
21
|
-
from .telemetry_hr_history import TelemetryHrHistory
|
22
|
-
from .telemetry_max_hr import TelemetryMaxHr
|
23
|
-
from .total_classes import TotalClasses
|
14
|
+
from .performance_summary_list import PerformanceSummaryEntry
|
15
|
+
from .studio_detail import StudioDetail
|
16
|
+
from .studio_services import StudioService
|
17
|
+
from .telemetry import Telemetry, TelemetryHistoryItem
|
24
18
|
|
25
19
|
__all__ = [
|
26
|
-
"
|
27
|
-
"BookClass",
|
20
|
+
"BodyCompositionData",
|
28
21
|
"Booking",
|
29
|
-
"BookingList",
|
30
22
|
"BookingStatus",
|
31
|
-
"
|
32
|
-
"
|
33
|
-
"
|
34
|
-
"ChallengeType",
|
23
|
+
"ChallengeCategory",
|
24
|
+
"ChallengeParticipation",
|
25
|
+
"ChallengeTracker",
|
35
26
|
"ClassType",
|
36
27
|
"DoW",
|
28
|
+
"EmailNotificationSettings",
|
37
29
|
"EquipmentType",
|
38
|
-
"
|
30
|
+
"FitnessBenchmark",
|
39
31
|
"LatestAgreement",
|
40
32
|
"MemberDetail",
|
41
33
|
"MemberMembership",
|
42
|
-
"
|
43
|
-
"OtfClassList",
|
34
|
+
"MemberPurchase",
|
44
35
|
"OtfClass",
|
45
|
-
"
|
46
|
-
"Pagination",
|
36
|
+
"OutOfStudioWorkoutHistory",
|
47
37
|
"PerformanceSummaryDetail",
|
48
|
-
"
|
38
|
+
"PerformanceSummaryEntry",
|
39
|
+
"SmsNotificationSettings",
|
49
40
|
"StatsResponse",
|
50
41
|
"StatsTime",
|
51
42
|
"StudioDetail",
|
52
|
-
"
|
53
|
-
"StudioServiceList",
|
43
|
+
"StudioService",
|
54
44
|
"StudioStatus",
|
55
45
|
"Telemetry",
|
56
|
-
"
|
57
|
-
"
|
58
|
-
"TotalClasses",
|
46
|
+
"TelemetryHistoryItem",
|
47
|
+
"TimeStats",
|
59
48
|
]
|
@@ -1,27 +1,28 @@
|
|
1
|
-
import inspect
|
2
1
|
from datetime import datetime
|
3
|
-
from enum import
|
2
|
+
from enum import StrEnum
|
3
|
+
from typing import Literal
|
4
4
|
|
5
5
|
import pint
|
6
|
-
from pydantic import
|
6
|
+
from pydantic import Field, field_validator
|
7
7
|
|
8
8
|
from otf_api.models.base import OtfItemBase
|
9
9
|
|
10
10
|
ureg = pint.UnitRegistry()
|
11
11
|
|
12
|
+
|
12
13
|
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]
|
13
14
|
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]
|
14
15
|
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]
|
15
16
|
|
16
17
|
|
17
|
-
class AverageType(
|
18
|
+
class AverageType(StrEnum):
|
18
19
|
BELOW_AVERAGE = "BELOW_AVERAGE"
|
19
20
|
AVERAGE = "AVERAGE"
|
20
21
|
ABOVE_AVERAGE = "ABOVE_AVERAGE"
|
21
22
|
MINIMUM = "MINIMUM" # unused
|
22
23
|
|
23
24
|
|
24
|
-
class BodyFatPercentIndicator(
|
25
|
+
class BodyFatPercentIndicator(StrEnum):
|
25
26
|
NO_INDICATOR = "NO_INDICATOR"
|
26
27
|
MINIMUM_BODY_FAT = "MINIMUM_BODY_FAT" # unused
|
27
28
|
LOW_BODY_FAT = "LOW_BODY_FAT" # unused
|
@@ -31,11 +32,6 @@ class BodyFatPercentIndicator(str, Enum):
|
|
31
32
|
OBESE_BODY_FAT = "OBESE_BODY_FAT" # unused
|
32
33
|
|
33
34
|
|
34
|
-
class Gender(str, Enum):
|
35
|
-
MALE = "M"
|
36
|
-
FEMALE = "F"
|
37
|
-
|
38
|
-
|
39
35
|
def get_percent_body_fat_descriptor(
|
40
36
|
percent_body_fat: float, body_fat_percent_dividers: list[float]
|
41
37
|
) -> BodyFatPercentIndicator:
|
@@ -61,8 +57,8 @@ def get_relative_descriptor(in_body_value: float, in_body_dividers: list[float])
|
|
61
57
|
return AverageType.ABOVE_AVERAGE
|
62
58
|
|
63
59
|
|
64
|
-
def get_body_fat_percent_dividers(age: int, gender:
|
65
|
-
if gender ==
|
60
|
+
def get_body_fat_percent_dividers(age: int, gender: Literal["M", "F"]) -> list[float]:
|
61
|
+
if gender == "M":
|
66
62
|
return get_body_fat_percent_dividers_male(age)
|
67
63
|
|
68
64
|
return get_body_fat_percent_dividers_female(age)
|
@@ -166,13 +162,15 @@ class ExtraCellularWaterOverTotalBodyWater(OtfItemBase):
|
|
166
162
|
|
167
163
|
|
168
164
|
class BodyCompositionData(OtfItemBase):
|
165
|
+
# NOTE: weight is hardcoded to be pounds here, regardless of the unit shown in the member details
|
166
|
+
|
169
167
|
member_uuid: str = Field(..., alias="memberUUId")
|
170
|
-
member_id: str = Field(..., alias="memberId")
|
168
|
+
member_id: str | int = Field(..., alias="memberId")
|
171
169
|
scan_result_uuid: str = Field(..., alias="scanResultUUId")
|
172
|
-
inbody_id: str = Field(..., alias="id", exclude=True, description="InBody ID, same as email address")
|
170
|
+
inbody_id: str = Field(..., alias="id", exclude=True, repr=False, description="InBody ID, same as email address")
|
173
171
|
email: str
|
174
172
|
height: str = Field(..., description="Height in cm")
|
175
|
-
gender:
|
173
|
+
gender: Literal["M", "F"]
|
176
174
|
age: int
|
177
175
|
scan_datetime: datetime = Field(..., alias="testDatetime")
|
178
176
|
provided_weight: float = Field(
|
@@ -193,44 +191,47 @@ class BodyCompositionData(OtfItemBase):
|
|
193
191
|
in_body_type: str = Field(..., alias="inBodyType")
|
194
192
|
|
195
193
|
# excluded because they are only useful for end result of calculations
|
196
|
-
body_fat_mass_dividers: list[float] = Field(..., alias="bfmGraphScale", exclude=True)
|
197
|
-
body_fat_mass_plot_point: float = Field(..., alias="pfatnew", exclude=True)
|
198
|
-
skeletal_muscle_mass_dividers: list[float] = Field(..., alias="smmGraphScale", exclude=True)
|
199
|
-
skeletal_muscle_mass_plot_point: float = Field(..., alias="psmm", exclude=True)
|
200
|
-
weight_dividers: list[float] = Field(..., alias="wtGraphScale", exclude=True)
|
201
|
-
weight_plot_point: float = Field(..., alias="pwt", exclude=True)
|
194
|
+
body_fat_mass_dividers: list[float] = Field(..., alias="bfmGraphScale", exclude=True, repr=False)
|
195
|
+
body_fat_mass_plot_point: float = Field(..., alias="pfatnew", exclude=True, repr=False)
|
196
|
+
skeletal_muscle_mass_dividers: list[float] = Field(..., alias="smmGraphScale", exclude=True, repr=False)
|
197
|
+
skeletal_muscle_mass_plot_point: float = Field(..., alias="psmm", exclude=True, repr=False)
|
198
|
+
weight_dividers: list[float] = Field(..., alias="wtGraphScale", exclude=True, repr=False)
|
199
|
+
weight_plot_point: float = Field(..., alias="pwt", exclude=True, repr=False)
|
202
200
|
|
203
201
|
# excluded due to 0 values
|
204
|
-
body_fat_mass_details: BodyFatMass = Field(..., exclude=True)
|
205
|
-
body_fat_mass_percent_details: BodyFatMassPercent = Field(..., exclude=True)
|
206
|
-
total_body_weight_details: TotalBodyWeight = Field(..., exclude=True)
|
207
|
-
intra_cellular_water_details: IntraCellularWater = Field(..., exclude=True)
|
208
|
-
extra_cellular_water_details: ExtraCellularWater = Field(..., exclude=True)
|
209
|
-
extra_cellular_water_over_total_body_water_details: ExtraCellularWaterOverTotalBodyWater = Field(
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
202
|
+
body_fat_mass_details: BodyFatMass = Field(..., exclude=True, repr=False)
|
203
|
+
body_fat_mass_percent_details: BodyFatMassPercent = Field(..., exclude=True, repr=False)
|
204
|
+
total_body_weight_details: TotalBodyWeight = Field(..., exclude=True, repr=False)
|
205
|
+
intra_cellular_water_details: IntraCellularWater = Field(..., exclude=True, repr=False)
|
206
|
+
extra_cellular_water_details: ExtraCellularWater = Field(..., exclude=True, repr=False)
|
207
|
+
extra_cellular_water_over_total_body_water_details: ExtraCellularWaterOverTotalBodyWater = Field(
|
208
|
+
..., exclude=True, repr=False
|
209
|
+
)
|
210
|
+
visceral_fat_level: float = Field(..., alias="vfl", exclude=True, repr=False)
|
211
|
+
visceral_fat_area: float = Field(..., alias="vfa", exclude=True, repr=False)
|
212
|
+
body_comp_measurement: float = Field(..., alias="bcm", exclude=True, repr=False)
|
213
|
+
total_body_weight_over_lean_body_mass: float = Field(..., alias="tbwOverLBM", exclude=True, repr=False)
|
214
|
+
intracellular_water: float = Field(..., alias="icw", exclude=True, repr=False)
|
215
|
+
extracellular_water: float = Field(..., alias="ecw", exclude=True, repr=False)
|
216
|
+
lean_body_mass_control: float = Field(..., alias="lbmControl", exclude=True, repr=False)
|
217
217
|
|
218
218
|
def __init__(self, **data):
|
219
|
-
#
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
219
|
+
# Convert the nested dictionaries to the appropriate classes
|
220
|
+
attr_to_class_map = {
|
221
|
+
"lean_body_mass_details": LeanBodyMass,
|
222
|
+
"lean_body_mass_percent_details": LeanBodyMassPercent,
|
223
|
+
"body_fat_mass_details": BodyFatMass,
|
224
|
+
"body_fat_mass_percent_details": BodyFatMassPercent,
|
225
|
+
"total_body_weight_details": TotalBodyWeight,
|
226
|
+
"intra_cellular_water_details": IntraCellularWater,
|
227
|
+
"extra_cellular_water_details": ExtraCellularWater,
|
228
|
+
"extra_cellular_water_over_total_body_water_details": ExtraCellularWaterOverTotalBodyWater,
|
224
229
|
}
|
225
|
-
for k, v in child_model_dict.items():
|
226
|
-
data[k] = v(**data)
|
227
230
|
|
228
|
-
|
231
|
+
for attr, cls in attr_to_class_map.items():
|
232
|
+
data[attr] = cls(**data)
|
229
233
|
|
230
|
-
|
231
|
-
@classmethod
|
232
|
-
def int_to_str(cls, v: int):
|
233
|
-
return str(v)
|
234
|
+
super().__init__(**data)
|
234
235
|
|
235
236
|
@field_validator("skeletal_muscle_mass_dividers", "weight_dividers", "body_fat_mass_dividers", mode="before")
|
236
237
|
@classmethod
|
@@ -289,7 +290,3 @@ class BodyCompositionData(OtfItemBase):
|
|
289
290
|
return get_percent_body_fat_descriptor(
|
290
291
|
self.percent_body_fat, get_body_fat_percent_dividers(self.age, self.gender)
|
291
292
|
)
|
292
|
-
|
293
|
-
|
294
|
-
class BodyCompositionList(OtfItemBase):
|
295
|
-
data: list[BodyCompositionData]
|
otf_api/models/bookings.py
CHANGED
@@ -1,119 +1,95 @@
|
|
1
|
-
from collections.abc import Hashable
|
2
1
|
from datetime import datetime
|
3
|
-
from typing import Any
|
4
2
|
|
5
3
|
from pydantic import Field
|
6
4
|
|
7
5
|
from otf_api.models.base import OtfItemBase
|
8
|
-
from otf_api.models.enums import BookingStatus
|
9
|
-
from otf_api.models.
|
10
|
-
|
11
|
-
|
12
|
-
class Location(OtfItemBase):
|
13
|
-
address_one: str | None = Field(None, alias="address1")
|
14
|
-
address_two: str | None = Field(alias="address2")
|
15
|
-
city: str | None = None
|
16
|
-
country: str | None = None
|
17
|
-
distance: float | None = None
|
18
|
-
location_name: str | None = Field(None, alias="locationName")
|
19
|
-
latitude: float | None = Field(None, alias="latitude")
|
20
|
-
longitude: float | None = Field(None, alias="longitude")
|
21
|
-
phone_number: str | None = Field(None, alias="phone")
|
22
|
-
postal_code: str | None = Field(None, alias="postalCode")
|
23
|
-
state: str | None = None
|
6
|
+
from otf_api.models.enums import BookingStatus
|
7
|
+
from otf_api.models.studio_detail import StudioDetail
|
24
8
|
|
25
9
|
|
26
10
|
class Coach(OtfItemBase):
|
27
11
|
coach_uuid: str = Field(alias="coachUUId")
|
28
|
-
name: str
|
29
12
|
first_name: str | None = Field(None, alias="firstName")
|
30
13
|
last_name: str | None = Field(None, alias="lastName")
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
physical_address2: str | None = Field(None, alias="physicalAddress2")
|
42
|
-
physical_state: str | None = Field(None, alias="physicalState")
|
43
|
-
physical_postal_code: str | None = Field(None, alias="physicalPostalCode")
|
44
|
-
physical_region: str | None = Field(None, alias="physicalRegion", exclude=True)
|
45
|
-
physical_country_id: int | None = Field(None, alias="physicalCountryId", exclude=True)
|
46
|
-
physical_country: str | None = Field(None, alias="physicalCountry")
|
47
|
-
country: dict[Hashable, Any] | None = Field(None, alias="country", exclude=True)
|
48
|
-
|
49
|
-
|
50
|
-
class Studio(OtfItemBase):
|
51
|
-
studio_uuid: str = Field(alias="studioUUId")
|
52
|
-
studio_name: str = Field(alias="studioName")
|
53
|
-
studio_id: int = Field(alias="studioId")
|
54
|
-
description: str | None = None
|
55
|
-
contact_email: str | None = Field(None, alias="contactEmail", exclude=True)
|
56
|
-
status: StudioStatus | None = None
|
57
|
-
logo_url: str | None = Field(None, alias="logoUrl", exclude=True)
|
58
|
-
time_zone: str = Field(alias="timeZone")
|
59
|
-
mbo_studio_id: int | None = Field(None, alias="mboStudioId", exclude=True)
|
60
|
-
allows_cr_waitlist: bool | None = Field(None, alias="allowsCRWaitlist")
|
61
|
-
cr_waitlist_flag_last_updated: datetime | None = Field(None, alias="crWaitlistFlagLastUpdated", exclude=True)
|
62
|
-
studio_location: StudioLocation | None = Field(None, alias="studioLocation", exclude=True)
|
63
|
-
|
64
|
-
|
65
|
-
class OtfClass(OtfItemBase, OtfClassTimeMixin):
|
14
|
+
|
15
|
+
# unused fields
|
16
|
+
name: str = Field(exclude=True, repr=False)
|
17
|
+
|
18
|
+
@property
|
19
|
+
def full_name(self) -> str:
|
20
|
+
return f"{self.first_name} {self.last_name}"
|
21
|
+
|
22
|
+
|
23
|
+
class OtfClass(OtfItemBase):
|
66
24
|
class_uuid: str = Field(alias="classUUId")
|
67
25
|
name: str
|
68
|
-
|
69
|
-
|
70
|
-
ends_at_local: datetime = Field(alias="endDateTime")
|
26
|
+
starts_at: datetime = Field(alias="startDateTime", description="Start time in local timezone")
|
27
|
+
ends_at: datetime = Field(alias="endDateTime", description="End time in local timezone")
|
71
28
|
is_available: bool = Field(alias="isAvailable")
|
72
29
|
is_cancelled: bool = Field(alias="isCancelled")
|
73
|
-
|
74
|
-
coach_id: int | None = Field(None, alias="coachId")
|
75
|
-
studio: Studio
|
30
|
+
studio: StudioDetail
|
76
31
|
coach: Coach
|
77
|
-
location: Location | None = None
|
78
|
-
virtual_class: bool | None = Field(None, alias="virtualClass")
|
79
32
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
email: str | None = None
|
86
|
-
phone_number: str | None = Field(None, alias="phoneNumber")
|
87
|
-
gender: str | None = None
|
88
|
-
cc_last_4: str | None = Field(None, alias="ccLast4", exclude=True)
|
33
|
+
# unused fields
|
34
|
+
coach_id: int | None = Field(None, alias="coachId", exclude=True, repr=False, description="Not used by API")
|
35
|
+
description: str | None = Field(None, exclude=True, repr=False)
|
36
|
+
program_name: str | None = Field(None, alias="programName", exclude=True, repr=False)
|
37
|
+
virtual_class: bool | None = Field(None, alias="virtualClass", exclude=True, repr=False)
|
89
38
|
|
90
39
|
|
91
40
|
class Booking(OtfItemBase):
|
92
|
-
|
93
|
-
class_booking_uuid: str = Field(alias="classBookingUUId", description="ID used to cancel the booking")
|
94
|
-
studio_id: int = Field(alias="studioId")
|
95
|
-
class_id: int = Field(alias="classId")
|
41
|
+
booking_uuid: str = Field(alias="classBookingUUId", description="ID used to cancel the booking")
|
96
42
|
is_intro: bool = Field(alias="isIntro")
|
97
|
-
member_id: int = Field(alias="memberId")
|
98
|
-
mbo_member_id: str | None = Field(None, alias="mboMemberId", exclude=True)
|
99
|
-
mbo_class_id: int | None = Field(None, alias="mboClassId", exclude=True)
|
100
|
-
mbo_visit_id: int | None = Field(None, alias="mboVisitId", exclude=True)
|
101
|
-
mbo_waitlist_entry_id: int | None = Field(None, alias="mboWaitlistEntryId", exclude=True)
|
102
|
-
mbo_sync_message: str | None = Field(None, alias="mboSyncMessage", exclude=True)
|
103
43
|
status: BookingStatus
|
104
44
|
booked_date: datetime | None = Field(None, alias="bookedDate")
|
105
45
|
checked_in_date: datetime | None = Field(None, alias="checkedInDate")
|
106
46
|
cancelled_date: datetime | None = Field(None, alias="cancelledDate")
|
107
|
-
created_by: str = Field(alias="createdBy", exclude=True)
|
108
47
|
created_date: datetime = Field(alias="createdDate")
|
109
|
-
updated_by: str = Field(alias="updatedBy", exclude=True)
|
110
48
|
updated_date: datetime = Field(alias="updatedDate")
|
111
49
|
is_deleted: bool = Field(alias="isDeleted")
|
112
|
-
member: Member | None = Field(None, exclude=True)
|
113
50
|
waitlist_position: int | None = Field(None, alias="waitlistPosition")
|
114
51
|
otf_class: OtfClass = Field(alias="class")
|
115
52
|
is_home_studio: bool | None = Field(None, description="Custom helper field to determine if at home studio")
|
116
53
|
|
117
|
-
|
118
|
-
|
119
|
-
|
54
|
+
# unused fields
|
55
|
+
class_booking_id: int = Field(alias="classBookingId", exclude=True, repr=False, description="Not used by API")
|
56
|
+
class_id: int = Field(alias="classId", exclude=True, repr=False, description="Not used by API")
|
57
|
+
created_by: str = Field(alias="createdBy", exclude=True, repr=False)
|
58
|
+
mbo_class_id: int | None = Field(None, alias="mboClassId", exclude=True, repr=False, description="MindBody attr")
|
59
|
+
mbo_member_id: str | None = Field(None, alias="mboMemberId", exclude=True, repr=False, description="MindBody attr")
|
60
|
+
mbo_sync_message: str | None = Field(
|
61
|
+
None, alias="mboSyncMessage", exclude=True, repr=False, description="MindBody attr"
|
62
|
+
)
|
63
|
+
mbo_visit_id: int | None = Field(None, alias="mboVisitId", exclude=True, repr=False, description="MindBody attr")
|
64
|
+
mbo_waitlist_entry_id: int | None = Field(None, alias="mboWaitlistEntryId", exclude=True, repr=False)
|
65
|
+
member_id: int = Field(alias="memberId", exclude=True, repr=False, description="Not used by API")
|
66
|
+
studio_id: int = Field(alias="studioId", exclude=True, repr=False, description="Not used by API")
|
67
|
+
updated_by: str = Field(alias="updatedBy", exclude=True, repr=False)
|
68
|
+
|
69
|
+
@property
|
70
|
+
def studio_uuid(self) -> str:
|
71
|
+
"""Shortcut to get the studio UUID"""
|
72
|
+
return self.otf_class.studio.studio_uuid
|
73
|
+
|
74
|
+
@property
|
75
|
+
def class_uuid(self) -> str:
|
76
|
+
"""Shortcut to get the class UUID"""
|
77
|
+
return self.otf_class.class_uuid
|
78
|
+
|
79
|
+
@property
|
80
|
+
def starts_at(self) -> datetime:
|
81
|
+
"""Shortcut to get the class start time"""
|
82
|
+
return self.otf_class.starts_at
|
83
|
+
|
84
|
+
@property
|
85
|
+
def ends_at(self) -> datetime:
|
86
|
+
"""Shortcut to get the class end time"""
|
87
|
+
return self.otf_class.ends_at
|
88
|
+
|
89
|
+
def __str__(self) -> str:
|
90
|
+
starts_at_str = self.otf_class.starts_at.strftime("%a %b %d, %I:%M %p")
|
91
|
+
class_name = self.otf_class.name
|
92
|
+
coach_name = self.otf_class.coach.name
|
93
|
+
booked_str = self.status.value
|
94
|
+
|
95
|
+
return f"Booking: {starts_at_str} {class_name} - {coach_name} ({booked_str})"
|
@@ -1,38 +1,59 @@
|
|
1
|
+
"""
|
2
|
+
These models represent the data returned by the challenge tracker endpoint.
|
3
|
+
|
4
|
+
I believe these are used by the app to populate the list of challenges, programs,
|
5
|
+
and benchmarks that a member can compete in.
|
6
|
+
|
7
|
+
The actual data for *your* participation in these challenges is returned by a different
|
8
|
+
endpoint.
|
9
|
+
"""
|
10
|
+
|
1
11
|
from pydantic import Field
|
2
12
|
|
3
13
|
from otf_api.models.base import OtfItemBase
|
14
|
+
from otf_api.models.enums import ChallengeCategory, EquipmentType
|
4
15
|
|
5
16
|
|
6
17
|
class Year(OtfItemBase):
|
7
|
-
year:
|
8
|
-
is_participated: bool = Field(
|
9
|
-
in_progress: bool = Field(
|
18
|
+
year: int | None = Field(None, alias="Year")
|
19
|
+
is_participated: bool | None = Field(None, alias="IsParticipated")
|
20
|
+
in_progress: bool | None = Field(None, alias="InProgress")
|
10
21
|
|
11
22
|
|
12
23
|
class Program(OtfItemBase):
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
24
|
+
"""A program represents multi-day/week challenges that members can participate in."""
|
25
|
+
|
26
|
+
# NOTE: These ones do seem to match the ChallengeType enums in the OTF app.
|
27
|
+
# Leaving them as int for now though in case older data or other user's
|
28
|
+
# data doesn't match up.
|
29
|
+
challenge_category_id: int | None = Field(None, alias="ChallengeCategoryId")
|
30
|
+
challenge_sub_category_id: int | None = Field(None, alias="ChallengeSubCategoryId")
|
31
|
+
challenge_name: str | None = Field(None, alias="ChallengeName")
|
32
|
+
years: list[Year] = Field(default_factory=list, alias="Years")
|
18
33
|
|
19
34
|
|
20
35
|
class Challenge(OtfItemBase):
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
36
|
+
"""A challenge represents a single day or event that members can participate in."""
|
37
|
+
|
38
|
+
# NOTE: The challenge category/subcategory ids here do not seem to be at
|
39
|
+
# all related to the ChallengeType enums or the few SubCategory enums I've
|
40
|
+
# been able to puzzle out. I haven't been able to link them to any code
|
41
|
+
# in the OTF app. Due to that, they are being excluded from the model for now.
|
42
|
+
challenge_category_id: ChallengeCategory | None = Field(None, alias="ChallengeCategoryId")
|
43
|
+
challenge_sub_category_id: int | None = Field(None, alias="ChallengeSubCategoryId")
|
44
|
+
challenge_name: str | None = Field(None, alias="ChallengeName")
|
45
|
+
years: list[Year] = Field(default_factory=list, alias="Years")
|
26
46
|
|
27
47
|
|
28
48
|
class Benchmark(OtfItemBase):
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
49
|
+
"""A benchmark represents a specific workout that members can participate in."""
|
50
|
+
|
51
|
+
equipment_id: EquipmentType | None = Field(None, alias="EquipmentId")
|
52
|
+
equipment_name: str | None = Field(None, alias="EquipmentName")
|
53
|
+
years: list[Year] = Field(default_factory=list, alias="Years")
|
33
54
|
|
34
55
|
|
35
|
-
class
|
36
|
-
programs: list[Program] = Field(
|
37
|
-
challenges: list[Challenge] = Field(
|
38
|
-
benchmarks: list[Benchmark] = Field(
|
56
|
+
class ChallengeTracker(OtfItemBase):
|
57
|
+
programs: list[Program] = Field(default_factory=list, alias="Programs")
|
58
|
+
challenges: list[Challenge] = Field(default_factory=list, alias="Challenges")
|
59
|
+
benchmarks: list[Benchmark] = Field(default_factory=list, alias="Benchmarks")
|