otf-api 0.4.0__py3-none-any.whl → 0.5.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.
otf_api/__init__.py CHANGED
@@ -6,7 +6,7 @@ from loguru import logger
6
6
  from .api import Otf
7
7
  from .auth import OtfUser
8
8
 
9
- __version__ = "0.4.0"
9
+ __version__ = "0.5.0"
10
10
 
11
11
 
12
12
  __all__ = ["Otf", "OtfUser"]
otf_api/api.py CHANGED
@@ -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 get_workouts(self) -> WorkoutList:
230
- """Get the list of workouts from OT Live.
227
+ async def get_body_composition_list(self) -> BodyCompositionList:
228
+ """Get the member's body composition list.
231
229
 
232
230
  Returns:
233
- WorkoutList: The list of workouts.
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
- res = await self._default_request("GET", "/virtual-class/in-studio-workouts")
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, class_history_uuid: str, max_data_points: int = 0) -> Telemetry:
864
- """Get the telemetry for a class history.
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
- class_history_uuid (str): The class history UUID.
871
- max_data_points (int): The max data points to use for the telemetry. Default is 0, which will attempt to\
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
- max_data_points = max_data_points or await self._get_max_data_points(class_history_uuid)
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
otf_api/auth.py CHANGED
@@ -63,7 +63,8 @@ class OtfCognito(Cognito):
63
63
  @device_key.setter
64
64
  def device_key(self, value: str | None):
65
65
  if not value:
66
- logger.info("Clearing device key")
66
+ if self._device_key:
67
+ logger.info("Clearing device key")
67
68
  self._device_key = value
68
69
  return
69
70
 
@@ -30,7 +30,6 @@ from .responses import (
30
30
  TelemetryHrHistory,
31
31
  TelemetryMaxHr,
32
32
  TotalClasses,
33
- WorkoutList,
34
33
  )
35
34
 
36
35
  __all__ = [
@@ -66,5 +65,4 @@ __all__ = [
66
65
  "TelemetryHrHistory",
67
66
  "TelemetryMaxHr",
68
67
  "TotalClasses",
69
- "WorkoutList",
70
68
  ]
@@ -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
- performance_summary_id: str = Field(..., alias="id")
59
+ id: str = Field(..., alias="id")
60
60
  details: Details
61
61
  ratable: bool
62
62
  otf_class: Class = Field(..., alias="class")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: otf-api
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Python OrangeTheory Fitness API Client
5
5
  License: MIT
6
6
  Author: Jessica Smith
@@ -1,14 +1,14 @@
1
- otf_api/__init__.py,sha256=r_Pbfy17O3PShjKEtmGbzMz77vKm6QIWf7nj1euogiw,237
2
- otf_api/api.py,sha256=4yiYQb_ShBduk0E9JLRs_PzLFEBDIw7c59FFE34BuKo,36749
3
- otf_api/auth.py,sha256=eapyHm768j402iqwgKOr2hpqzztXS8DBp021TSK0Txk,10194
1
+ otf_api/__init__.py,sha256=Co48iPZnr9aWJ_ZXqQwnCvhd1kwgn_wGyPiFWMr5eGs,237
2
+ otf_api/api.py,sha256=QlRHXDwzbB97aCfacb_9FVox-5AuPLMweRQAZkz-EE0,34885
3
+ otf_api/auth.py,sha256=XzwLSi5M3DyG7bE7DmWAzXF2y6fkJyAZxHUA9lpW25M,10231
4
4
  otf_api/cli/__init__.py,sha256=WI-882LPH7Tj_ygDHqE5ehsas_u7m3ulsplS9vXKByk,151
5
5
  otf_api/cli/_utilities.py,sha256=epjEO9S6ag4HgJLXlTpCQXfdVQkqGWyNavp7DjwPL78,1753
6
6
  otf_api/cli/app.py,sha256=88TuMwq3foRr1Cui0V3h0mxNkoANqd6QQifI9CIgLvI,6469
7
7
  otf_api/cli/bookings.py,sha256=wSmZA-03etcL6Tvb1vDSvHZW8EA9CZUgKX6W1pps3Yw,8161
8
8
  otf_api/cli/prompts.py,sha256=iyodQXVa5v9VsrMxw0ob1okGRBDbWCSxhrNEylsOTEQ,5358
9
- otf_api/models/__init__.py,sha256=2Zvf7u1Z3qguDd4PsWeoP_Lma3bk-A7RmYQ4LbPJ9Kg,1464
9
+ otf_api/models/__init__.py,sha256=3GHBOirQA4yu06cgD9pYmCU8u8_F9nxNHeSXDuFpe5A,1428
10
10
  otf_api/models/base.py,sha256=oTDxyliK64GyTNx1bGTd-b9dfVn0r3YPpSycs2qEuIw,7285
11
- otf_api/models/responses/__init__.py,sha256=UdJhkzREux-5DnHE5VSYN0KNKxyDkUkYMPWQpa9Y9qs,2000
11
+ otf_api/models/responses/__init__.py,sha256=xxwz-JwRd0upmI0VNdvInbAm2FOQvPo3pS0SEhWfkI4,1947
12
12
  otf_api/models/responses/body_composition_list.py,sha256=RTC5bQpmMDUKqFl0nGFExdDxfnbOAGoBLWunjpOym80,12193
13
13
  otf_api/models/responses/book_class.py,sha256=bWURKEjLZWPzwu3HNP2zUmHWo7q7h6_z43a9KTST0Ec,15413
14
14
  otf_api/models/responses/bookings.py,sha256=0oQxdKTK-k30GVDKiVxTh0vvPTbrw78sqpQpYL7JnJU,11058
@@ -25,18 +25,17 @@ otf_api/models/responses/member_membership.py,sha256=_z301T9DrdQW9vIgnx_LeZmkRhv
25
25
  otf_api/models/responses/member_purchases.py,sha256=JoTk3hYjsq4rXogVivZxeFaM-j3gIChmIAGVldOU7rE,6085
26
26
  otf_api/models/responses/out_of_studio_workout_history.py,sha256=FwdnmTgFrMtQ8PngsmCv3UroWj3kDnQg6KfGLievoaU,1709
27
27
  otf_api/models/responses/performance_summary_detail.py,sha256=H5yWxGShR4uiXvY2OaniENburTGM7DKQjN7gvF3MG6g,1585
28
- otf_api/models/responses/performance_summary_list.py,sha256=1cbRX4bnLWwy6iYT6NmJXaT2AdZKGJvQMezkvcGLO58,1240
28
+ otf_api/models/responses/performance_summary_list.py,sha256=R__tsXGz5tVX5gfoRoVUNK4UP2pXRoK5jdSyHABsDXs,1234
29
29
  otf_api/models/responses/studio_detail.py,sha256=CJBCsi4SMs_W5nrWE4hfCs1ugJ5t7GrH80hTv7Ie3eg,5007
30
30
  otf_api/models/responses/studio_services.py,sha256=mFDClPtU0HCk5fb19gjGKpt2F8n8kto7sj1pE_l4RdQ,1836
31
31
  otf_api/models/responses/telemetry.py,sha256=8dl8FKLeyb6jtqsZT7XD4JzXBMLlami448-Jt0tFbSY,1663
32
32
  otf_api/models/responses/telemetry_hr_history.py,sha256=vDcLb4wTHVBw8O0mGblUujHfJegkflOCWW-bnTXNCI0,763
33
33
  otf_api/models/responses/telemetry_max_hr.py,sha256=xKxH0fIlOqFyZv8UW98XsxF-GMoIs9gnCTAbu88ZQtg,266
34
34
  otf_api/models/responses/total_classes.py,sha256=WrKkWbq0eK8J0RC4qhZ5kmXnv_ZTDbyzsoRm7XKGlss,288
35
- otf_api/models/responses/workouts.py,sha256=4r6wQVY-yUsI83JYBpSCYhd7I5u-5OLvy1Vd1_gra88,3177
36
35
  otf_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
- otf_api-0.4.0.dist-info/AUTHORS.md,sha256=FcNWMxpe8KDuTq4Qau0SUXsabQwGs9TGnMp1WkXRnj8,123
38
- otf_api-0.4.0.dist-info/LICENSE,sha256=UaPT9ynYigC3nX8n22_rC37n-qmTRKLFaHrtUwF9ktE,1071
39
- otf_api-0.4.0.dist-info/METADATA,sha256=0VCBjMyO3Wrka4Jz0ZwB38zm5KxVOlb-H1ZL6_M64WU,2259
40
- otf_api-0.4.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
41
- otf_api-0.4.0.dist-info/entry_points.txt,sha256=V2jhhfsUo3DeF0CA9HmKrMnvSoOldn9ShIzbApbeHTY,44
42
- otf_api-0.4.0.dist-info/RECORD,,
36
+ otf_api-0.5.0.dist-info/AUTHORS.md,sha256=FcNWMxpe8KDuTq4Qau0SUXsabQwGs9TGnMp1WkXRnj8,123
37
+ otf_api-0.5.0.dist-info/LICENSE,sha256=UaPT9ynYigC3nX8n22_rC37n-qmTRKLFaHrtUwF9ktE,1071
38
+ otf_api-0.5.0.dist-info/METADATA,sha256=wE1IJvQLG8KVsIiQJjRoIz80amFtCv5zezo4j4tPqEg,2259
39
+ otf_api-0.5.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
40
+ otf_api-0.5.0.dist-info/entry_points.txt,sha256=V2jhhfsUo3DeF0CA9HmKrMnvSoOldn9ShIzbApbeHTY,44
41
+ otf_api-0.5.0.dist-info/RECORD,,
@@ -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}