otf-api 0.10.0__tar.gz → 0.10.1__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.10.0 → otf_api-0.10.1}/PKG-INFO +1 -1
- {otf_api-0.10.0 → otf_api-0.10.1}/pyproject.toml +1 -1
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/__init__.py +1 -1
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/api.py +46 -26
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/enums.py +1 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/performance_summary.py +2 -1
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/studio_detail.py +3 -3
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/telemetry.py +4 -1
- {otf_api-0.10.0 → otf_api-0.10.1}/LICENSE +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/README.md +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/auth/__init__.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/auth/auth.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/auth/user.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/auth/utils.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/exceptions.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/filters.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/logging.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/__init__.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/base.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/body_composition_list.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/bookings.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/challenge_tracker_content.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/challenge_tracker_detail.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/classes.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/lifetime_stats.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/member_detail.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/member_membership.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/member_purchases.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/mixins.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/notifications.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/out_of_studio_workout_history.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/models/studio_services.py +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/py.typed +0 -0
- {otf_api-0.10.0 → otf_api-0.10.1}/src/otf_api/utils.py +0 -0
@@ -1,7 +1,6 @@
|
|
1
1
|
import atexit
|
2
2
|
import contextlib
|
3
3
|
import functools
|
4
|
-
import warnings
|
5
4
|
from concurrent.futures import ThreadPoolExecutor
|
6
5
|
from copy import deepcopy
|
7
6
|
from datetime import date, datetime, timedelta
|
@@ -177,9 +176,10 @@ class Otf:
|
|
177
176
|
"""Retrieve raw member membership details."""
|
178
177
|
return self._default_request("GET", f"/member/members/{self.member_uuid}/memberships")
|
179
178
|
|
180
|
-
def _get_performance_summaries_raw(self) -> dict:
|
179
|
+
def _get_performance_summaries_raw(self, limit: int | None = None) -> dict:
|
181
180
|
"""Retrieve raw performance summaries data."""
|
182
|
-
|
181
|
+
params = {"limit": limit} if limit else {}
|
182
|
+
return self._performance_summary_request("GET", "/v1/performance-summaries", params=params)
|
183
183
|
|
184
184
|
def _get_performance_summary_raw(self, performance_summary_id: str) -> dict:
|
185
185
|
"""Retrieve raw performance summary data."""
|
@@ -321,14 +321,16 @@ class Otf:
|
|
321
321
|
},
|
322
322
|
)
|
323
323
|
|
324
|
-
def _rate_class_raw(
|
324
|
+
def _rate_class_raw(
|
325
|
+
self, class_uuid: str, performance_summary_id: str, class_rating: int, coach_rating: int
|
326
|
+
) -> dict:
|
325
327
|
"""Retrieve raw response from rating a class and coach."""
|
326
328
|
return self._default_request(
|
327
329
|
"POST",
|
328
330
|
"/mobile/v1/members/classes/ratings",
|
329
331
|
json={
|
330
332
|
"classUUId": class_uuid,
|
331
|
-
"otBeatClassHistoryUUId":
|
333
|
+
"otBeatClassHistoryUUId": performance_summary_id,
|
332
334
|
"classRating": class_rating,
|
333
335
|
"coachRating": coach_rating,
|
334
336
|
},
|
@@ -919,6 +921,8 @@ class Otf:
|
|
919
921
|
"""Get detailed information about a specific studio. If no studio UUID is provided, it will default to the
|
920
922
|
user's home studio.
|
921
923
|
|
924
|
+
If the studio is not found, it will return a StudioDetail object with default values.
|
925
|
+
|
922
926
|
Args:
|
923
927
|
studio_uuid (str, optional): The studio UUID to get detailed information about.
|
924
928
|
|
@@ -926,7 +930,11 @@ class Otf:
|
|
926
930
|
StudioDetail: Detailed information about the studio.
|
927
931
|
"""
|
928
932
|
studio_uuid = studio_uuid or self.home_studio_uuid
|
929
|
-
|
933
|
+
|
934
|
+
try:
|
935
|
+
res = self._get_studio_detail_raw(studio_uuid)
|
936
|
+
except exc.ResourceNotFoundError:
|
937
|
+
return models.StudioDetail(studioUUId=studio_uuid, studioName="Studio Not Found", studioStatus="Unknown")
|
930
938
|
|
931
939
|
return models.StudioDetail(**res["data"])
|
932
940
|
|
@@ -1103,9 +1111,12 @@ class Otf:
|
|
1103
1111
|
return models.FitnessBenchmark(**data["Dto"][0])
|
1104
1112
|
|
1105
1113
|
@cached(cache=TTLCache(maxsize=1024, ttl=600))
|
1106
|
-
def get_performance_summaries_dict(self) -> dict[str, models.PerformanceSummary]:
|
1114
|
+
def get_performance_summaries_dict(self, limit: int | None = None) -> dict[str, models.PerformanceSummary]:
|
1107
1115
|
"""Get a dictionary of performance summaries for the authenticated user.
|
1108
1116
|
|
1117
|
+
Args:
|
1118
|
+
limit (int | None): The maximum number of entries to return. Default is None.
|
1119
|
+
|
1109
1120
|
Returns:
|
1110
1121
|
dict[str, PerformanceSummary]: A dictionary of performance summaries, keyed by class history UUID.
|
1111
1122
|
|
@@ -1115,7 +1126,7 @@ class Otf:
|
|
1115
1126
|
|
1116
1127
|
"""
|
1117
1128
|
|
1118
|
-
items = self._get_performance_summaries_raw()["items"]
|
1129
|
+
items = self._get_performance_summaries_raw(limit=limit)["items"]
|
1119
1130
|
|
1120
1131
|
distinct_studio_ids = set([rec["class"]["studio"]["id"] for rec in items])
|
1121
1132
|
perf_summary_ids = set([rec["id"] for rec in items])
|
@@ -1133,7 +1144,7 @@ class Otf:
|
|
1133
1144
|
item["detail"] = perf_summary_dict[item["id"]]
|
1134
1145
|
|
1135
1146
|
entries = [models.PerformanceSummary(**item) for item in items]
|
1136
|
-
entries_dict = {entry.
|
1147
|
+
entries_dict = {entry.performance_summary_id: entry for entry in entries}
|
1137
1148
|
|
1138
1149
|
return entries_dict
|
1139
1150
|
|
@@ -1141,7 +1152,7 @@ class Otf:
|
|
1141
1152
|
"""Get a list of all performance summaries for the authenticated user.
|
1142
1153
|
|
1143
1154
|
Args:
|
1144
|
-
limit (int | None): The maximum number of entries to return. Default is None.
|
1155
|
+
limit (int | None): The maximum number of entries to return. Default is None.
|
1145
1156
|
|
1146
1157
|
Returns:
|
1147
1158
|
list[PerformanceSummary]: A list of performance summaries.
|
@@ -1152,26 +1163,33 @@ class Otf:
|
|
1152
1163
|
|
1153
1164
|
"""
|
1154
1165
|
|
1155
|
-
|
1156
|
-
warnings.warn("Limit is deprecated and will be removed in a future version.", DeprecationWarning)
|
1157
|
-
|
1158
|
-
records = list(self.get_performance_summaries_dict().values())
|
1166
|
+
records = list(self.get_performance_summaries_dict(limit=limit).values())
|
1159
1167
|
|
1160
1168
|
sorted_records = sorted(records, key=lambda x: x.otf_class.starts_at, reverse=True)
|
1161
1169
|
|
1162
1170
|
return sorted_records
|
1163
1171
|
|
1164
|
-
def get_performance_summary(
|
1172
|
+
def get_performance_summary(
|
1173
|
+
self, performance_summary_id: str, limit: int | None = None
|
1174
|
+
) -> models.PerformanceSummary:
|
1165
1175
|
"""Get performance summary for a given workout.
|
1166
1176
|
|
1177
|
+
Note: Due to the way the OTF API is set up, we have to call both the list and the get endpoints. By
|
1178
|
+
default this will call the list endpoint with no limit, in order to ensure that the performance summary
|
1179
|
+
is returned if it exists. This could result in a lot of requests, so you also have the option to provide
|
1180
|
+
a limit to only fetch a certain number of performance summaries.
|
1181
|
+
|
1167
1182
|
Args:
|
1168
1183
|
performance_summary_id (str): The ID of the performance summary to retrieve.
|
1169
1184
|
|
1170
1185
|
Returns:
|
1171
1186
|
PerformanceSummary: The performance summary.
|
1187
|
+
|
1188
|
+
Raises:
|
1189
|
+
ResourceNotFoundError: If the performance_summary_id is not in the list of performance summaries.
|
1172
1190
|
"""
|
1173
1191
|
|
1174
|
-
perf_summary = self.get_performance_summaries_dict().get(performance_summary_id)
|
1192
|
+
perf_summary = self.get_performance_summaries_dict(limit=limit).get(performance_summary_id)
|
1175
1193
|
|
1176
1194
|
if perf_summary is None:
|
1177
1195
|
raise exc.ResourceNotFoundError(f"Performance summary {performance_summary_id} not found")
|
@@ -1180,7 +1198,7 @@ class Otf:
|
|
1180
1198
|
|
1181
1199
|
@functools.lru_cache(maxsize=1024)
|
1182
1200
|
def _get_performancy_summary_detail(self, performance_summary_id: str) -> dict[str, Any]:
|
1183
|
-
"""Get the details for a performance summary.
|
1201
|
+
"""Get the details for a performance summary. Generally should not be called directly. This
|
1184
1202
|
|
1185
1203
|
Args:
|
1186
1204
|
performance_summary_id (str): The performance summary ID.
|
@@ -1350,7 +1368,7 @@ class Otf:
|
|
1350
1368
|
def _rate_class(
|
1351
1369
|
self,
|
1352
1370
|
class_uuid: str,
|
1353
|
-
|
1371
|
+
performance_summary_id: str,
|
1354
1372
|
class_rating: Literal[0, 1, 2, 3],
|
1355
1373
|
coach_rating: Literal[0, 1, 2, 3],
|
1356
1374
|
) -> models.PerformanceSummary:
|
@@ -1363,7 +1381,7 @@ class Otf:
|
|
1363
1381
|
|
1364
1382
|
Args:
|
1365
1383
|
class_uuid (str): The class UUID.
|
1366
|
-
|
1384
|
+
performance_summary_id (str): The performance summary ID.
|
1367
1385
|
class_rating (int): The class rating. Must be 0, 1, 2, or 3.
|
1368
1386
|
coach_rating (int): The coach rating. Must be 0, 1, 2, or 3.
|
1369
1387
|
|
@@ -1390,10 +1408,10 @@ class Otf:
|
|
1390
1408
|
body_coach_rating = COACH_RATING_MAP[coach_rating]
|
1391
1409
|
|
1392
1410
|
try:
|
1393
|
-
self._rate_class_raw(class_uuid,
|
1411
|
+
self._rate_class_raw(class_uuid, performance_summary_id, body_class_rating, body_coach_rating)
|
1394
1412
|
except exc.OtfRequestError as e:
|
1395
1413
|
if e.response.status_code == 403:
|
1396
|
-
raise exc.AlreadyRatedError(f"Performance summary {
|
1414
|
+
raise exc.AlreadyRatedError(f"Performance summary {performance_summary_id} is already rated.") from None
|
1397
1415
|
raise
|
1398
1416
|
|
1399
1417
|
# we have to clear the cache after rating a class, otherwise we will get back the same data
|
@@ -1403,7 +1421,7 @@ class Otf:
|
|
1403
1421
|
# NOTE: the individual perf summary endpoint does not have rating data, so it's cache is not cleared
|
1404
1422
|
self.get_performance_summaries_dict.cache_clear()
|
1405
1423
|
|
1406
|
-
return self.get_performance_summary(
|
1424
|
+
return self.get_performance_summary(performance_summary_id)
|
1407
1425
|
|
1408
1426
|
def rate_class_from_performance_summary(
|
1409
1427
|
self,
|
@@ -1429,18 +1447,20 @@ class Otf:
|
|
1429
1447
|
"""
|
1430
1448
|
|
1431
1449
|
if perf_summary.is_rated:
|
1432
|
-
raise exc.AlreadyRatedError(f"Performance summary {perf_summary.
|
1450
|
+
raise exc.AlreadyRatedError(f"Performance summary {perf_summary.performance_summary_id} is already rated.")
|
1433
1451
|
|
1434
1452
|
if not perf_summary.ratable:
|
1435
|
-
raise exc.ClassNotRatableError(
|
1453
|
+
raise exc.ClassNotRatableError(
|
1454
|
+
f"Performance summary {perf_summary.performance_summary_id} is not rateable."
|
1455
|
+
)
|
1436
1456
|
|
1437
1457
|
if not perf_summary.otf_class or not perf_summary.otf_class.class_uuid:
|
1438
1458
|
raise ValueError(
|
1439
|
-
f"Performance summary {perf_summary.
|
1459
|
+
f"Performance summary {perf_summary.performance_summary_id} does not have an associated class."
|
1440
1460
|
)
|
1441
1461
|
|
1442
1462
|
return self._rate_class(
|
1443
|
-
perf_summary.otf_class.class_uuid, perf_summary.
|
1463
|
+
perf_summary.otf_class.class_uuid, perf_summary.performance_summary_id, class_rating, coach_rating
|
1444
1464
|
)
|
1445
1465
|
|
1446
1466
|
# the below do not return any data for me, so I can't test them
|
@@ -105,7 +105,8 @@ class PerformanceSummary(OtfItemBase):
|
|
105
105
|
|
106
106
|
"""
|
107
107
|
|
108
|
-
|
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")
|
109
110
|
ratable: bool | None = None
|
110
111
|
otf_class: Class | None = Field(None, alias="class")
|
111
112
|
coach: str | None = Field(None, alias=AliasPath("class", "coach", "first_name"))
|
@@ -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)
|
@@ -27,7 +27,7 @@ class StudioDetail(OtfItemBase):
|
|
27
27
|
exclude=True,
|
28
28
|
repr=False,
|
29
29
|
)
|
30
|
-
location: StudioLocation = Field(..., alias="studioLocation")
|
30
|
+
location: StudioLocation = Field(..., alias="studioLocation", default_factory=StudioLocation)
|
31
31
|
name: str | None = Field(None, alias="studioName")
|
32
32
|
status: StudioStatus | None = Field(
|
33
33
|
None, alias="studioStatus", description="Active, Temporarily Closed, Coming Soon"
|
@@ -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
|
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
|
File without changes
|
File without changes
|
File without changes
|