otf-api 0.9.4__py3-none-any.whl → 0.10.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 +1 -1
- otf_api/api.py +391 -210
- otf_api/models/__init__.py +13 -7
- otf_api/models/enums.py +19 -4
- otf_api/models/performance_summary.py +169 -0
- otf_api/models/studio_detail.py +5 -3
- otf_api/models/telemetry.py +4 -1
- {otf_api-0.9.4.dist-info → otf_api-0.10.1.dist-info}/METADATA +3 -1
- {otf_api-0.9.4.dist-info → otf_api-0.10.1.dist-info}/RECORD +11 -12
- otf_api/models/performance_summary_detail.py +0 -89
- otf_api/models/performance_summary_list.py +0 -50
- {otf_api-0.9.4.dist-info → otf_api-0.10.1.dist-info}/LICENSE +0 -0
- {otf_api-0.9.4.dist-info → otf_api-0.10.1.dist-info}/WHEEL +0 -0
otf_api/models/__init__.py
CHANGED
@@ -3,39 +3,45 @@ from .bookings import Booking
|
|
3
3
|
from .challenge_tracker_content import ChallengeTracker
|
4
4
|
from .challenge_tracker_detail import FitnessBenchmark
|
5
5
|
from .classes import OtfClass
|
6
|
-
from .enums import
|
6
|
+
from .enums import (
|
7
|
+
HISTORICAL_BOOKING_STATUSES,
|
8
|
+
BookingStatus,
|
9
|
+
ChallengeCategory,
|
10
|
+
ClassType,
|
11
|
+
DoW,
|
12
|
+
EquipmentType,
|
13
|
+
StatsTime,
|
14
|
+
StudioStatus,
|
15
|
+
)
|
7
16
|
from .lifetime_stats import StatsResponse, TimeStats
|
8
17
|
from .member_detail import MemberDetail
|
9
18
|
from .member_membership import MemberMembership
|
10
19
|
from .member_purchases import MemberPurchase
|
11
20
|
from .notifications import EmailNotificationSettings, SmsNotificationSettings
|
12
21
|
from .out_of_studio_workout_history import OutOfStudioWorkoutHistory
|
13
|
-
from .
|
14
|
-
from .performance_summary_list import PerformanceSummaryEntry
|
22
|
+
from .performance_summary import PerformanceSummary
|
15
23
|
from .studio_detail import StudioDetail
|
16
24
|
from .studio_services import StudioService
|
17
25
|
from .telemetry import Telemetry, TelemetryHistoryItem
|
18
26
|
|
19
27
|
__all__ = [
|
28
|
+
"HISTORICAL_BOOKING_STATUSES",
|
20
29
|
"BodyCompositionData",
|
21
30
|
"Booking",
|
22
31
|
"BookingStatus",
|
23
32
|
"ChallengeCategory",
|
24
|
-
"ChallengeParticipation",
|
25
33
|
"ChallengeTracker",
|
26
34
|
"ClassType",
|
27
35
|
"DoW",
|
28
36
|
"EmailNotificationSettings",
|
29
37
|
"EquipmentType",
|
30
38
|
"FitnessBenchmark",
|
31
|
-
"LatestAgreement",
|
32
39
|
"MemberDetail",
|
33
40
|
"MemberMembership",
|
34
41
|
"MemberPurchase",
|
35
42
|
"OtfClass",
|
36
43
|
"OutOfStudioWorkoutHistory",
|
37
|
-
"
|
38
|
-
"PerformanceSummaryEntry",
|
44
|
+
"PerformanceSummary",
|
39
45
|
"SmsNotificationSettings",
|
40
46
|
"StatsResponse",
|
41
47
|
"StatsTime",
|
otf_api/models/enums.py
CHANGED
@@ -8,19 +8,34 @@ class StudioStatus(StrEnum):
|
|
8
8
|
COMING_SOON = "Coming Soon"
|
9
9
|
TEMP_CLOSED = "Temporarily Closed"
|
10
10
|
PERM_CLOSED = "Permanently Closed"
|
11
|
+
UNKNOWN = "Unknown"
|
11
12
|
|
12
13
|
|
13
14
|
class BookingStatus(StrEnum):
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
Pending = "Pending"
|
16
|
+
Requested = "Requested"
|
17
|
+
Booked = "Booked"
|
17
18
|
Cancelled = "Cancelled"
|
18
19
|
LateCancelled = "Late Cancelled"
|
19
|
-
Booked = "Booked"
|
20
20
|
Waitlisted = "Waitlisted"
|
21
|
+
CheckedIn = "Checked In"
|
21
22
|
CheckinPending = "Checkin Pending"
|
22
23
|
CheckinRequested = "Checkin Requested"
|
24
|
+
Confirmed = "Confirmed"
|
23
25
|
CheckinCancelled = "Checkin Cancelled"
|
26
|
+
CancelCheckinPending = "Cancel Checkin Pending"
|
27
|
+
CancelCheckinRequested = "Cancel Checkin Requested"
|
28
|
+
|
29
|
+
|
30
|
+
HISTORICAL_BOOKING_STATUSES = [
|
31
|
+
BookingStatus.CheckedIn,
|
32
|
+
BookingStatus.CancelCheckinPending,
|
33
|
+
BookingStatus.CancelCheckinRequested,
|
34
|
+
BookingStatus.LateCancelled,
|
35
|
+
BookingStatus.CheckinPending,
|
36
|
+
BookingStatus.CheckinRequested,
|
37
|
+
BookingStatus.CheckinCancelled,
|
38
|
+
]
|
24
39
|
|
25
40
|
|
26
41
|
class DoW(StrEnum):
|
@@ -0,0 +1,169 @@
|
|
1
|
+
from datetime import datetime, time
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
from pydantic import AliasPath, Field, field_validator
|
5
|
+
|
6
|
+
from otf_api.models.base import OtfItemBase
|
7
|
+
from otf_api.models.enums import ClassType
|
8
|
+
from otf_api.models.studio_detail import StudioDetail
|
9
|
+
|
10
|
+
|
11
|
+
class ZoneTimeMinutes(OtfItemBase):
|
12
|
+
gray: int
|
13
|
+
blue: int
|
14
|
+
green: int
|
15
|
+
orange: int
|
16
|
+
red: int
|
17
|
+
|
18
|
+
|
19
|
+
class CoachRating(OtfItemBase):
|
20
|
+
id: str
|
21
|
+
description: str
|
22
|
+
value: int
|
23
|
+
|
24
|
+
|
25
|
+
class ClassRating(OtfItemBase):
|
26
|
+
id: str
|
27
|
+
description: str
|
28
|
+
value: int
|
29
|
+
|
30
|
+
|
31
|
+
class HeartRate(OtfItemBase):
|
32
|
+
max_hr: int
|
33
|
+
peak_hr: int
|
34
|
+
peak_hr_percent: int
|
35
|
+
avg_hr: int
|
36
|
+
avg_hr_percent: int
|
37
|
+
|
38
|
+
|
39
|
+
class Class(OtfItemBase):
|
40
|
+
class_uuid: str | None = Field(None, description="Only populated if class is ratable", alias="ot_base_class_uuid")
|
41
|
+
starts_at: datetime | None = Field(None, alias="starts_at_local")
|
42
|
+
type: ClassType | None = None
|
43
|
+
studio: StudioDetail | None = None
|
44
|
+
name: str | None = None
|
45
|
+
|
46
|
+
|
47
|
+
class PerformanceMetric(OtfItemBase):
|
48
|
+
display_value: time | float
|
49
|
+
display_unit: str
|
50
|
+
metric_value: float
|
51
|
+
|
52
|
+
@field_validator("display_value", mode="before")
|
53
|
+
@classmethod
|
54
|
+
def convert_to_time_format(cls, value) -> time | float:
|
55
|
+
if not value:
|
56
|
+
return value
|
57
|
+
|
58
|
+
if isinstance(value, float | int):
|
59
|
+
return value
|
60
|
+
|
61
|
+
if isinstance(value, str) and ":" in value:
|
62
|
+
if value.count(":") == 1:
|
63
|
+
minutes, seconds = value.split(":")
|
64
|
+
return time(minute=int(minutes), second=int(seconds))
|
65
|
+
if value.count(":") == 2:
|
66
|
+
hours, minutes, seconds = value.split(":")
|
67
|
+
return time(hour=int(hours), minute=int(minutes), second=int(seconds))
|
68
|
+
|
69
|
+
return value
|
70
|
+
|
71
|
+
|
72
|
+
class BaseEquipment(OtfItemBase):
|
73
|
+
avg_pace: PerformanceMetric
|
74
|
+
avg_speed: PerformanceMetric
|
75
|
+
max_pace: PerformanceMetric
|
76
|
+
max_speed: PerformanceMetric
|
77
|
+
moving_time: PerformanceMetric
|
78
|
+
total_distance: PerformanceMetric
|
79
|
+
|
80
|
+
|
81
|
+
class Treadmill(BaseEquipment):
|
82
|
+
avg_incline: PerformanceMetric
|
83
|
+
elevation_gained: PerformanceMetric
|
84
|
+
max_incline: PerformanceMetric
|
85
|
+
|
86
|
+
|
87
|
+
class Rower(BaseEquipment):
|
88
|
+
avg_cadence: PerformanceMetric
|
89
|
+
avg_power: PerformanceMetric
|
90
|
+
max_cadence: PerformanceMetric
|
91
|
+
|
92
|
+
|
93
|
+
class PerformanceSummary(OtfItemBase):
|
94
|
+
"""Represents a workout performance summary - the same data that is shown in the OTF app after a workout"
|
95
|
+
|
96
|
+
This data comes from two different endpoints that do not actually match, because of course not.
|
97
|
+
The summary endpoint returns one distinct set of data plus some detailed data - this is the only place we can get
|
98
|
+
the studio information. The detail endpoint returns more performance data but does not have much class data and does
|
99
|
+
not have the studio.
|
100
|
+
|
101
|
+
* The summary endpoint data is missing step_count, the value is always 0.
|
102
|
+
* The detail endpoint data is missing active_time_seconds, the value is always 0.
|
103
|
+
* The detail endpoint class name is more generic than the summary endpoint class name.
|
104
|
+
|
105
|
+
|
106
|
+
"""
|
107
|
+
|
108
|
+
performance_summary_id: str = Field(..., alias="id", description="Unique identifier for this performance summary")
|
109
|
+
class_history_uuid: str = Field(..., alias="id", description="Same as performance_summary_id")
|
110
|
+
ratable: bool | None = None
|
111
|
+
otf_class: Class | None = Field(None, alias="class")
|
112
|
+
coach: str | None = Field(None, alias=AliasPath("class", "coach", "first_name"))
|
113
|
+
coach_rating: CoachRating | None = Field(None, alias=AliasPath("ratings", "coach"))
|
114
|
+
class_rating: ClassRating | None = Field(None, alias=AliasPath("ratings", "class"))
|
115
|
+
|
116
|
+
active_time_seconds: int | None = Field(None, alias=AliasPath("details", "active_time_seconds"))
|
117
|
+
calories_burned: int | None = Field(None, alias=AliasPath("details", "calories_burned"))
|
118
|
+
splat_points: int | None = Field(None, alias=AliasPath("details", "splat_points"))
|
119
|
+
step_count: int | None = Field(None, alias=AliasPath("details", "step_count"))
|
120
|
+
zone_time_minutes: ZoneTimeMinutes | None = Field(None, alias=AliasPath("details", "zone_time_minutes"))
|
121
|
+
heart_rate: HeartRate | None = Field(None, alias=AliasPath("details", "heart_rate"))
|
122
|
+
|
123
|
+
rower_data: Rower | None = Field(None, alias=AliasPath("details", "equipment_data", "rower"))
|
124
|
+
treadmill_data: Treadmill | None = Field(None, alias=AliasPath("details", "equipment_data", "treadmill"))
|
125
|
+
|
126
|
+
@property
|
127
|
+
def is_rated(self) -> bool:
|
128
|
+
return self.coach_rating is not None or self.class_rating is not None
|
129
|
+
|
130
|
+
@property
|
131
|
+
def studio(self) -> StudioDetail | None:
|
132
|
+
return self.otf_class.studio if self.otf_class else None
|
133
|
+
|
134
|
+
def __init__(self, **kwargs) -> None:
|
135
|
+
summary_detail = kwargs.pop("details", {}) or {}
|
136
|
+
true_detail = kwargs.pop("detail", {}) or {}
|
137
|
+
|
138
|
+
summary_class = kwargs.pop("class", {})
|
139
|
+
detail_class = true_detail.pop("class", {})
|
140
|
+
|
141
|
+
kwargs["class"] = combine_class_data(summary_class, detail_class)
|
142
|
+
kwargs["details"] = combine_detail_data(summary_detail, true_detail.pop("details", {}))
|
143
|
+
|
144
|
+
super().__init__(**kwargs)
|
145
|
+
|
146
|
+
|
147
|
+
def combine_class_data(summary_class: dict[str, str], detail_class: dict[str, str]) -> dict[str, str]:
|
148
|
+
class_data = {}
|
149
|
+
|
150
|
+
class_data["ot_base_class_uuid"] = summary_class.get("ot_base_class_uuid")
|
151
|
+
class_data["starts_at_local"] = summary_class.get("starts_at_local") or detail_class.get("starts_at_local")
|
152
|
+
class_data["type"] = summary_class.get("type")
|
153
|
+
class_data["studio"] = summary_class.get("studio")
|
154
|
+
class_data["name"] = detail_class.get("name") or summary_class.get("name")
|
155
|
+
class_data["coach"] = summary_class.get("coach")
|
156
|
+
|
157
|
+
return class_data
|
158
|
+
|
159
|
+
|
160
|
+
def combine_detail_data(summary_detail: dict[str, Any], true_detail: dict[str, Any]) -> dict[str, Any]:
|
161
|
+
# active time seconds always 0 in detail
|
162
|
+
del true_detail["active_time_seconds"]
|
163
|
+
|
164
|
+
# step count always 0 in summary
|
165
|
+
summary_detail["step_count"] = true_detail["step_count"]
|
166
|
+
|
167
|
+
combined_details = summary_detail | true_detail
|
168
|
+
|
169
|
+
return combined_details
|
otf_api/models/studio_detail.py
CHANGED
@@ -9,8 +9,8 @@ from otf_api.models.mixins import AddressMixin
|
|
9
9
|
|
10
10
|
class StudioLocation(AddressMixin):
|
11
11
|
phone_number: str | None = Field(None, alias=AliasChoices("phone", "phoneNumber"))
|
12
|
-
latitude: float = Field(
|
13
|
-
longitude: float = Field(
|
12
|
+
latitude: float | None = Field(None, alias=AliasChoices("latitude"))
|
13
|
+
longitude: float | None = Field(None, alias=AliasChoices("longitude"))
|
14
14
|
|
15
15
|
physical_region: str | None = Field(None, alias="physicalRegion", exclude=True, repr=False)
|
16
16
|
physical_country_id: int | None = Field(None, alias="physicalCountryId", exclude=True, repr=False)
|
@@ -24,8 +24,10 @@ class StudioDetail(OtfItemBase):
|
|
24
24
|
None,
|
25
25
|
description="Distance from latitude and longitude provided to `search_studios_by_geo` method,\
|
26
26
|
NULL if that method was not used",
|
27
|
+
exclude=True,
|
28
|
+
repr=False,
|
27
29
|
)
|
28
|
-
location: StudioLocation = Field(..., alias="studioLocation")
|
30
|
+
location: StudioLocation = Field(..., alias="studioLocation", default_factory=StudioLocation)
|
29
31
|
name: str | None = Field(None, alias="studioName")
|
30
32
|
status: StudioStatus | None = Field(
|
31
33
|
None, alias="studioStatus", description="Active, Temporarily Closed, Coming Soon"
|
otf_api/models/telemetry.py
CHANGED
@@ -49,7 +49,10 @@ class TelemetryItem(OtfItemBase):
|
|
49
49
|
|
50
50
|
class Telemetry(OtfItemBase):
|
51
51
|
member_uuid: str = Field(..., alias="memberUuid")
|
52
|
-
|
52
|
+
performance_summary_id: str = Field(
|
53
|
+
..., alias="classHistoryUuid", description="The ID of the performance summary this telemetry item belongs to."
|
54
|
+
)
|
55
|
+
class_history_uuid: str = Field(..., alias="classHistoryUuid", description="The same as performance_summary_id.")
|
53
56
|
class_start_time: datetime | None = Field(None, alias="classStartTime")
|
54
57
|
max_hr: int | None = Field(None, alias="maxHr")
|
55
58
|
zones: Zones
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: otf-api
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.10.1
|
4
4
|
Summary: Python OrangeTheory Fitness API Client
|
5
5
|
License: MIT
|
6
6
|
Author: Jessica Smith
|
@@ -19,6 +19,7 @@ Classifier: Topic :: Software Development :: Libraries
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
20
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
21
21
|
Requires-Dist: attrs (>=24.3.0,<25.0.0)
|
22
|
+
Requires-Dist: cachetools (>=5.5.0)
|
22
23
|
Requires-Dist: httpx (>=0.27.2)
|
23
24
|
Requires-Dist: humanize (>=4.9.0,<5.0.0)
|
24
25
|
Requires-Dist: inflection (==0.5.*)
|
@@ -26,6 +27,7 @@ Requires-Dist: mypy-boto3-cognito-idp (>=1.35.93,<2.0.0)
|
|
26
27
|
Requires-Dist: pint (==0.24.*)
|
27
28
|
Requires-Dist: pycognito (==2024.5.1)
|
28
29
|
Requires-Dist: pydantic (>=2.7.3)
|
30
|
+
Requires-Dist: tenacity (>=9.0.0,<10.0.0)
|
29
31
|
Requires-Dist: yarl (>=1.18.3,<2.0.0)
|
30
32
|
Project-URL: Documentation, https://otf-api.readthedocs.io/en/stable/
|
31
33
|
Description-Content-Type: text/markdown
|
@@ -1,5 +1,5 @@
|
|
1
|
-
otf_api/__init__.py,sha256=
|
2
|
-
otf_api/api.py,sha256=
|
1
|
+
otf_api/__init__.py,sha256=jmIypN9FJ7JVYl5DU1tyUooGkPfpx-vsQ-kzOi36G1w,205
|
2
|
+
otf_api/api.py,sha256=eAAkmgE7nySpHEe52O9SWv6hC3hLy1Aiy5jDz6ySoV0,60312
|
3
3
|
otf_api/auth/__init__.py,sha256=Pu6ugbEXhgjJEpYKr7K2CqEac53Wq9FRF3F16GLQ_ww,133
|
4
4
|
otf_api/auth/auth.py,sha256=m9qzUn0hdAo6AZHm2Mn054nLqUWPxEyP3y3DOBVkvYA,9010
|
5
5
|
otf_api/auth/user.py,sha256=HgggfneeVMnKFWSUMnlikfZyC__HE8vVaKSpBS4_sIg,2256
|
@@ -7,14 +7,14 @@ otf_api/auth/utils.py,sha256=jUH_A1-DU3KfY-XrymCuQoud79o9qfu5P9Ws9QA2aWs,3211
|
|
7
7
|
otf_api/exceptions.py,sha256=GISekwF5dPt0Ol0WCU55kE5ODc5VxicNEEhmlguuE0U,1815
|
8
8
|
otf_api/filters.py,sha256=fk2bFGi3srjS96qZlaDx-ARZRaj93NUTUdMJ01TX420,3702
|
9
9
|
otf_api/logging.py,sha256=PRZpCaJ1F1Xya3L9Efkt3mKS5_QNr3sXjEUERSxYjvE,563
|
10
|
-
otf_api/models/__init__.py,sha256=
|
10
|
+
otf_api/models/__init__.py,sha256=Ak1Wph5Yn9Npu3m3vvG-n5CsMH6j7NCuzoOBd0b_OlI,1524
|
11
11
|
otf_api/models/base.py,sha256=KJlIxl_sRj6f-g5vKYPw4yV6fGDk-fwZ93EO0JGPYMw,202
|
12
12
|
otf_api/models/body_composition_list.py,sha256=jGdR-9ScvIOtULJNB99aYh2INk2ihoHAnTWtbQCIea4,12202
|
13
13
|
otf_api/models/bookings.py,sha256=8zgLnQ40ci0c7AimGeoAI-Raw8d2byvKoXkJXyW1xUE,4260
|
14
14
|
otf_api/models/challenge_tracker_content.py,sha256=zukbda38DloiBHW9HUEKQnumGhgVqpjjbxo7G4bDIc0,2630
|
15
15
|
otf_api/models/challenge_tracker_detail.py,sha256=KDfLRDjr8quVhDPVj9x_tuLnkJPua5y8C3WtGIVzXZw,4356
|
16
16
|
otf_api/models/classes.py,sha256=DZ7oo_MNK7m3ul2gK929maGBksekG9RtLzYa-Di8skk,3005
|
17
|
-
otf_api/models/enums.py,sha256=
|
17
|
+
otf_api/models/enums.py,sha256=VyG3w-kcpDMTsHvX7s6xp-cI61mqvsmN_A76jsnmKj0,4604
|
18
18
|
otf_api/models/lifetime_stats.py,sha256=sQw0oaT5uSlCTZyMSEEhYJg7IdOzuxelTI3mzDBHNls,3055
|
19
19
|
otf_api/models/member_detail.py,sha256=CDDZg3Ow07U57yRqbm3i-BVha0cvCNOZ8QhN0pliU_A,6598
|
20
20
|
otf_api/models/member_membership.py,sha256=jZwHzwtVyMUr8dWGlFbMYj9qveCbiOblWW5szXDUFFo,1338
|
@@ -22,14 +22,13 @@ otf_api/models/member_purchases.py,sha256=Ne7ByEbGTqTJhuEyCgywWe8I3nc-D46qw09up7
|
|
22
22
|
otf_api/models/mixins.py,sha256=VR5EeweySHhzaiqqnCr853Cpe1uK97cxY0IFEdf5T8w,2262
|
23
23
|
otf_api/models/notifications.py,sha256=AkmIfiIiU6wox_7puyenbhCX10SFvBD5eBAovcurRgY,833
|
24
24
|
otf_api/models/out_of_studio_workout_history.py,sha256=El5i0K2Td_sMReyfUKP-Iv1L1WgRx0ijjjnHzYvlCeY,1703
|
25
|
-
otf_api/models/
|
26
|
-
otf_api/models/
|
27
|
-
otf_api/models/studio_detail.py,sha256=4HZXP6khjuFs7J7lokr3rMEDDpCwcb7OYJVJvzgly7U,3639
|
25
|
+
otf_api/models/performance_summary.py,sha256=0pg1TTnYEk7HW6exA3Czclt1YMtYRBusgPQFWN_udGo,6066
|
26
|
+
otf_api/models/studio_detail.py,sha256=DZicrQWee8iexyK5I8UN1wBYevTB_1dciz2EKBhPids,3729
|
28
27
|
otf_api/models/studio_services.py,sha256=aGLQMQmjGVpI6YxzAl-mcp3Y9cHPXuH9dIqrl6E-78E,1665
|
29
|
-
otf_api/models/telemetry.py,sha256=
|
28
|
+
otf_api/models/telemetry.py,sha256=LuoQ-W7wWQ-trjQqfITQsUpkWL1X3TXdsHiDhyIDvUk,2748
|
30
29
|
otf_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
30
|
otf_api/utils.py,sha256=-9JBjRmoS6xpua_lHccJYjEX2tWp2-5QQrbbuOYMyIo,3945
|
32
|
-
otf_api-0.
|
33
|
-
otf_api-0.
|
34
|
-
otf_api-0.
|
35
|
-
otf_api-0.
|
31
|
+
otf_api-0.10.1.dist-info/LICENSE,sha256=UaPT9ynYigC3nX8n22_rC37n-qmTRKLFaHrtUwF9ktE,1071
|
32
|
+
otf_api-0.10.1.dist-info/METADATA,sha256=vOdLR2wtWuJGfjPCKnvjLzqziKPZvnh1DJKR47-QGsI,2375
|
33
|
+
otf_api-0.10.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
34
|
+
otf_api-0.10.1.dist-info/RECORD,,
|
@@ -1,89 +0,0 @@
|
|
1
|
-
from datetime import datetime, time
|
2
|
-
|
3
|
-
from pydantic import AliasPath, Field, field_validator
|
4
|
-
|
5
|
-
from otf_api.models.base import OtfItemBase
|
6
|
-
|
7
|
-
|
8
|
-
class ZoneTimeMinutes(OtfItemBase):
|
9
|
-
gray: int
|
10
|
-
blue: int
|
11
|
-
green: int
|
12
|
-
orange: int
|
13
|
-
red: int
|
14
|
-
|
15
|
-
|
16
|
-
class HeartRate(OtfItemBase):
|
17
|
-
max_hr: int
|
18
|
-
peak_hr: int
|
19
|
-
peak_hr_percent: int
|
20
|
-
avg_hr: int
|
21
|
-
avg_hr_percent: int
|
22
|
-
|
23
|
-
|
24
|
-
class PerformanceMetric(OtfItemBase):
|
25
|
-
display_value: time | float
|
26
|
-
display_unit: str
|
27
|
-
metric_value: float
|
28
|
-
|
29
|
-
@field_validator("display_value", mode="before")
|
30
|
-
@classmethod
|
31
|
-
def convert_to_time_format(cls, value) -> time | float:
|
32
|
-
if not value:
|
33
|
-
return value
|
34
|
-
|
35
|
-
if isinstance(value, float | int):
|
36
|
-
return value
|
37
|
-
|
38
|
-
if isinstance(value, str) and ":" in value:
|
39
|
-
if value.count(":") == 1:
|
40
|
-
minutes, seconds = value.split(":")
|
41
|
-
return time(minute=int(minutes), second=int(seconds))
|
42
|
-
if value.count(":") == 2:
|
43
|
-
hours, minutes, seconds = value.split(":")
|
44
|
-
return time(hour=int(hours), minute=int(minutes), second=int(seconds))
|
45
|
-
|
46
|
-
return value
|
47
|
-
|
48
|
-
|
49
|
-
class BaseEquipment(OtfItemBase):
|
50
|
-
avg_pace: PerformanceMetric
|
51
|
-
avg_speed: PerformanceMetric
|
52
|
-
max_pace: PerformanceMetric
|
53
|
-
max_speed: PerformanceMetric
|
54
|
-
moving_time: PerformanceMetric
|
55
|
-
total_distance: PerformanceMetric
|
56
|
-
|
57
|
-
|
58
|
-
class Treadmill(BaseEquipment):
|
59
|
-
avg_incline: PerformanceMetric
|
60
|
-
elevation_gained: PerformanceMetric
|
61
|
-
max_incline: PerformanceMetric
|
62
|
-
|
63
|
-
|
64
|
-
class Rower(BaseEquipment):
|
65
|
-
avg_cadence: PerformanceMetric
|
66
|
-
avg_power: PerformanceMetric
|
67
|
-
max_cadence: PerformanceMetric
|
68
|
-
|
69
|
-
|
70
|
-
class PerformanceSummaryDetail(OtfItemBase):
|
71
|
-
class_history_uuid: str = Field(..., alias="id")
|
72
|
-
class_name: str | None = Field(None, alias=AliasPath("class", "name"))
|
73
|
-
class_starts_at: datetime | None = Field(None, alias=AliasPath("class", "starts_at_local"))
|
74
|
-
|
75
|
-
ratable: bool | None = Field(
|
76
|
-
None,
|
77
|
-
exclude=True,
|
78
|
-
repr=False,
|
79
|
-
description="Seems to be inaccurate, not reflecting ratable from `PerformanceSummaryEntry`",
|
80
|
-
)
|
81
|
-
calories_burned: int | None = Field(None, alias=AliasPath("details", "calories_burned"))
|
82
|
-
splat_points: int | None = Field(None, alias=AliasPath("details", "splat_points"))
|
83
|
-
step_count: int | None = Field(None, alias=AliasPath("details", "step_count"))
|
84
|
-
active_time_seconds: int | None = Field(None, alias=AliasPath("details", "active_time_seconds"))
|
85
|
-
zone_time_minutes: ZoneTimeMinutes | None = Field(None, alias=AliasPath("details", "zone_time_minutes"))
|
86
|
-
heart_rate: HeartRate | None = Field(None, alias=AliasPath("details", "heart_rate"))
|
87
|
-
|
88
|
-
rower_data: Rower | None = Field(None, alias=AliasPath("details", "equipment_data", "rower"))
|
89
|
-
treadmill_data: Treadmill | None = Field(None, alias=AliasPath("details", "equipment_data", "treadmill"))
|
@@ -1,50 +0,0 @@
|
|
1
|
-
from datetime import datetime
|
2
|
-
|
3
|
-
from pydantic import AliasPath, Field
|
4
|
-
|
5
|
-
from otf_api.models.base import OtfItemBase
|
6
|
-
|
7
|
-
|
8
|
-
class ZoneTimeMinutes(OtfItemBase):
|
9
|
-
gray: int
|
10
|
-
blue: int
|
11
|
-
green: int
|
12
|
-
orange: int
|
13
|
-
red: int
|
14
|
-
|
15
|
-
|
16
|
-
class Class(OtfItemBase):
|
17
|
-
class_uuid: str | None = Field(None, description="Only populated if class is ratable", alias="ot_base_class_uuid")
|
18
|
-
starts_at: datetime | None = Field(None, alias="starts_at_local")
|
19
|
-
name: str | None = None
|
20
|
-
type: str | None = None
|
21
|
-
|
22
|
-
|
23
|
-
class CoachRating(OtfItemBase):
|
24
|
-
id: str
|
25
|
-
description: str
|
26
|
-
value: int
|
27
|
-
|
28
|
-
|
29
|
-
class ClassRating(OtfItemBase):
|
30
|
-
id: str
|
31
|
-
description: str
|
32
|
-
value: int
|
33
|
-
|
34
|
-
|
35
|
-
class PerformanceSummaryEntry(OtfItemBase):
|
36
|
-
class_history_uuid: str = Field(..., alias="id")
|
37
|
-
calories_burned: int | None = Field(None, alias=AliasPath("details", "calories_burned"))
|
38
|
-
splat_points: int | None = Field(None, alias=AliasPath("details", "splat_points"))
|
39
|
-
step_count: int | None = Field(None, alias=AliasPath("details", "step_count"))
|
40
|
-
active_time_seconds: int | None = Field(None, alias=AliasPath("details", "active_time_seconds"))
|
41
|
-
zone_time_minutes: ZoneTimeMinutes | None = Field(None, alias=AliasPath("details", "zone_time_minutes"))
|
42
|
-
ratable: bool | None = None
|
43
|
-
otf_class: Class | None = Field(None, alias="class")
|
44
|
-
coach: str | None = Field(None, alias=AliasPath("class", "coach", "first_name"))
|
45
|
-
coach_rating: CoachRating | None = Field(None, alias=AliasPath("ratings", "coach"))
|
46
|
-
class_rating: ClassRating | None = Field(None, alias=AliasPath("ratings", "class"))
|
47
|
-
|
48
|
-
@property
|
49
|
-
def is_rated(self) -> bool:
|
50
|
-
return self.coach_rating is not None or self.class_rating is not None
|
File without changes
|
File without changes
|