otf-api 0.9.1__tar.gz → 0.9.3__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.9.1 → otf_api-0.9.3}/PKG-INFO +1 -1
- {otf_api-0.9.1 → otf_api-0.9.3}/pyproject.toml +1 -1
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/__init__.py +1 -1
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/api.py +134 -4
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/exceptions.py +11 -1
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/performance_summary_detail.py +7 -2
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/performance_summary_list.py +1 -1
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/utils.py +1 -1
- {otf_api-0.9.1 → otf_api-0.9.3}/LICENSE +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/README.md +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/auth/__init__.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/auth/auth.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/auth/user.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/auth/utils.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/filters.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/logging.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/__init__.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/base.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/body_composition_list.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/bookings.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/challenge_tracker_content.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/challenge_tracker_detail.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/classes.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/enums.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/lifetime_stats.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/member_detail.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/member_membership.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/member_purchases.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/mixins.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/notifications.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/out_of_studio_workout_history.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/studio_detail.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/studio_services.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/models/telemetry.py +0 -0
- {otf_api-0.9.1 → otf_api-0.9.3}/src/otf_api/py.typed +0 -0
@@ -87,9 +87,7 @@ class Otf:
|
|
87
87
|
LOGGER.exception(f"Response: {response.text}")
|
88
88
|
raise
|
89
89
|
except httpx.HTTPStatusError as e:
|
90
|
-
|
91
|
-
LOGGER.exception(f"Response: {response.text}")
|
92
|
-
raise exc.OtfRequestError("Error making request", response=response, request=request)
|
90
|
+
raise exc.OtfRequestError("Error making request", e, response=response, request=request)
|
93
91
|
except Exception as e:
|
94
92
|
LOGGER.exception(f"Error making request: {e}")
|
95
93
|
raise
|
@@ -110,7 +108,7 @@ class Otf:
|
|
110
108
|
and not (resp["Status"] >= 200 and resp["Status"] <= 299)
|
111
109
|
):
|
112
110
|
LOGGER.error(f"Error making request: {resp}")
|
113
|
-
raise exc.OtfRequestError("Error making request", response=response, request=request)
|
111
|
+
raise exc.OtfRequestError("Error making request", None, response=response, request=request)
|
114
112
|
|
115
113
|
return resp
|
116
114
|
|
@@ -966,6 +964,7 @@ class Otf:
|
|
966
964
|
|
967
965
|
path = f"/v1/performance-summaries/{performance_summary_id}"
|
968
966
|
res = self._performance_summary_request("GET", path)
|
967
|
+
|
969
968
|
if res is None:
|
970
969
|
raise exc.ResourceNotFoundError(f"Performance summary {performance_summary_id} not found")
|
971
970
|
|
@@ -1147,6 +1146,137 @@ class Otf:
|
|
1147
1146
|
|
1148
1147
|
return models.MemberDetail(**res["data"])
|
1149
1148
|
|
1149
|
+
def _rate_class(
|
1150
|
+
self,
|
1151
|
+
class_uuid: str,
|
1152
|
+
class_history_uuid: str,
|
1153
|
+
class_rating: Literal[0, 1, 2, 3],
|
1154
|
+
coach_rating: Literal[0, 1, 2, 3],
|
1155
|
+
) -> models.PerformanceSummaryEntry:
|
1156
|
+
"""Rate a class and coach. A simpler method is provided in `rate_class_from_performance_summary`.
|
1157
|
+
|
1158
|
+
|
1159
|
+
The class rating must be between 0 and 4.
|
1160
|
+
0 is the same as dismissing the prompt to rate the class/coach in the app.
|
1161
|
+
1 through 3 is a range from bad to good.
|
1162
|
+
|
1163
|
+
Args:
|
1164
|
+
class_uuid (str): The class UUID.
|
1165
|
+
class_history_uuid (str): The performance summary ID.
|
1166
|
+
class_rating (int): The class rating. Must be 0, 1, 2, or 3.
|
1167
|
+
coach_rating (int): The coach rating. Must be 0, 1, 2, or 3.
|
1168
|
+
|
1169
|
+
Returns:
|
1170
|
+
PerformanceSummaryEntry: The updated performance summary entry.
|
1171
|
+
"""
|
1172
|
+
|
1173
|
+
# com/orangetheoryfitness/fragment/rating/RateStatus.java
|
1174
|
+
|
1175
|
+
# we convert these to the new values that the app uses
|
1176
|
+
# mainly because we don't want to cause any issues with the API and/or with OTF corporate
|
1177
|
+
# wondering where the old values are coming from
|
1178
|
+
|
1179
|
+
COACH_RATING_MAP = {0: 0, 1: 16, 2: 17, 3: 18}
|
1180
|
+
CLASS_RATING_MAP = {0: 0, 1: 19, 2: 20, 3: 21}
|
1181
|
+
|
1182
|
+
if class_rating not in CLASS_RATING_MAP:
|
1183
|
+
raise ValueError(f"Invalid class rating {class_rating}")
|
1184
|
+
|
1185
|
+
if coach_rating not in COACH_RATING_MAP:
|
1186
|
+
raise ValueError(f"Invalid coach rating {coach_rating}")
|
1187
|
+
|
1188
|
+
body_class_rating = CLASS_RATING_MAP[class_rating]
|
1189
|
+
body_coach_rating = COACH_RATING_MAP[coach_rating]
|
1190
|
+
|
1191
|
+
body = {
|
1192
|
+
"classUUId": class_uuid,
|
1193
|
+
"otBeatClassHistoryUUId": class_history_uuid,
|
1194
|
+
"classRating": body_class_rating,
|
1195
|
+
"coachRating": body_coach_rating,
|
1196
|
+
}
|
1197
|
+
|
1198
|
+
try:
|
1199
|
+
self._default_request("POST", "/mobile/v1/members/classes/ratings", json=body)
|
1200
|
+
except exc.OtfRequestError as e:
|
1201
|
+
if e.response.status_code == 403:
|
1202
|
+
raise exc.AlreadyRatedError(f"Performance summary {class_history_uuid} is already rated.") from None
|
1203
|
+
raise
|
1204
|
+
|
1205
|
+
return self._get_performance_summary_entry_from_id(class_history_uuid)
|
1206
|
+
|
1207
|
+
def _get_performance_summary_entry_from_id(self, class_history_uuid: str) -> models.PerformanceSummaryEntry:
|
1208
|
+
"""Get a performance summary entry from the ID.
|
1209
|
+
|
1210
|
+
This is a helper function to compensate for the fact that a PerformanceSummaryDetail object does not contain
|
1211
|
+
the class UUID, which is required to rate the class. It will also be used to return an updated performance
|
1212
|
+
summary entry after rating a class.
|
1213
|
+
|
1214
|
+
Args:
|
1215
|
+
class_history_uuid (str): The performance summary ID.
|
1216
|
+
|
1217
|
+
Returns:
|
1218
|
+
PerformanceSummaryEntry: The performance summary entry.
|
1219
|
+
|
1220
|
+
Raises:
|
1221
|
+
ResourceNotFoundError: If the performance summary is not found.
|
1222
|
+
"""
|
1223
|
+
|
1224
|
+
# try going in as small of increments as possible, assuming that the rating request
|
1225
|
+
# will be for a recent class
|
1226
|
+
for limit in [5, 20, 60, 100]:
|
1227
|
+
summaries = self.get_performance_summaries(limit)
|
1228
|
+
summary = next((s for s in summaries if s.class_history_uuid == class_history_uuid), None)
|
1229
|
+
|
1230
|
+
if summary:
|
1231
|
+
return summary
|
1232
|
+
|
1233
|
+
raise exc.ResourceNotFoundError(f"Performance summary {class_history_uuid} not found.")
|
1234
|
+
|
1235
|
+
def rate_class_from_performance_summary(
|
1236
|
+
self,
|
1237
|
+
perf_summary: models.PerformanceSummaryEntry | models.PerformanceSummaryDetail,
|
1238
|
+
class_rating: Literal[0, 1, 2, 3],
|
1239
|
+
coach_rating: Literal[0, 1, 2, 3],
|
1240
|
+
) -> models.PerformanceSummaryEntry:
|
1241
|
+
"""Rate a class and coach. The class rating must be 0, 1, 2, or 3. 0 is the same as dismissing the prompt to
|
1242
|
+
rate the class/coach. 1 - 3 is a range from bad to good.
|
1243
|
+
|
1244
|
+
Args:
|
1245
|
+
perf_summary (PerformanceSummaryEntry): The performance summary entry to rate.
|
1246
|
+
class_rating (int): The class rating. Must be 0, 1, 2, or 3.
|
1247
|
+
coach_rating (int): The coach rating. Must be 0, 1, 2, or 3.
|
1248
|
+
|
1249
|
+
Returns:
|
1250
|
+
PerformanceSummaryEntry: The updated performance summary entry.
|
1251
|
+
|
1252
|
+
Raises:
|
1253
|
+
ValueError: If `perf_summary` is not a PerformanceSummaryEntry.
|
1254
|
+
AlreadyRatedError: If the performance summary is already rated.
|
1255
|
+
ClassNotRatableError: If the performance summary is not rateable.
|
1256
|
+
ValueError: If the performance summary does not have an associated class.
|
1257
|
+
"""
|
1258
|
+
|
1259
|
+
if isinstance(perf_summary, models.PerformanceSummaryDetail):
|
1260
|
+
perf_summary = self._get_performance_summary_entry_from_id(perf_summary.class_history_uuid)
|
1261
|
+
|
1262
|
+
if not isinstance(perf_summary, models.PerformanceSummaryEntry):
|
1263
|
+
raise ValueError(f"`perf_summary` must be a PerformanceSummaryEntry, got {type(perf_summary)}")
|
1264
|
+
|
1265
|
+
if perf_summary.is_rated:
|
1266
|
+
raise exc.AlreadyRatedError(f"Performance summary {perf_summary.class_history_uuid} is already rated.")
|
1267
|
+
|
1268
|
+
if not perf_summary.ratable:
|
1269
|
+
raise exc.ClassNotRatableError(f"Performance summary {perf_summary.class_history_uuid} is not rateable.")
|
1270
|
+
|
1271
|
+
if not perf_summary.otf_class or not perf_summary.otf_class.class_uuid:
|
1272
|
+
raise ValueError(
|
1273
|
+
f"Performance summary {perf_summary.class_history_uuid} does not have an associated class."
|
1274
|
+
)
|
1275
|
+
|
1276
|
+
return self._rate_class(
|
1277
|
+
perf_summary.otf_class.class_uuid, perf_summary.class_history_uuid, class_rating, coach_rating
|
1278
|
+
)
|
1279
|
+
|
1150
1280
|
# the below do not return any data for me, so I can't test them
|
1151
1281
|
|
1152
1282
|
def _get_member_services(self, active_only: bool = True) -> Any:
|
@@ -8,11 +8,13 @@ class OtfException(Exception):
|
|
8
8
|
class OtfRequestError(OtfException):
|
9
9
|
"""Raised when an error occurs while making a request to the OTF API."""
|
10
10
|
|
11
|
+
original_exception: Exception
|
11
12
|
response: Response
|
12
13
|
request: Request
|
13
14
|
|
14
|
-
def __init__(self, message: str, response: Response, request: Request):
|
15
|
+
def __init__(self, message: str, original_exception: Exception | None, response: Response, request: Request):
|
15
16
|
super().__init__(message)
|
17
|
+
self.original_exception = original_exception
|
16
18
|
self.response = response
|
17
19
|
self.request = request
|
18
20
|
|
@@ -49,3 +51,11 @@ class BookingNotFoundError(OtfException):
|
|
49
51
|
|
50
52
|
class ResourceNotFoundError(OtfException):
|
51
53
|
"""Raised when a resource is not found."""
|
54
|
+
|
55
|
+
|
56
|
+
class AlreadyRatedError(OtfException):
|
57
|
+
"""Raised when attempting to rate a class that is already rated."""
|
58
|
+
|
59
|
+
|
60
|
+
class ClassNotRatableError(OtfException):
|
61
|
+
"""Raised when attempting to rate a class that is not ratable."""
|
@@ -68,11 +68,16 @@ class Rower(BaseEquipment):
|
|
68
68
|
|
69
69
|
|
70
70
|
class PerformanceSummaryDetail(OtfItemBase):
|
71
|
-
|
71
|
+
class_history_uuid: str = Field(..., alias="id")
|
72
72
|
class_name: str | None = Field(None, alias=AliasPath("class", "name"))
|
73
73
|
class_starts_at: datetime | None = Field(None, alias=AliasPath("class", "starts_at_local"))
|
74
74
|
|
75
|
-
ratable: bool | None =
|
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
|
+
)
|
76
81
|
calories_burned: int | None = Field(None, alias=AliasPath("details", "calories_burned"))
|
77
82
|
splat_points: int | None = Field(None, alias=AliasPath("details", "splat_points"))
|
78
83
|
step_count: int | None = Field(None, alias=AliasPath("details", "step_count"))
|
@@ -33,7 +33,7 @@ class ClassRating(OtfItemBase):
|
|
33
33
|
|
34
34
|
|
35
35
|
class PerformanceSummaryEntry(OtfItemBase):
|
36
|
-
|
36
|
+
class_history_uuid: str = Field(..., alias="id")
|
37
37
|
calories_burned: int | None = Field(None, alias=AliasPath("details", "calories_burned"))
|
38
38
|
splat_points: int | None = Field(None, alias=AliasPath("details", "splat_points"))
|
39
39
|
step_count: int | None = Field(None, alias=AliasPath("details", "step_count"))
|
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
|
File without changes
|