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/__init__.py +4 -4
- otf_api/api.py +137 -132
- otf_api/auth.py +315 -0
- otf_api/cli/app.py +2 -7
- otf_api/cli/bookings.py +6 -6
- otf_api/models/__init__.py +27 -18
- otf_api/models/responses/__init__.py +24 -18
- otf_api/models/responses/performance_summary_list.py +2 -2
- {otf_api-0.3.0.dist-info → otf_api-0.5.0.dist-info}/METADATA +5 -6
- {otf_api-0.3.0.dist-info → otf_api-0.5.0.dist-info}/RECORD +14 -15
- otf_api/models/auth.py +0 -147
- otf_api/models/responses/workouts.py +0 -78
- {otf_api-0.3.0.dist-info → otf_api-0.5.0.dist-info}/AUTHORS.md +0 -0
- {otf_api-0.3.0.dist-info → otf_api-0.5.0.dist-info}/LICENSE +0 -0
- {otf_api-0.3.0.dist-info → otf_api-0.5.0.dist-info}/WHEEL +0 -0
- {otf_api-0.3.0.dist-info → otf_api-0.5.0.dist-info}/entry_points.txt +0 -0
otf_api/models/auth.py
DELETED
@@ -1,147 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from pathlib import Path
|
3
|
-
from typing import ClassVar
|
4
|
-
|
5
|
-
from pycognito import Cognito, TokenVerificationException
|
6
|
-
from pydantic import Field
|
7
|
-
|
8
|
-
from otf_api.models.base import OtfItemBase
|
9
|
-
|
10
|
-
CLIENT_ID = "65knvqta6p37efc2l3eh26pl5o" # from otlive
|
11
|
-
USER_POOL_ID = "us-east-1_dYDxUeyL1"
|
12
|
-
|
13
|
-
|
14
|
-
class IdClaimsData(OtfItemBase):
|
15
|
-
sub: str
|
16
|
-
email_verified: bool
|
17
|
-
iss: str
|
18
|
-
cognito_username: str = Field(alias="cognito:username")
|
19
|
-
given_name: str
|
20
|
-
locale: str
|
21
|
-
home_studio_id: str = Field(alias="custom:home_studio_id")
|
22
|
-
aud: str
|
23
|
-
event_id: str
|
24
|
-
token_use: str
|
25
|
-
auth_time: int
|
26
|
-
exp: int
|
27
|
-
is_migration: str = Field(alias="custom:isMigration")
|
28
|
-
iat: int
|
29
|
-
family_name: str
|
30
|
-
email: str
|
31
|
-
koji_person_id: str = Field(alias="custom:koji_person_id")
|
32
|
-
|
33
|
-
@property
|
34
|
-
def member_uuid(self) -> str:
|
35
|
-
return self.cognito_username
|
36
|
-
|
37
|
-
@property
|
38
|
-
def full_name(self) -> str:
|
39
|
-
return f"{self.given_name} {self.family_name}"
|
40
|
-
|
41
|
-
|
42
|
-
class AccessClaimsData(OtfItemBase):
|
43
|
-
sub: str
|
44
|
-
device_key: str
|
45
|
-
iss: str
|
46
|
-
client_id: str
|
47
|
-
event_id: str
|
48
|
-
token_use: str
|
49
|
-
scope: str
|
50
|
-
auth_time: int
|
51
|
-
exp: int
|
52
|
-
iat: int
|
53
|
-
jti: str
|
54
|
-
username: str
|
55
|
-
|
56
|
-
@property
|
57
|
-
def member_uuid(self) -> str:
|
58
|
-
return self.username
|
59
|
-
|
60
|
-
|
61
|
-
class User:
|
62
|
-
token_path: ClassVar[Path] = Path("~/.otf/.tokens").expanduser()
|
63
|
-
cognito: Cognito
|
64
|
-
|
65
|
-
def __init__(self, cognito: Cognito):
|
66
|
-
self.cognito = cognito
|
67
|
-
|
68
|
-
@property
|
69
|
-
def member_id(self) -> str:
|
70
|
-
return self.id_claims_data.cognito_username
|
71
|
-
|
72
|
-
@property
|
73
|
-
def member_uuid(self) -> str:
|
74
|
-
return self.access_claims_data.sub
|
75
|
-
|
76
|
-
@property
|
77
|
-
def access_claims_data(self) -> AccessClaimsData:
|
78
|
-
return AccessClaimsData(**self.cognito.access_claims)
|
79
|
-
|
80
|
-
@property
|
81
|
-
def id_claims_data(self) -> IdClaimsData:
|
82
|
-
return IdClaimsData(**self.cognito.id_claims)
|
83
|
-
|
84
|
-
def save_to_disk(self) -> None:
|
85
|
-
self.token_path.parent.mkdir(parents=True, exist_ok=True)
|
86
|
-
data = {
|
87
|
-
"username": self.cognito.username,
|
88
|
-
"id_token": self.cognito.id_token,
|
89
|
-
"access_token": self.cognito.access_token,
|
90
|
-
"refresh_token": self.cognito.refresh_token,
|
91
|
-
}
|
92
|
-
self.token_path.write_text(json.dumps(data))
|
93
|
-
|
94
|
-
@classmethod
|
95
|
-
def cache_file_exists(cls) -> bool:
|
96
|
-
return cls.token_path.exists()
|
97
|
-
|
98
|
-
@classmethod
|
99
|
-
def username_from_disk(cls) -> str:
|
100
|
-
val: str = json.loads(cls.token_path.read_text())["username"]
|
101
|
-
return val
|
102
|
-
|
103
|
-
@classmethod
|
104
|
-
def load_from_disk(cls, username: str, password: str) -> "User":
|
105
|
-
"""Load a User instance from disk. If the token is invalid, reauthenticate with the provided credentials.
|
106
|
-
|
107
|
-
Args:
|
108
|
-
username (str): The username to reauthenticate with.
|
109
|
-
password (str): The password to reauthenticate with.
|
110
|
-
|
111
|
-
Returns:
|
112
|
-
User: The loaded user.
|
113
|
-
|
114
|
-
"""
|
115
|
-
attr_dict = json.loads(cls.token_path.read_text())
|
116
|
-
|
117
|
-
cognito_user = Cognito(USER_POOL_ID, CLIENT_ID, **attr_dict)
|
118
|
-
try:
|
119
|
-
cognito_user.verify_tokens()
|
120
|
-
return cls(cognito=cognito_user)
|
121
|
-
except TokenVerificationException:
|
122
|
-
user = cls.login(username, password)
|
123
|
-
return user
|
124
|
-
|
125
|
-
@classmethod
|
126
|
-
def login(cls, username: str, password: str) -> "User":
|
127
|
-
"""Login and return a User instance. After a successful login, the user is saved to disk.
|
128
|
-
|
129
|
-
Args:
|
130
|
-
username (str): The username to login with.
|
131
|
-
password (str): The password to login with.
|
132
|
-
|
133
|
-
Returns:
|
134
|
-
User: The logged in user.
|
135
|
-
"""
|
136
|
-
cognito_user = Cognito(USER_POOL_ID, CLIENT_ID, username=username)
|
137
|
-
cognito_user.authenticate(password)
|
138
|
-
cognito_user.check_token()
|
139
|
-
user = cls(cognito=cognito_user)
|
140
|
-
user.save_to_disk()
|
141
|
-
return user
|
142
|
-
|
143
|
-
def refresh_token(self) -> "User":
|
144
|
-
"""Refresh the user's access token."""
|
145
|
-
self.cognito.check_token()
|
146
|
-
self.save_to_disk()
|
147
|
-
return self
|
@@ -1,78 +0,0 @@
|
|
1
|
-
from ast import literal_eval
|
2
|
-
from datetime import datetime
|
3
|
-
from typing import Any
|
4
|
-
|
5
|
-
from pydantic import Field, PrivateAttr
|
6
|
-
|
7
|
-
from otf_api.models.base import OtfItemBase
|
8
|
-
|
9
|
-
|
10
|
-
class WorkoutType(OtfItemBase):
|
11
|
-
id: int
|
12
|
-
display_name: str = Field(..., alias="displayName")
|
13
|
-
icon: str
|
14
|
-
|
15
|
-
|
16
|
-
class Workout(OtfItemBase):
|
17
|
-
studio_number: str = Field(..., alias="studioNumber")
|
18
|
-
studio_name: str = Field(..., alias="studioName")
|
19
|
-
class_type: str = Field(..., alias="classType")
|
20
|
-
active_time: int = Field(..., alias="activeTime")
|
21
|
-
coach: str
|
22
|
-
member_uuid: str = Field(..., alias="memberUuId")
|
23
|
-
class_date: datetime = Field(..., alias="classDate")
|
24
|
-
total_calories: int = Field(..., alias="totalCalories")
|
25
|
-
avg_hr: int = Field(..., alias="avgHr")
|
26
|
-
max_hr: int = Field(..., alias="maxHr")
|
27
|
-
avg_percent_hr: int = Field(..., alias="avgPercentHr")
|
28
|
-
max_percent_hr: int = Field(..., alias="maxPercentHr")
|
29
|
-
total_splat_points: int = Field(..., alias="totalSplatPoints")
|
30
|
-
red_zone_time_second: int = Field(..., alias="redZoneTimeSecond")
|
31
|
-
orange_zone_time_second: int = Field(..., alias="orangeZoneTimeSecond")
|
32
|
-
green_zone_time_second: int = Field(..., alias="greenZoneTimeSecond")
|
33
|
-
blue_zone_time_second: int = Field(..., alias="blueZoneTimeSecond")
|
34
|
-
black_zone_time_second: int = Field(..., alias="blackZoneTimeSecond")
|
35
|
-
step_count: int = Field(..., alias="stepCount")
|
36
|
-
class_history_uuid: str = Field(..., alias="classHistoryUuId")
|
37
|
-
class_id: str = Field(..., alias="classId")
|
38
|
-
date_created: datetime = Field(..., alias="dateCreated")
|
39
|
-
date_updated: datetime = Field(..., alias="dateUpdated")
|
40
|
-
is_intro: bool = Field(..., alias="isIntro")
|
41
|
-
is_leader: bool = Field(..., alias="isLeader")
|
42
|
-
member_email: None = Field(..., alias="memberEmail")
|
43
|
-
member_name: str = Field(..., alias="memberName")
|
44
|
-
member_performance_id: str = Field(..., alias="memberPerformanceId")
|
45
|
-
minute_by_minute_hr: list[int] = Field(
|
46
|
-
...,
|
47
|
-
alias="minuteByMinuteHr",
|
48
|
-
description="HR data for each minute of the workout. It is returned as a string literal, so it needs to be "
|
49
|
-
"evaluated to a list. If can't be parsed, it will return an empty list.",
|
50
|
-
)
|
51
|
-
source: str
|
52
|
-
studio_account_uuid: str = Field(..., alias="studioAccountUuId")
|
53
|
-
version: str
|
54
|
-
workout_type: WorkoutType = Field(..., alias="workoutType")
|
55
|
-
_minute_by_minute_raw: str | None = PrivateAttr(None)
|
56
|
-
|
57
|
-
@property
|
58
|
-
def active_time_minutes(self) -> int:
|
59
|
-
"""Get the active time in minutes."""
|
60
|
-
return self.active_time // 60
|
61
|
-
|
62
|
-
def __init__(self, **data: Any):
|
63
|
-
if "minuteByMinuteHr" in data:
|
64
|
-
try:
|
65
|
-
data["minuteByMinuteHr"] = literal_eval(data["minuteByMinuteHr"])
|
66
|
-
except (ValueError, SyntaxError):
|
67
|
-
data["minuteByMinuteHr"] = []
|
68
|
-
|
69
|
-
super().__init__(**data)
|
70
|
-
self._minute_by_minute_raw = data.get("minuteByMinuteHr")
|
71
|
-
|
72
|
-
|
73
|
-
class WorkoutList(OtfItemBase):
|
74
|
-
workouts: list[Workout]
|
75
|
-
|
76
|
-
@property
|
77
|
-
def by_class_history_uuid(self) -> dict[str, Workout]:
|
78
|
-
return {workout.class_history_uuid: workout for workout in self.workouts}
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|