otf-api 0.9.0__py3-none-any.whl → 0.9.2__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 +222 -12
- otf_api/exceptions.py +11 -1
- otf_api/models/__init__.py +3 -0
- otf_api/models/notifications.py +17 -0
- otf_api/models/performance_summary_detail.py +7 -2
- otf_api/models/performance_summary_list.py +1 -1
- {otf_api-0.9.0.dist-info → otf_api-0.9.2.dist-info}/METADATA +14 -5
- {otf_api-0.9.0.dist-info → otf_api-0.9.2.dist-info}/RECORD +11 -10
- {otf_api-0.9.0.dist-info → otf_api-0.9.2.dist-info}/LICENSE +0 -0
- {otf_api-0.9.0.dist-info → otf_api-0.9.2.dist-info}/WHEEL +0 -0
otf_api/__init__.py
CHANGED
otf_api/api.py
CHANGED
@@ -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
|
|
@@ -1007,14 +1006,58 @@ class Otf:
|
|
1007
1006
|
res = self._telemetry_request("GET", path, params=params)
|
1008
1007
|
return models.Telemetry(**res)
|
1009
1008
|
|
1010
|
-
def get_sms_notification_settings(self):
|
1009
|
+
def get_sms_notification_settings(self) -> models.SmsNotificationSettings:
|
1010
|
+
"""Get the member's SMS notification settings.
|
1011
|
+
|
1012
|
+
Returns:
|
1013
|
+
SmsNotificationSettings: The member's SMS notification settings.
|
1014
|
+
"""
|
1011
1015
|
res = self._default_request("GET", url="/sms/v1/preferences", params={"phoneNumber": self.member.phone_number})
|
1012
1016
|
|
1013
|
-
return res["data"]
|
1017
|
+
return models.SmsNotificationSettings(**res["data"])
|
1018
|
+
|
1019
|
+
def update_sms_notification_settings(
|
1020
|
+
self, promotional_enabled: bool | None = None, transactional_enabled: bool | None = None
|
1021
|
+
) -> models.SmsNotificationSettings:
|
1022
|
+
"""Update the member's SMS notification settings. Arguments not provided will be left unchanged.
|
1023
|
+
|
1024
|
+
Args:
|
1025
|
+
promotional_enabled (bool | None): Whether to enable promotional SMS notifications.
|
1026
|
+
transactional_enabled (bool | None): Whether to enable transactional SMS notifications.
|
1027
|
+
|
1028
|
+
Returns:
|
1029
|
+
SmsNotificationSettings: The updated SMS notification settings.
|
1014
1030
|
|
1015
|
-
|
1031
|
+
Warning:
|
1032
|
+
---
|
1033
|
+
This endpoint seems to accept almost anything, converting values to truthy/falsey and
|
1034
|
+
updating the settings accordingly. The one error I've gotten is with -1
|
1035
|
+
|
1036
|
+
```
|
1037
|
+
ERROR - Response:
|
1038
|
+
{
|
1039
|
+
"code": "ER_WARN_DATA_OUT_OF_RANGE",
|
1040
|
+
"message": "An unexpected server error occurred, please try again.",
|
1041
|
+
"details": [
|
1042
|
+
{
|
1043
|
+
"message": "ER_WARN_DATA_OUT_OF_RANGE: Out of range value for column 'IsPromotionalSMSOptIn' at row 1",
|
1044
|
+
"additionalInfo": ""
|
1045
|
+
}
|
1046
|
+
]
|
1047
|
+
}
|
1048
|
+
```
|
1049
|
+
"""
|
1016
1050
|
url = "/sms/v1/preferences"
|
1017
1051
|
|
1052
|
+
current_settings = self.get_sms_notification_settings()
|
1053
|
+
|
1054
|
+
promotional_enabled = (
|
1055
|
+
promotional_enabled if promotional_enabled is not None else current_settings.is_promotional_sms_opt_in
|
1056
|
+
)
|
1057
|
+
transactional_enabled = (
|
1058
|
+
transactional_enabled if transactional_enabled is not None else current_settings.is_transactional_sms_opt_in
|
1059
|
+
)
|
1060
|
+
|
1018
1061
|
body = {
|
1019
1062
|
"promosms": promotional_enabled,
|
1020
1063
|
"source": "OTF",
|
@@ -1022,11 +1065,45 @@ class Otf:
|
|
1022
1065
|
"phoneNumber": self.member.phone_number,
|
1023
1066
|
}
|
1024
1067
|
|
1025
|
-
|
1068
|
+
self._default_request("POST", url, json=body)
|
1069
|
+
|
1070
|
+
# the response returns nothing useful, so we just query the settings again
|
1071
|
+
new_settings = self.get_sms_notification_settings()
|
1072
|
+
return new_settings
|
1073
|
+
|
1074
|
+
def get_email_notification_settings(self) -> models.EmailNotificationSettings:
|
1075
|
+
"""Get the member's email notification settings.
|
1026
1076
|
|
1027
|
-
|
1077
|
+
Returns:
|
1078
|
+
EmailNotificationSettings: The member's email notification settings.
|
1079
|
+
"""
|
1080
|
+
res = self._default_request("GET", url="/otfmailing/v2/preferences", params={"email": self.member.email})
|
1081
|
+
|
1082
|
+
return models.EmailNotificationSettings(**res["data"])
|
1083
|
+
|
1084
|
+
def update_email_notification_settings(
|
1085
|
+
self, promotional_enabled: bool | None = None, transactional_enabled: bool | None = None
|
1086
|
+
) -> models.EmailNotificationSettings:
|
1087
|
+
"""Update the member's email notification settings. Arguments not provided will be left unchanged.
|
1088
|
+
|
1089
|
+
Args:
|
1090
|
+
promotional_enabled (bool | None): Whether to enable promotional email notifications.
|
1091
|
+
transactional_enabled (bool | None): Whether to enable transactional email notifications.
|
1092
|
+
|
1093
|
+
Returns:
|
1094
|
+
EmailNotificationSettings: The updated email notification settings.
|
1095
|
+
"""
|
1096
|
+
current_settings = self.get_email_notification_settings()
|
1097
|
+
|
1098
|
+
promotional_enabled = (
|
1099
|
+
promotional_enabled if promotional_enabled is not None else current_settings.is_promotional_email_opt_in
|
1100
|
+
)
|
1101
|
+
transactional_enabled = (
|
1102
|
+
transactional_enabled
|
1103
|
+
if transactional_enabled is not None
|
1104
|
+
else current_settings.is_transactional_email_opt_in
|
1105
|
+
)
|
1028
1106
|
|
1029
|
-
def update_email_notification_settings(self, promotional_enabled: bool, transactional_enabled: bool):
|
1030
1107
|
body = {
|
1031
1108
|
"promotionalEmail": promotional_enabled,
|
1032
1109
|
"source": "OTF",
|
@@ -1034,9 +1111,11 @@ class Otf:
|
|
1034
1111
|
"email": self.member.email,
|
1035
1112
|
}
|
1036
1113
|
|
1037
|
-
|
1114
|
+
self._default_request("POST", "/otfmailing/v2/preferences", json=body)
|
1038
1115
|
|
1039
|
-
|
1116
|
+
# the response returns nothing useful, so we just query the settings again
|
1117
|
+
new_settings = self.get_email_notification_settings()
|
1118
|
+
return new_settings
|
1040
1119
|
|
1041
1120
|
def update_member_name(self, first_name: str | None = None, last_name: str | None = None) -> models.MemberDetail:
|
1042
1121
|
"""Update the member's name. Will return the original member details if no names are provided.
|
@@ -1067,6 +1146,137 @@ class Otf:
|
|
1067
1146
|
|
1068
1147
|
return models.MemberDetail(**res["data"])
|
1069
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
|
+
|
1070
1280
|
# the below do not return any data for me, so I can't test them
|
1071
1281
|
|
1072
1282
|
def _get_member_services(self, active_only: bool = True) -> Any:
|
otf_api/exceptions.py
CHANGED
@@ -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."""
|
otf_api/models/__init__.py
CHANGED
@@ -8,6 +8,7 @@ from .lifetime_stats import StatsResponse, TimeStats
|
|
8
8
|
from .member_detail import MemberDetail
|
9
9
|
from .member_membership import MemberMembership
|
10
10
|
from .member_purchases import MemberPurchase
|
11
|
+
from .notifications import EmailNotificationSettings, SmsNotificationSettings
|
11
12
|
from .out_of_studio_workout_history import OutOfStudioWorkoutHistory
|
12
13
|
from .performance_summary_detail import PerformanceSummaryDetail
|
13
14
|
from .performance_summary_list import PerformanceSummaryEntry
|
@@ -24,6 +25,7 @@ __all__ = [
|
|
24
25
|
"ChallengeTracker",
|
25
26
|
"ClassType",
|
26
27
|
"DoW",
|
28
|
+
"EmailNotificationSettings",
|
27
29
|
"EquipmentType",
|
28
30
|
"FitnessBenchmark",
|
29
31
|
"LatestAgreement",
|
@@ -34,6 +36,7 @@ __all__ = [
|
|
34
36
|
"OutOfStudioWorkoutHistory",
|
35
37
|
"PerformanceSummaryDetail",
|
36
38
|
"PerformanceSummaryEntry",
|
39
|
+
"SmsNotificationSettings",
|
37
40
|
"StatsResponse",
|
38
41
|
"StatsTime",
|
39
42
|
"StudioDetail",
|
@@ -0,0 +1,17 @@
|
|
1
|
+
from pydantic import Field
|
2
|
+
|
3
|
+
from otf_api.models.base import OtfItemBase
|
4
|
+
|
5
|
+
|
6
|
+
class SmsNotificationSettings(OtfItemBase):
|
7
|
+
is_promotional_sms_opt_in: bool | None = Field(None, alias="isPromotionalSmsOptIn")
|
8
|
+
is_transactional_sms_opt_in: bool | None = Field(None, alias="isTransactionalSmsOptIn")
|
9
|
+
is_promotional_phone_opt_in: bool | None = Field(None, alias="isPromotionalPhoneOptIn")
|
10
|
+
is_transactional_phone_opt_in: bool | None = Field(None, alias="isTransactionalPhoneOptIn")
|
11
|
+
|
12
|
+
|
13
|
+
class EmailNotificationSettings(OtfItemBase):
|
14
|
+
is_system_email_opt_in: bool | None = Field(None, alias="isSystemEmailOptIn")
|
15
|
+
is_promotional_email_opt_in: bool | None = Field(None, alias="isPromotionalEmailOptIn")
|
16
|
+
is_transactional_email_opt_in: bool | None = Field(None, alias="isTransactionalEmailOptIn")
|
17
|
+
email: str | None = None
|
@@ -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"))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: otf-api
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.2
|
4
4
|
Summary: Python OrangeTheory Fitness API Client
|
5
5
|
License: MIT
|
6
6
|
Author: Jessica Smith
|
@@ -44,11 +44,20 @@ pip install otf-api
|
|
44
44
|
|
45
45
|
## Overview
|
46
46
|
|
47
|
-
To use the API, you need to create an instance of the `Otf` class
|
47
|
+
To use the API, you need to create an instance of the `Otf` class. This will authenticate you with the API and allow you to make requests. When the `Otf` object is created it automatically grabs your member details and home studio, to simplify the process of making requests.
|
48
48
|
|
49
|
+
You can either pass an `OtfUser` object to the `OtfClass` or you can pass nothing and allow it to prompt you for your username and password.
|
49
50
|
|
50
|
-
|
51
|
+
You can also export environment variables `OTF_EMAIL` and `OTF_PASSWORD` to get these from the environment.
|
51
52
|
|
52
|
-
|
53
|
-
|
53
|
+
```python
|
54
|
+
from otf_api import Otf, OtfUser
|
55
|
+
|
56
|
+
otf = Otf()
|
57
|
+
|
58
|
+
# OR
|
59
|
+
|
60
|
+
otf = Otf(user=OtfUser(<email_address>,<password>))
|
61
|
+
|
62
|
+
```
|
54
63
|
|
@@ -1,13 +1,13 @@
|
|
1
|
-
otf_api/__init__.py,sha256=
|
2
|
-
otf_api/api.py,sha256=
|
1
|
+
otf_api/__init__.py,sha256=QDClynfqFpgl8WPwfIe7AGh9UKPBrhj081fXluLF4-k,204
|
2
|
+
otf_api/api.py,sha256=UNuMgJ6RnEj1rU8V6Funtnv3WQlxp2ShOJKCkfI-iC0,52570
|
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
|
6
6
|
otf_api/auth/utils.py,sha256=jUH_A1-DU3KfY-XrymCuQoud79o9qfu5P9Ws9QA2aWs,3211
|
7
|
-
otf_api/exceptions.py,sha256=
|
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=czt_OYsU4igeudRAi8kqOQro6fjNrbE9aLr--FA76Sg,1588
|
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
|
@@ -20,15 +20,16 @@ otf_api/models/member_detail.py,sha256=CDDZg3Ow07U57yRqbm3i-BVha0cvCNOZ8QhN0pliU
|
|
20
20
|
otf_api/models/member_membership.py,sha256=jZwHzwtVyMUr8dWGlFbMYj9qveCbiOblWW5szXDUFFo,1338
|
21
21
|
otf_api/models/member_purchases.py,sha256=Ne7ByEbGTqTJhuEyCgywWe8I3nc-D46qw09up7ys38s,1627
|
22
22
|
otf_api/models/mixins.py,sha256=VR5EeweySHhzaiqqnCr853Cpe1uK97cxY0IFEdf5T8w,2262
|
23
|
+
otf_api/models/notifications.py,sha256=AkmIfiIiU6wox_7puyenbhCX10SFvBD5eBAovcurRgY,833
|
23
24
|
otf_api/models/out_of_studio_workout_history.py,sha256=El5i0K2Td_sMReyfUKP-Iv1L1WgRx0ijjjnHzYvlCeY,1703
|
24
|
-
otf_api/models/performance_summary_detail.py,sha256=
|
25
|
-
otf_api/models/performance_summary_list.py,sha256=
|
25
|
+
otf_api/models/performance_summary_detail.py,sha256=U-Z-6-1rJHhtMuzCAnKZtAE0VPRCPUB8IzldINI8JqA,2917
|
26
|
+
otf_api/models/performance_summary_list.py,sha256=ydOVYRwdI_U0yxW4USrDQilLafwxMzUy80AHDmJvmdQ,1708
|
26
27
|
otf_api/models/studio_detail.py,sha256=4HZXP6khjuFs7J7lokr3rMEDDpCwcb7OYJVJvzgly7U,3639
|
27
28
|
otf_api/models/studio_services.py,sha256=aGLQMQmjGVpI6YxzAl-mcp3Y9cHPXuH9dIqrl6E-78E,1665
|
28
29
|
otf_api/models/telemetry.py,sha256=_g-wtJDbaXENk1DqH0bbpJUsGepWDYraRIsIvpvDBU8,2531
|
29
30
|
otf_api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
31
|
otf_api/utils.py,sha256=Af_Xg2QLoFqRzHOj4LtCIIqpRsdih-7HW1rJ8tUo8tE,3937
|
31
|
-
otf_api-0.9.
|
32
|
-
otf_api-0.9.
|
33
|
-
otf_api-0.9.
|
34
|
-
otf_api-0.9.
|
32
|
+
otf_api-0.9.2.dist-info/LICENSE,sha256=UaPT9ynYigC3nX8n22_rC37n-qmTRKLFaHrtUwF9ktE,1071
|
33
|
+
otf_api-0.9.2.dist-info/METADATA,sha256=VA_ZhzHmo4mZMLFaTP1YD94N04xJXeQG29n32chePaE,2296
|
34
|
+
otf_api-0.9.2.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
35
|
+
otf_api-0.9.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|