otf-api 0.3.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/auth.py ADDED
@@ -0,0 +1,315 @@
1
+ import typing
2
+ from typing import Any
3
+
4
+ import jwt
5
+ import pendulum
6
+ from loguru import logger
7
+ from pycognito import AWSSRP, Cognito, MFAChallengeException
8
+ from pycognito.exceptions import TokenVerificationException
9
+ from pydantic import Field
10
+ from pydantic.config import ConfigDict
11
+
12
+ from otf_api.models.base import OtfItemBase
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from boto3.session import Session
16
+ from botocore.config import Config
17
+
18
+ CLIENT_ID = "65knvqta6p37efc2l3eh26pl5o" # from otlive
19
+ USER_POOL_ID = "us-east-1_dYDxUeyL1"
20
+
21
+
22
+ class OtfCognito(Cognito):
23
+ _device_key: str | None = None
24
+
25
+ def __init__(
26
+ self,
27
+ user_pool_id: str,
28
+ client_id: str,
29
+ user_pool_region: str | None = None,
30
+ username: str | None = None,
31
+ id_token: str | None = None,
32
+ refresh_token: str | None = None,
33
+ access_token: str | None = None,
34
+ client_secret: str | None = None,
35
+ access_key: str | None = None,
36
+ secret_key: str | None = None,
37
+ session: "Session|None" = None,
38
+ botocore_config: "Config|None" = None,
39
+ boto3_client_kwargs: dict[str, Any] | None = None,
40
+ device_key: str | None = None,
41
+ ):
42
+ super().__init__(
43
+ user_pool_id,
44
+ client_id,
45
+ user_pool_region=user_pool_region,
46
+ username=username,
47
+ id_token=id_token,
48
+ refresh_token=refresh_token,
49
+ access_token=access_token,
50
+ client_secret=client_secret,
51
+ access_key=access_key,
52
+ secret_key=secret_key,
53
+ session=session,
54
+ botocore_config=botocore_config,
55
+ boto3_client_kwargs=boto3_client_kwargs,
56
+ )
57
+ self.device_key = device_key
58
+
59
+ @property
60
+ def device_key(self) -> str | None:
61
+ return self._device_key
62
+
63
+ @device_key.setter
64
+ def device_key(self, value: str | None):
65
+ if not value:
66
+ if self._device_key:
67
+ logger.info("Clearing device key")
68
+ self._device_key = value
69
+ return
70
+
71
+ redacted_value = value[:4] + "*" * (len(value) - 8) + value[-4:]
72
+ logger.info(f"Setting device key: {redacted_value}")
73
+ self._device_key = value
74
+
75
+ def _set_tokens(self, tokens: dict[str, Any]):
76
+ """Set the tokens and device metadata from the response.
77
+
78
+ Args:
79
+ tokens (dict): The response from the Cognito service.
80
+ """
81
+ super()._set_tokens(tokens)
82
+
83
+ if new_metadata := tokens["AuthenticationResult"].get("NewDeviceMetadata"):
84
+ self.device_key = new_metadata["DeviceKey"]
85
+
86
+ def authenticate(self, password: str, client_metadata: dict[str, Any] | None = None, device_key: str | None = None):
87
+ """
88
+ Authenticate the user using the SRP protocol. Overridden to add `confirm_device` call.
89
+
90
+ Args:
91
+ password (str): The user's password
92
+ client_metadata (dict, optional): Any additional client metadata to send to Cognito
93
+ """
94
+ aws = AWSSRP(
95
+ username=self.username,
96
+ password=password,
97
+ pool_id=self.user_pool_id,
98
+ client_id=self.client_id,
99
+ client=self.client,
100
+ client_secret=self.client_secret,
101
+ )
102
+ try:
103
+ tokens = aws.authenticate_user(client_metadata=client_metadata)
104
+ except MFAChallengeException as mfa_challenge:
105
+ self.mfa_tokens = mfa_challenge.get_tokens()
106
+ raise mfa_challenge
107
+
108
+ # Set the tokens and device metadata
109
+ self._set_tokens(tokens)
110
+
111
+ if not device_key:
112
+ # Confirm the device so we can use the refresh token
113
+ aws.confirm_device(tokens)
114
+ else:
115
+ self.device_key = device_key
116
+ try:
117
+ self.renew_access_token()
118
+ except TokenVerificationException:
119
+ logger.error("Failed to renew access token. Confirming device.")
120
+ self.device_key = None
121
+ aws.confirm_device(tokens)
122
+
123
+ def check_token(self, renew: bool = True) -> bool:
124
+ """
125
+ Checks the exp attribute of the access_token and either refreshes
126
+ the tokens by calling the renew_access_tokens method or does nothing
127
+ :param renew: bool indicating whether to refresh on expiration
128
+ :return: bool indicating whether access_token has expired
129
+ """
130
+ if not self.access_token:
131
+ raise AttributeError("Access Token Required to Check Token")
132
+ now = pendulum.now()
133
+ dec_access_token = jwt.decode(self.access_token, options={"verify_signature": False})
134
+
135
+ exp = pendulum.DateTime.fromtimestamp(dec_access_token["exp"])
136
+ if now > exp.subtract(minutes=15):
137
+ expired = True
138
+ if renew:
139
+ self.renew_access_token()
140
+ else:
141
+ expired = False
142
+ return expired
143
+
144
+ def renew_access_token(self):
145
+ """Sets a new access token on the User using the cached refresh token and device metadata."""
146
+ auth_params = {"REFRESH_TOKEN": self.refresh_token}
147
+ self._add_secret_hash(auth_params, "SECRET_HASH")
148
+
149
+ if self.device_key:
150
+ logger.info("Using device key for refresh token")
151
+ auth_params["DEVICE_KEY"] = self.device_key
152
+
153
+ refresh_response = self.client.initiate_auth(
154
+ ClientId=self.client_id, AuthFlow="REFRESH_TOKEN_AUTH", AuthParameters=auth_params
155
+ )
156
+ self._set_tokens(refresh_response)
157
+
158
+ @classmethod
159
+ def from_token(
160
+ cls, access_token: str, id_token: str, refresh_token: str | None = None, device_key: str | None = None
161
+ ) -> "OtfCognito":
162
+ """Create an OtfCognito instance from an id token.
163
+
164
+ Args:
165
+ access_token (str): The access token.
166
+ id_token (str): The id token.
167
+ refresh_token (str, optional): The refresh token. Defaults to None.
168
+ device_key (str, optional): The device key. Defaults
169
+
170
+ Returns:
171
+ OtfCognito: The user instance
172
+ """
173
+ cognito = OtfCognito(
174
+ USER_POOL_ID,
175
+ CLIENT_ID,
176
+ access_token=access_token,
177
+ id_token=id_token,
178
+ refresh_token=refresh_token,
179
+ device_key=device_key,
180
+ )
181
+ cognito.verify_tokens()
182
+ cognito.check_token()
183
+ return cognito
184
+
185
+ @classmethod
186
+ def login(cls, username: str, password: str) -> "OtfCognito":
187
+ """Create an OtfCognito instance from a username and password.
188
+
189
+ Args:
190
+ username (str): The username to login with.
191
+ password (str): The password to login with.
192
+
193
+ Returns:
194
+ OtfCognito: The logged in user.
195
+ """
196
+ cognito_user = OtfCognito(USER_POOL_ID, CLIENT_ID, username=username)
197
+ cognito_user.authenticate(password)
198
+ cognito_user.check_token()
199
+ return cognito_user
200
+
201
+
202
+ class IdClaimsData(OtfItemBase):
203
+ sub: str
204
+ email_verified: bool
205
+ iss: str
206
+ cognito_username: str = Field(alias="cognito:username")
207
+ given_name: str
208
+ locale: str
209
+ home_studio_id: str = Field(alias="custom:home_studio_id")
210
+ aud: str
211
+ event_id: str
212
+ token_use: str
213
+ auth_time: int
214
+ exp: int
215
+ is_migration: str = Field(alias="custom:isMigration")
216
+ iat: int
217
+ family_name: str
218
+ email: str
219
+ koji_person_id: str = Field(alias="custom:koji_person_id")
220
+
221
+ @property
222
+ def member_uuid(self) -> str:
223
+ return self.cognito_username
224
+
225
+ @property
226
+ def full_name(self) -> str:
227
+ return f"{self.given_name} {self.family_name}"
228
+
229
+
230
+ class AccessClaimsData(OtfItemBase):
231
+ sub: str
232
+ device_key: str
233
+ iss: str
234
+ client_id: str
235
+ event_id: str
236
+ token_use: str
237
+ scope: str
238
+ auth_time: int
239
+ exp: int
240
+ iat: int
241
+ jti: str
242
+ username: str
243
+
244
+ @property
245
+ def member_uuid(self) -> str:
246
+ return self.username
247
+
248
+
249
+ class OtfUser(OtfItemBase):
250
+ model_config = ConfigDict(arbitrary_types_allowed=True)
251
+ cognito: OtfCognito
252
+
253
+ def __init__(
254
+ self,
255
+ username: str | None = None,
256
+ password: str | None = None,
257
+ id_token: str | None = None,
258
+ access_token: str | None = None,
259
+ refresh_token: str | None = None,
260
+ device_key: str | None = None,
261
+ cognito: OtfCognito | None = None,
262
+ ):
263
+ """Create a User instance.
264
+
265
+ Args:
266
+ username (str, optional): The username to login with. Defaults to None.
267
+ password (str, optional): The password to login with. Defaults to None.
268
+ id_token (str, optional): The id token. Defaults to None.
269
+ access_token (str, optional): The access token. Defaults to None.
270
+ refresh_token (str, optional): The refresh token. Defaults to None.
271
+ device_key (str, optional): The device key. Defaults to None.
272
+ cognito (OtfCognito, optional): A Cognito instance. Defaults to None.
273
+
274
+ Raises:
275
+ ValueError: Must provide either username and password or id token
276
+
277
+
278
+ """
279
+ if cognito:
280
+ cognito = cognito
281
+ elif username and password:
282
+ cognito = OtfCognito.login(username, password)
283
+ elif access_token and id_token:
284
+ cognito = OtfCognito.from_token(access_token, id_token, refresh_token, device_key)
285
+ else:
286
+ raise ValueError("Must provide either username and password or id token.")
287
+
288
+ super().__init__(cognito=cognito)
289
+
290
+ @property
291
+ def member_id(self) -> str:
292
+ return self.id_claims_data.cognito_username
293
+
294
+ @property
295
+ def member_uuid(self) -> str:
296
+ return self.access_claims_data.sub
297
+
298
+ @property
299
+ def access_claims_data(self) -> AccessClaimsData:
300
+ return AccessClaimsData(**self.cognito.access_claims)
301
+
302
+ @property
303
+ def id_claims_data(self) -> IdClaimsData:
304
+ return IdClaimsData(**self.cognito.id_claims)
305
+
306
+ def get_tokens(self) -> dict[str, str]:
307
+ return {
308
+ "id_token": self.cognito.id_token,
309
+ "access_token": self.cognito.access_token,
310
+ "refresh_token": self.cognito.refresh_token,
311
+ }
312
+
313
+ @property
314
+ def device_key(self) -> str:
315
+ return self.cognito.device_key
otf_api/cli/app.py CHANGED
@@ -12,10 +12,9 @@ from rich.theme import Theme
12
12
 
13
13
  import otf_api
14
14
  from otf_api.cli._utilities import is_async_fn, with_cli_exception_handling
15
- from otf_api.models.auth import User
16
15
 
17
16
  if typing.TYPE_CHECKING:
18
- from otf_api.api import Api
17
+ from otf_api import Otf
19
18
 
20
19
 
21
20
  class OutputType(str, Enum):
@@ -79,7 +78,7 @@ class AsyncTyper(typer.Typer):
79
78
  self.console = Console(highlight=False, theme=theme, color_system="auto")
80
79
 
81
80
  # TODO: clean these up later, just don't want warnings everywhere that these could be None
82
- self.api: Api = None # type: ignore
81
+ self.api: Otf = None # type: ignore
83
82
  self.username: str = None # type: ignore
84
83
  self.password: str = None # type: ignore
85
84
  self.output: OutputType = None # type: ignore
@@ -91,10 +90,6 @@ class AsyncTyper(typer.Typer):
91
90
  self.username = username
92
91
  return
93
92
 
94
- if User.cache_file_exists():
95
- self.username = User.username_from_disk()
96
- return
97
-
98
93
  raise ValueError("Username not provided and not found in cache")
99
94
 
100
95
  def set_log_level(self, level: str) -> None:
otf_api/cli/bookings.py CHANGED
@@ -56,7 +56,7 @@ async def list_bookings(
56
56
  bk_status = BookingStatus.get_from_key_insensitive(status.value) if status else None
57
57
 
58
58
  if not base_app.api:
59
- base_app.api = await otf_api.Api.create(base_app.username, base_app.password)
59
+ base_app.api = await otf_api.Otf.create(base_app.username, base_app.password)
60
60
  bookings = await base_app.api.get_bookings(start_date, end_date, bk_status, limit, exclude_cancelled)
61
61
 
62
62
  if base_app.output == "json":
@@ -82,7 +82,7 @@ async def book(class_uuid: str = typer.Option(help="Class UUID to cancel")) -> N
82
82
  logger.info(f"Booking class {class_uuid}")
83
83
 
84
84
  if not base_app.api:
85
- base_app.api = await otf_api.Api.create(base_app.username, base_app.password)
85
+ base_app.api = await otf_api.Otf.create(base_app.username, base_app.password)
86
86
  booking = await base_app.api.book_class(class_uuid)
87
87
 
88
88
  base_app.console.print(booking)
@@ -115,7 +115,7 @@ async def book_interactive(
115
115
  class_type_enums = None
116
116
 
117
117
  if not base_app.api:
118
- base_app.api = await otf_api.Api.create(base_app.username, base_app.password)
118
+ base_app.api = await otf_api.Otf.create(base_app.username, base_app.password)
119
119
 
120
120
  classes = await base_app.api.get_classes(
121
121
  studio_uuids,
@@ -152,7 +152,7 @@ async def cancel_interactive() -> None:
152
152
 
153
153
  with base_app.console.status("Getting bookings...", spinner="arc"):
154
154
  if not base_app.api:
155
- base_app.api = await otf_api.Api.create(base_app.username, base_app.password)
155
+ base_app.api = await otf_api.Otf.create(base_app.username, base_app.password)
156
156
  bookings = await base_app.api.get_bookings()
157
157
 
158
158
  result = prompt_select_from_table(
@@ -177,7 +177,7 @@ async def cancel(booking_uuid: str = typer.Option(help="Booking UUID to cancel")
177
177
  logger.info(f"Cancelling booking {booking_uuid}")
178
178
 
179
179
  if not base_app.api:
180
- base_app.api = await otf_api.Api.create(base_app.username, base_app.password)
180
+ base_app.api = await otf_api.Otf.create(base_app.username, base_app.password)
181
181
  booking = await base_app.api.cancel_booking(booking_uuid)
182
182
 
183
183
  base_app.console.print(booking)
@@ -211,7 +211,7 @@ async def list_classes(
211
211
  class_type_enum = ClassType.get_from_key_insensitive(class_type.value) if class_type else None
212
212
 
213
213
  if not base_app.api:
214
- base_app.api = await otf_api.Api.create(base_app.username, base_app.password)
214
+ base_app.api = await otf_api.Otf.create(base_app.username, base_app.password)
215
215
  classes = await base_app.api.get_classes(
216
216
  studio_uuids, include_home_studio, start_date, end_date, limit, class_type_enum, exclude_cancelled
217
217
  )
@@ -1,5 +1,5 @@
1
- from .auth import User
2
1
  from .responses import (
2
+ BodyCompositionList,
3
3
  BookClass,
4
4
  BookingList,
5
5
  BookingStatus,
@@ -7,6 +7,8 @@ from .responses import (
7
7
  ChallengeTrackerContent,
8
8
  ChallengeTrackerDetailList,
9
9
  ChallengeType,
10
+ ClassType,
11
+ DoW,
10
12
  EquipmentType,
11
13
  FavoriteStudioList,
12
14
  HistoryClassStatus,
@@ -16,8 +18,11 @@ from .responses import (
16
18
  MemberPurchaseList,
17
19
  OtfClassList,
18
20
  OutOfStudioWorkoutHistoryList,
21
+ Pagination,
19
22
  PerformanceSummaryDetail,
20
23
  PerformanceSummaryList,
24
+ StatsResponse,
25
+ StatsTime,
21
26
  StudioDetail,
22
27
  StudioDetailList,
23
28
  StudioServiceList,
@@ -25,35 +30,39 @@ from .responses import (
25
30
  TelemetryHrHistory,
26
31
  TelemetryMaxHr,
27
32
  TotalClasses,
28
- WorkoutList,
29
33
  )
30
34
 
31
35
  __all__ = [
32
- "User",
33
- "ChallengeType",
34
- "BookingStatus",
35
- "EquipmentType",
36
- "HistoryClassStatus",
36
+ "BodyCompositionList",
37
+ "BookClass",
37
38
  "BookingList",
39
+ "BookingStatus",
40
+ "CancelBooking",
38
41
  "ChallengeTrackerContent",
39
42
  "ChallengeTrackerDetailList",
43
+ "ChallengeType",
44
+ "ClassType",
45
+ "DoW",
46
+ "EquipmentType",
47
+ "FavoriteStudioList",
48
+ "HistoryClassStatus",
40
49
  "LatestAgreement",
41
50
  "MemberDetail",
42
51
  "MemberMembership",
43
52
  "MemberPurchaseList",
53
+ "OtfClassList",
44
54
  "OutOfStudioWorkoutHistoryList",
55
+ "Pagination",
56
+ "PerformanceSummaryDetail",
57
+ "PerformanceSummaryList",
58
+ "StatsResponse",
59
+ "StatsTime",
60
+ "StudioDetail",
61
+ "StudioDetailList",
45
62
  "StudioServiceList",
46
- "TotalClasses",
47
- "WorkoutList",
48
- "FavoriteStudioList",
49
- "OtfClassList",
50
- "TelemetryHrHistory",
63
+ "StudioStatus",
51
64
  "Telemetry",
65
+ "TelemetryHrHistory",
52
66
  "TelemetryMaxHr",
53
- "StudioDetail",
54
- "StudioDetailList",
55
- "PerformanceSummaryDetail",
56
- "PerformanceSummaryList",
57
- "BookClass",
58
- "CancelBooking",
67
+ "TotalClasses",
59
68
  ]
@@ -1,52 +1,58 @@
1
+ from .body_composition_list import BodyCompositionList
1
2
  from .book_class import BookClass
2
3
  from .bookings import BookingList, BookingStatus
3
4
  from .cancel_booking import CancelBooking
4
5
  from .challenge_tracker_content import ChallengeTrackerContent
5
6
  from .challenge_tracker_detail import ChallengeTrackerDetailList
6
- from .classes import OtfClassList
7
+ from .classes import ClassType, DoW, OtfClassList
7
8
  from .enums import ChallengeType, EquipmentType, HistoryClassStatus
8
9
  from .favorite_studios import FavoriteStudioList
9
10
  from .latest_agreement import LatestAgreement
11
+ from .lifetime_stats import StatsResponse, StatsTime
10
12
  from .member_detail import MemberDetail
11
13
  from .member_membership import MemberMembership
12
14
  from .member_purchases import MemberPurchaseList
13
15
  from .out_of_studio_workout_history import OutOfStudioWorkoutHistoryList
14
16
  from .performance_summary_detail import PerformanceSummaryDetail
15
17
  from .performance_summary_list import PerformanceSummaryList
16
- from .studio_detail import StudioDetail, StudioDetailList
18
+ from .studio_detail import Pagination, StudioDetail, StudioDetailList
17
19
  from .studio_services import StudioServiceList
18
20
  from .telemetry import Telemetry
19
21
  from .telemetry_hr_history import TelemetryHrHistory
20
22
  from .telemetry_max_hr import TelemetryMaxHr
21
23
  from .total_classes import TotalClasses
22
- from .workouts import WorkoutList
23
24
 
24
25
  __all__ = [
26
+ "BodyCompositionList",
27
+ "BookClass",
25
28
  "BookingList",
29
+ "BookingStatus",
30
+ "CancelBooking",
26
31
  "ChallengeTrackerContent",
27
32
  "ChallengeTrackerDetailList",
33
+ "ChallengeType",
34
+ "ClassType",
35
+ "DoW",
36
+ "EquipmentType",
37
+ "FavoriteStudioList",
38
+ "HistoryClassStatus",
28
39
  "LatestAgreement",
29
40
  "MemberDetail",
30
41
  "MemberMembership",
31
42
  "MemberPurchaseList",
43
+ "OtfClassList",
32
44
  "OutOfStudioWorkoutHistoryList",
45
+ "Pagination",
46
+ "PerformanceSummaryDetail",
47
+ "PerformanceSummaryList",
48
+ "StatsResponse",
49
+ "StatsTime",
50
+ "StudioDetail",
51
+ "StudioDetailList",
33
52
  "StudioServiceList",
34
- "TotalClasses",
35
- "WorkoutList",
36
- "ChallengeType",
37
- "BookingStatus",
38
- "EquipmentType",
39
- "HistoryClassStatus",
40
53
  "StudioStatus",
41
- "FavoriteStudioList",
42
- "OtfClassList",
43
- "TelemetryHrHistory",
44
54
  "Telemetry",
55
+ "TelemetryHrHistory",
45
56
  "TelemetryMaxHr",
46
- "StudioDetail",
47
- "StudioDetailList",
48
- "PerformanceSummaryDetail",
49
- "PerformanceSummaryList",
50
- "BookClass",
51
- "CancelBooking",
57
+ "TotalClasses",
52
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.3.0
3
+ Version: 0.5.0
4
4
  Summary: Python OrangeTheory Fitness API Client
5
5
  License: MIT
6
6
  Author: Jessica Smith
@@ -22,13 +22,13 @@ Requires-Dist: aiohttp (==3.9.5)
22
22
  Requires-Dist: humanize (>=4.9.0,<5.0.0)
23
23
  Requires-Dist: inflection (==0.5.*)
24
24
  Requires-Dist: loguru (==0.7.2)
25
- Requires-Dist: pendulum[cli] (>=3.0.0,<4.0.0)
25
+ Requires-Dist: pendulum (>=3.0.0,<4.0.0)
26
26
  Requires-Dist: pint (==0.24.*)
27
27
  Requires-Dist: pycognito (==2024.5.1)
28
28
  Requires-Dist: pydantic (==2.7.3)
29
29
  Requires-Dist: python-box (>=7.2.0,<8.0.0)
30
- Requires-Dist: readchar[cli] (>=4.1.0,<5.0.0)
31
- Requires-Dist: typer[cli] (>=0.12.3,<0.13.0)
30
+ Requires-Dist: readchar (>=4.1.0,<5.0.0)
31
+ Requires-Dist: typer (>=0.12.3,<0.13.0)
32
32
  Project-URL: Documentation, https://otf-api.readthedocs.io/en/stable/
33
33
  Description-Content-Type: text/markdown
34
34
 
@@ -44,12 +44,11 @@ pip install otf-api
44
44
 
45
45
  ## Overview
46
46
 
47
- To use the API, you need to create an instance of the `Api` class, providing your email address and password. This will authenticate you with the API and allow you to make requests. When the `Api` 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, 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.
48
48
 
49
49
 
50
50
  See the [examples](./examples) for more information on how to use the API.
51
51
 
52
-
53
52
  Disclaimer:
54
53
  This project is in no way affiliated with OrangeTheory Fitness.
55
54
 
@@ -1,14 +1,14 @@
1
- otf_api/__init__.py,sha256=TWtPFBlCS-SBoVXJ5VOR3o_IFWXDHItwKLuvpUpR5WU,238
2
- otf_api/api.py,sha256=41sJ6gIvqKnt3jyrqtbPYDuWGcQzmAtOBwiobBWGwKQ,35714
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
3
4
  otf_api/cli/__init__.py,sha256=WI-882LPH7Tj_ygDHqE5ehsas_u7m3ulsplS9vXKByk,151
4
5
  otf_api/cli/_utilities.py,sha256=epjEO9S6ag4HgJLXlTpCQXfdVQkqGWyNavp7DjwPL78,1753
5
- otf_api/cli/app.py,sha256=CGEjaDZW32jWpCAd-afyXNvuNfmT25Ts9HrXKvp5Mf0,6621
6
- otf_api/cli/bookings.py,sha256=yXednsIrpgfa8m26S-LXVDIEB8zCfj9J9osdz5c6Kko,8161
6
+ otf_api/cli/app.py,sha256=88TuMwq3foRr1Cui0V3h0mxNkoANqd6QQifI9CIgLvI,6469
7
+ otf_api/cli/bookings.py,sha256=wSmZA-03etcL6Tvb1vDSvHZW8EA9CZUgKX6W1pps3Yw,8161
7
8
  otf_api/cli/prompts.py,sha256=iyodQXVa5v9VsrMxw0ob1okGRBDbWCSxhrNEylsOTEQ,5358
8
- otf_api/models/__init__.py,sha256=zewymgFsv81_s43hl6dPbFkACT17A2dTSY-nXoBk-Qg,1269
9
- otf_api/models/auth.py,sha256=DyFManp5RlfQinA0rWG_PjBZXrn2TpSM1-PJ630lamo,4073
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=mhusNSeDyUqW9ki-HahX6xkmLud_9ighPS6yBX7T0k0,1753
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.3.0.dist-info/AUTHORS.md,sha256=FcNWMxpe8KDuTq4Qau0SUXsabQwGs9TGnMp1WkXRnj8,123
38
- otf_api-0.3.0.dist-info/LICENSE,sha256=UaPT9ynYigC3nX8n22_rC37n-qmTRKLFaHrtUwF9ktE,1071
39
- otf_api-0.3.0.dist-info/METADATA,sha256=IZh9vTuZTckAY8AsAGmmF0eQWE5yaiHG2C2yPKM_nAE,2275
40
- otf_api-0.3.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
41
- otf_api-0.3.0.dist-info/entry_points.txt,sha256=V2jhhfsUo3DeF0CA9HmKrMnvSoOldn9ShIzbApbeHTY,44
42
- otf_api-0.3.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,,