otf-api 0.9.0__tar.gz → 0.9.2__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.
Files changed (36) hide show
  1. {otf_api-0.9.0 → otf_api-0.9.2}/PKG-INFO +14 -5
  2. otf_api-0.9.2/README.md +30 -0
  3. {otf_api-0.9.0 → otf_api-0.9.2}/pyproject.toml +1 -1
  4. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/__init__.py +1 -1
  5. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/api.py +222 -12
  6. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/exceptions.py +11 -1
  7. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/__init__.py +3 -0
  8. otf_api-0.9.2/src/otf_api/models/notifications.py +17 -0
  9. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/performance_summary_detail.py +7 -2
  10. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/performance_summary_list.py +1 -1
  11. otf_api-0.9.0/README.md +0 -21
  12. {otf_api-0.9.0 → otf_api-0.9.2}/LICENSE +0 -0
  13. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/auth/__init__.py +0 -0
  14. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/auth/auth.py +0 -0
  15. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/auth/user.py +0 -0
  16. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/auth/utils.py +0 -0
  17. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/filters.py +0 -0
  18. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/logging.py +0 -0
  19. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/base.py +0 -0
  20. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/body_composition_list.py +0 -0
  21. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/bookings.py +0 -0
  22. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/challenge_tracker_content.py +0 -0
  23. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/challenge_tracker_detail.py +0 -0
  24. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/classes.py +0 -0
  25. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/enums.py +0 -0
  26. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/lifetime_stats.py +0 -0
  27. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/member_detail.py +0 -0
  28. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/member_membership.py +0 -0
  29. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/member_purchases.py +0 -0
  30. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/mixins.py +0 -0
  31. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/out_of_studio_workout_history.py +0 -0
  32. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/studio_detail.py +0 -0
  33. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/studio_services.py +0 -0
  34. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/models/telemetry.py +0 -0
  35. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/py.typed +0 -0
  36. {otf_api-0.9.0 → otf_api-0.9.2}/src/otf_api/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: otf-api
3
- Version: 0.9.0
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, providing your email address and password. 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.
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
- See the [examples](./examples) for more information on how to use the API.
51
+ You can also export environment variables `OTF_EMAIL` and `OTF_PASSWORD` to get these from the environment.
51
52
 
52
- Disclaimer:
53
- This project is in no way affiliated with OrangeTheory Fitness.
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
 
@@ -0,0 +1,30 @@
1
+ Simple API client for interacting with the OrangeTheory Fitness APIs.
2
+
3
+ Review the [documentation](https://otf-api.readthedocs.io/en/stable/).
4
+
5
+
6
+ This library allows access to the OrangeTheory API to retrieve workouts and performance data, class schedules, studio information, and bookings.
7
+
8
+ ## Installation
9
+ ```bash
10
+ pip install otf-api
11
+ ```
12
+
13
+ ## Overview
14
+
15
+ 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.
16
+
17
+ 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.
18
+
19
+ You can also export environment variables `OTF_EMAIL` and `OTF_PASSWORD` to get these from the environment.
20
+
21
+ ```python
22
+ from otf_api import Otf, OtfUser
23
+
24
+ otf = Otf()
25
+
26
+ # OR
27
+
28
+ otf = Otf(user=OtfUser(<email_address>,<password>))
29
+
30
+ ```
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "otf-api"
3
- version = "0.9.0"
3
+ version = "0.9.2"
4
4
  description = "Python OrangeTheory Fitness API Client"
5
5
  authors = ["Jessica Smith <j.smith.git1@gmail.com>"]
6
6
  license = "MIT"
@@ -4,7 +4,7 @@ from otf_api.api import Otf
4
4
  from otf_api import models
5
5
  from otf_api.auth import OtfUser
6
6
 
7
- __version__ = "0.9.0"
7
+ __version__ = "0.9.2"
8
8
 
9
9
 
10
10
  __all__ = ["Otf", "OtfUser", "models"]
@@ -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
- LOGGER.exception(f"Error making request: {e}")
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
- def update_sms_notification_settings(self, promotional_enabled: bool, transactional_enabled: bool):
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
- res = self._default_request("POST", url, json=body)
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
- return res["data"]
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
- res = self._default_request("POST", "/otfmailing/v2/preferences", json=body)
1114
+ self._default_request("POST", "/otfmailing/v2/preferences", json=body)
1038
1115
 
1039
- return res["data"]
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:
@@ -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."""
@@ -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
- id: str
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 = 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
- id: str = Field(..., alias="id")
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"))
otf_api-0.9.0/README.md DELETED
@@ -1,21 +0,0 @@
1
- Simple API client for interacting with the OrangeTheory Fitness APIs.
2
-
3
- Review the [documentation](https://otf-api.readthedocs.io/en/stable/).
4
-
5
-
6
- This library allows access to the OrangeTheory API to retrieve workouts and performance data, class schedules, studio information, and bookings.
7
-
8
- ## Installation
9
- ```bash
10
- pip install otf-api
11
- ```
12
-
13
- ## Overview
14
-
15
- To use the API, you need to create an instance of the `Otf` class, providing your email address and password. 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.
16
-
17
-
18
- See the [examples](./examples) for more information on how to use the API.
19
-
20
- Disclaimer:
21
- This project is in no way affiliated with OrangeTheory Fitness.
File without changes
File without changes
File without changes
File without changes
File without changes