otf-api 0.4.0__tar.gz → 0.5.0__tar.gz
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-0.4.0 → otf_api-0.5.0}/PKG-INFO +1 -1
- {otf_api-0.4.0 → otf_api-0.5.0}/pyproject.toml +1 -1
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/__init__.py +1 -1
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/api.py +20 -64
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/auth.py +2 -1
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/__init__.py +0 -2
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/__init__.py +0 -2
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/performance_summary_list.py +2 -2
- otf_api-0.4.0/src/otf_api/models/responses/workouts.py +0 -78
- {otf_api-0.4.0 → otf_api-0.5.0}/AUTHORS.md +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/LICENSE +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/README.md +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/cli/__init__.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/cli/_utilities.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/cli/app.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/cli/bookings.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/cli/prompts.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/base.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/body_composition_list.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/book_class.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/bookings.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/cancel_booking.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/challenge_tracker_content.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/challenge_tracker_detail.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/classes.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/enums.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/favorite_studios.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/latest_agreement.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/lifetime_stats.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/member_detail.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/member_membership.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/member_purchases.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/out_of_studio_workout_history.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/performance_summary_detail.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/studio_detail.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/studio_services.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/telemetry.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/telemetry_hr_history.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/telemetry_max_hr.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/total_classes.py +0 -0
- {otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/py.typed +0 -0
@@ -3,7 +3,6 @@ import contextlib
|
|
3
3
|
import json
|
4
4
|
import typing
|
5
5
|
from datetime import date, datetime
|
6
|
-
from math import ceil
|
7
6
|
from typing import Any
|
8
7
|
|
9
8
|
import aiohttp
|
@@ -43,7 +42,6 @@ from otf_api.models import (
|
|
43
42
|
TelemetryHrHistory,
|
44
43
|
TelemetryMaxHr,
|
45
44
|
TotalClasses,
|
46
|
-
WorkoutList,
|
47
45
|
)
|
48
46
|
|
49
47
|
|
@@ -226,32 +224,15 @@ class Otf:
|
|
226
224
|
"""Perform an API request to the performance summary API."""
|
227
225
|
return await self._do(method, API_IO_BASE_URL, url, params, headers)
|
228
226
|
|
229
|
-
async def
|
230
|
-
"""Get the
|
227
|
+
async def get_body_composition_list(self) -> BodyCompositionList:
|
228
|
+
"""Get the member's body composition list.
|
231
229
|
|
232
230
|
Returns:
|
233
|
-
|
234
|
-
|
235
|
-
Info:
|
236
|
-
---
|
237
|
-
This returns data from the same api the [OT Live website](https://otlive.orangetheory.com/) uses.
|
238
|
-
It is quite a bit of data, and all workouts going back to ~2019. The data includes the class history
|
239
|
-
UUID, which can be used to get telemetry data for a specific workout.
|
231
|
+
Any: The member's body composition list.
|
240
232
|
"""
|
233
|
+
data = await self._default_request("GET", f"/member/members/{self._member_uuid}/body-composition")
|
241
234
|
|
242
|
-
|
243
|
-
|
244
|
-
return WorkoutList(workouts=res["data"])
|
245
|
-
|
246
|
-
async def get_total_classes(self) -> TotalClasses:
|
247
|
-
"""Get the member's total classes. This is a simple object reflecting the total number of classes attended,
|
248
|
-
both in-studio and OT Live.
|
249
|
-
|
250
|
-
Returns:
|
251
|
-
TotalClasses: The member's total classes.
|
252
|
-
"""
|
253
|
-
data = await self._default_request("GET", "/mobile/v1/members/classes/summary")
|
254
|
-
return TotalClasses(**data["data"])
|
235
|
+
return BodyCompositionList(data=data["data"])
|
255
236
|
|
256
237
|
async def get_classes(
|
257
238
|
self,
|
@@ -346,6 +327,16 @@ class Otf:
|
|
346
327
|
|
347
328
|
return classes_list
|
348
329
|
|
330
|
+
async def get_total_classes(self) -> TotalClasses:
|
331
|
+
"""Get the member's total classes. This is a simple object reflecting the total number of classes attended,
|
332
|
+
both in-studio and OT Live.
|
333
|
+
|
334
|
+
Returns:
|
335
|
+
TotalClasses: The member's total classes.
|
336
|
+
"""
|
337
|
+
data = await self._default_request("GET", "/mobile/v1/members/classes/summary")
|
338
|
+
return TotalClasses(**data["data"])
|
339
|
+
|
349
340
|
async def book_class(self, class_uuid: str) -> BookClass | typing.Any:
|
350
341
|
"""Book a class by class_uuid.
|
351
342
|
|
@@ -860,16 +851,15 @@ class Otf:
|
|
860
851
|
res = await self._telemetry_request("GET", path, params=params)
|
861
852
|
return TelemetryMaxHr(**res)
|
862
853
|
|
863
|
-
async def get_telemetry(self,
|
864
|
-
"""Get the telemetry for a
|
854
|
+
async def get_telemetry(self, performance_summary_id: str, max_data_points: int = 120) -> Telemetry:
|
855
|
+
"""Get the telemetry for a performance summary.
|
865
856
|
|
866
857
|
This returns an object that contains the max heartrate, start/end bpm for each zone,
|
867
858
|
and a list of telemetry items that contain the heartrate, splat points, calories, and timestamp.
|
868
859
|
|
869
860
|
Args:
|
870
|
-
|
871
|
-
max_data_points (int): The max data points to use for the telemetry. Default is
|
872
|
-
get the max data points from the workout. If the workout is not found, it will default to 120 data points.
|
861
|
+
performance_summary_id (str): The performance summary id.
|
862
|
+
max_data_points (int): The max data points to use for the telemetry. Default is 120.
|
873
863
|
|
874
864
|
Returns:
|
875
865
|
TelemetryItem: The telemetry for the class history.
|
@@ -877,30 +867,10 @@ class Otf:
|
|
877
867
|
"""
|
878
868
|
path = "/v1/performance/summary"
|
879
869
|
|
880
|
-
|
881
|
-
|
882
|
-
params = {"classHistoryUuid": class_history_uuid, "maxDataPoints": max_data_points}
|
870
|
+
params = {"classHistoryUuid": performance_summary_id, "maxDataPoints": max_data_points}
|
883
871
|
res = await self._telemetry_request("GET", path, params=params)
|
884
872
|
return Telemetry(**res)
|
885
873
|
|
886
|
-
async def _get_max_data_points(self, class_history_uuid: str) -> int:
|
887
|
-
"""Get the max data points to use for the telemetry.
|
888
|
-
|
889
|
-
Attempts to get the amount of active time for the workout from the OT Live API. If the workout is not found,
|
890
|
-
it will default to 120 data points. If it is found, it will calculate the amount of data points needed based on
|
891
|
-
the active time. This should amount to a data point per 30 seconds, roughly.
|
892
|
-
|
893
|
-
Args:
|
894
|
-
class_history_uuid (str): The class history UUID.
|
895
|
-
|
896
|
-
Returns:
|
897
|
-
int: The max data points to use.
|
898
|
-
"""
|
899
|
-
workouts = await self.get_workouts()
|
900
|
-
workout = workouts.by_class_history_uuid.get(class_history_uuid)
|
901
|
-
max_data_points = 120 if workout is None else ceil(active_time_to_data_points(workout.active_time))
|
902
|
-
return max_data_points
|
903
|
-
|
904
874
|
# the below do not return any data for me, so I can't test them
|
905
875
|
|
906
876
|
async def _get_member_services(self, active_only: bool = True) -> typing.Any:
|
@@ -934,17 +904,3 @@ class Otf:
|
|
934
904
|
|
935
905
|
data = self._default_request("GET", f"/member/wearables/{self._member_id}/wearable-daily", params=params)
|
936
906
|
return data
|
937
|
-
|
938
|
-
async def get_body_composition_list(self) -> BodyCompositionList:
|
939
|
-
"""Get the member's body composition list.
|
940
|
-
|
941
|
-
Returns:
|
942
|
-
Any: The member's body composition list.
|
943
|
-
"""
|
944
|
-
data = await self._default_request("GET", f"/member/members/{self._member_uuid}/body-composition")
|
945
|
-
|
946
|
-
return BodyCompositionList(data=data["data"])
|
947
|
-
|
948
|
-
|
949
|
-
def active_time_to_data_points(active_time: int) -> float:
|
950
|
-
return active_time / 60 * 2
|
@@ -21,7 +21,6 @@ from .telemetry import Telemetry
|
|
21
21
|
from .telemetry_hr_history import TelemetryHrHistory
|
22
22
|
from .telemetry_max_hr import TelemetryMaxHr
|
23
23
|
from .total_classes import TotalClasses
|
24
|
-
from .workouts import WorkoutList
|
25
24
|
|
26
25
|
__all__ = [
|
27
26
|
"BodyCompositionList",
|
@@ -56,5 +55,4 @@ __all__ = [
|
|
56
55
|
"TelemetryHrHistory",
|
57
56
|
"TelemetryMaxHr",
|
58
57
|
"TotalClasses",
|
59
|
-
"WorkoutList",
|
60
58
|
]
|
@@ -33,7 +33,7 @@ class Studio(OtfItemBase):
|
|
33
33
|
class Class(OtfItemBase):
|
34
34
|
ot_base_class_uuid: str | None = None
|
35
35
|
starts_at_local: str
|
36
|
-
name: str
|
36
|
+
name: str | None = None
|
37
37
|
coach: Coach
|
38
38
|
studio: Studio
|
39
39
|
|
@@ -56,7 +56,7 @@ class Ratings(OtfItemBase):
|
|
56
56
|
|
57
57
|
|
58
58
|
class PerformanceSummaryEntry(OtfItemBase):
|
59
|
-
|
59
|
+
id: str = Field(..., alias="id")
|
60
60
|
details: Details
|
61
61
|
ratable: bool
|
62
62
|
otf_class: Class = Field(..., alias="class")
|
@@ -1,78 +0,0 @@
|
|
1
|
-
from ast import literal_eval
|
2
|
-
from datetime import datetime
|
3
|
-
from typing import Any
|
4
|
-
|
5
|
-
from pydantic import Field, PrivateAttr
|
6
|
-
|
7
|
-
from otf_api.models.base import OtfItemBase
|
8
|
-
|
9
|
-
|
10
|
-
class WorkoutType(OtfItemBase):
|
11
|
-
id: int
|
12
|
-
display_name: str = Field(..., alias="displayName")
|
13
|
-
icon: str
|
14
|
-
|
15
|
-
|
16
|
-
class Workout(OtfItemBase):
|
17
|
-
studio_number: str = Field(..., alias="studioNumber")
|
18
|
-
studio_name: str = Field(..., alias="studioName")
|
19
|
-
class_type: str = Field(..., alias="classType")
|
20
|
-
active_time: int = Field(..., alias="activeTime")
|
21
|
-
coach: str
|
22
|
-
member_uuid: str = Field(..., alias="memberUuId")
|
23
|
-
class_date: datetime = Field(..., alias="classDate")
|
24
|
-
total_calories: int = Field(..., alias="totalCalories")
|
25
|
-
avg_hr: int = Field(..., alias="avgHr")
|
26
|
-
max_hr: int = Field(..., alias="maxHr")
|
27
|
-
avg_percent_hr: int = Field(..., alias="avgPercentHr")
|
28
|
-
max_percent_hr: int = Field(..., alias="maxPercentHr")
|
29
|
-
total_splat_points: int = Field(..., alias="totalSplatPoints")
|
30
|
-
red_zone_time_second: int = Field(..., alias="redZoneTimeSecond")
|
31
|
-
orange_zone_time_second: int = Field(..., alias="orangeZoneTimeSecond")
|
32
|
-
green_zone_time_second: int = Field(..., alias="greenZoneTimeSecond")
|
33
|
-
blue_zone_time_second: int = Field(..., alias="blueZoneTimeSecond")
|
34
|
-
black_zone_time_second: int = Field(..., alias="blackZoneTimeSecond")
|
35
|
-
step_count: int = Field(..., alias="stepCount")
|
36
|
-
class_history_uuid: str = Field(..., alias="classHistoryUuId")
|
37
|
-
class_id: str = Field(..., alias="classId")
|
38
|
-
date_created: datetime = Field(..., alias="dateCreated")
|
39
|
-
date_updated: datetime = Field(..., alias="dateUpdated")
|
40
|
-
is_intro: bool = Field(..., alias="isIntro")
|
41
|
-
is_leader: bool = Field(..., alias="isLeader")
|
42
|
-
member_email: None = Field(..., alias="memberEmail")
|
43
|
-
member_name: str = Field(..., alias="memberName")
|
44
|
-
member_performance_id: str = Field(..., alias="memberPerformanceId")
|
45
|
-
minute_by_minute_hr: list[int] = Field(
|
46
|
-
...,
|
47
|
-
alias="minuteByMinuteHr",
|
48
|
-
description="HR data for each minute of the workout. It is returned as a string literal, so it needs to be "
|
49
|
-
"evaluated to a list. If can't be parsed, it will return an empty list.",
|
50
|
-
)
|
51
|
-
source: str
|
52
|
-
studio_account_uuid: str = Field(..., alias="studioAccountUuId")
|
53
|
-
version: str
|
54
|
-
workout_type: WorkoutType = Field(..., alias="workoutType")
|
55
|
-
_minute_by_minute_raw: str | None = PrivateAttr(None)
|
56
|
-
|
57
|
-
@property
|
58
|
-
def active_time_minutes(self) -> int:
|
59
|
-
"""Get the active time in minutes."""
|
60
|
-
return self.active_time // 60
|
61
|
-
|
62
|
-
def __init__(self, **data: Any):
|
63
|
-
if "minuteByMinuteHr" in data:
|
64
|
-
try:
|
65
|
-
data["minuteByMinuteHr"] = literal_eval(data["minuteByMinuteHr"])
|
66
|
-
except (ValueError, SyntaxError):
|
67
|
-
data["minuteByMinuteHr"] = []
|
68
|
-
|
69
|
-
super().__init__(**data)
|
70
|
-
self._minute_by_minute_raw = data.get("minuteByMinuteHr")
|
71
|
-
|
72
|
-
|
73
|
-
class WorkoutList(OtfItemBase):
|
74
|
-
workouts: list[Workout]
|
75
|
-
|
76
|
-
@property
|
77
|
-
def by_class_history_uuid(self) -> dict[str, Workout]:
|
78
|
-
return {workout.class_history_uuid: workout for workout in self.workouts}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{otf_api-0.4.0 → otf_api-0.5.0}/src/otf_api/models/responses/out_of_studio_workout_history.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|