sweatstack 0.17.0__py3-none-any.whl → 0.19.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.
- sweatstack/Sweat Stack examples/Getting started.ipynb +28784 -0
- sweatstack/client.py +70 -3
- sweatstack/openapi_schemas.py +31 -17
- sweatstack/schemas.py +1 -1
- {sweatstack-0.17.0.dist-info → sweatstack-0.19.0.dist-info}/METADATA +5 -5
- {sweatstack-0.17.0.dist-info → sweatstack-0.19.0.dist-info}/RECORD +8 -7
- {sweatstack-0.17.0.dist-info → sweatstack-0.19.0.dist-info}/WHEEL +0 -0
- {sweatstack-0.17.0.dist-info → sweatstack-0.19.0.dist-info}/entry_points.txt +0 -0
sweatstack/client.py
CHANGED
|
@@ -15,10 +15,14 @@ from typing import Any, Generator, get_type_hints, List, Literal
|
|
|
15
15
|
from urllib.parse import parse_qs, urlparse
|
|
16
16
|
|
|
17
17
|
import httpx
|
|
18
|
+
import ipywidgets as widgets
|
|
18
19
|
import pandas as pd
|
|
20
|
+
from IPython.display import display
|
|
19
21
|
|
|
20
22
|
from .constants import DEFAULT_URL
|
|
21
|
-
from .schemas import
|
|
23
|
+
from .schemas import (
|
|
24
|
+
ActivityDetails, ActivitySummary, Sport, TraceDetails, UserSummary
|
|
25
|
+
)
|
|
22
26
|
from .utils import decode_jwt_body, make_dataframe_streamlit_compatible
|
|
23
27
|
|
|
24
28
|
|
|
@@ -62,6 +66,7 @@ class OAuth2Mixin:
|
|
|
62
66
|
"redirect_uri": redirect_uri,
|
|
63
67
|
"code_challenge": code_challenge,
|
|
64
68
|
"scope": "data:read",
|
|
69
|
+
"prompt": "none",
|
|
65
70
|
}
|
|
66
71
|
base_url = self.url
|
|
67
72
|
path = "/oauth/authorize"
|
|
@@ -83,7 +88,7 @@ class OAuth2Mixin:
|
|
|
83
88
|
"grant_type": "authorization_code",
|
|
84
89
|
"client_id": OAUTH2_CLIENT_ID,
|
|
85
90
|
"code": server.code,
|
|
86
|
-
"code_verifier": code_verifier
|
|
91
|
+
"code_verifier": code_verifier,
|
|
87
92
|
}
|
|
88
93
|
response = httpx.post(
|
|
89
94
|
f"{self.url}/api/v1/oauth/token",
|
|
@@ -103,7 +108,55 @@ class OAuth2Mixin:
|
|
|
103
108
|
raise Exception("SweatStack Python login failed. Please try again.")
|
|
104
109
|
|
|
105
110
|
|
|
106
|
-
class
|
|
111
|
+
class DelegationMixin:
|
|
112
|
+
def _get_delegated_token(self, user_id: str):
|
|
113
|
+
with self._http_client() as client:
|
|
114
|
+
response = client.post(
|
|
115
|
+
"/api/v1/oauth/delegated-token",
|
|
116
|
+
json={"sub": user_id},
|
|
117
|
+
)
|
|
118
|
+
response.raise_for_status()
|
|
119
|
+
|
|
120
|
+
return response.json()
|
|
121
|
+
|
|
122
|
+
def switch_user(self, user_id: str):
|
|
123
|
+
token_response = self._get_delegated_token(user_id)
|
|
124
|
+
self.api_key = token_response["access_token"]
|
|
125
|
+
self.refresh_token = token_response["refresh_token"]
|
|
126
|
+
|
|
127
|
+
def _get_principal_token(self):
|
|
128
|
+
with self._http_client() as client:
|
|
129
|
+
response = client.get(
|
|
130
|
+
"/api/v1/oauth/principal-token",
|
|
131
|
+
)
|
|
132
|
+
response.raise_for_status()
|
|
133
|
+
return response.json()
|
|
134
|
+
|
|
135
|
+
def switch_back(self):
|
|
136
|
+
token_response = self._get_principal_token()
|
|
137
|
+
self.api_key = token_response["access_token"]
|
|
138
|
+
self.refresh_token = token_response["refresh_token"]
|
|
139
|
+
|
|
140
|
+
def delegated_client(self, user_id: str):
|
|
141
|
+
token_response = self._get_delegated_token(user_id)
|
|
142
|
+
return self.__class__(
|
|
143
|
+
api_key=token_response["access_token"],
|
|
144
|
+
refresh_token=token_response["refresh_token"],
|
|
145
|
+
url=self.url,
|
|
146
|
+
streamlit_compatible=self.streamlit_compatible,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def principal_client(self):
|
|
150
|
+
token_response = self._get_principal_token()
|
|
151
|
+
return self.__class__(
|
|
152
|
+
api_key=token_response["access_token"],
|
|
153
|
+
refresh_token=token_response["refresh_token"],
|
|
154
|
+
url=self.url,
|
|
155
|
+
streamlit_compatible=self.streamlit_compatible,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class Client(OAuth2Mixin, DelegationMixin, JupyterInteractivityMixin):
|
|
107
160
|
def __init__(
|
|
108
161
|
self,
|
|
109
162
|
api_key: str | None = None,
|
|
@@ -553,6 +606,13 @@ class Client(OAuth2Mixin):
|
|
|
553
606
|
response.raise_for_status()
|
|
554
607
|
return response.json()
|
|
555
608
|
|
|
609
|
+
def list_users(self) -> list[UserSummary]:
|
|
610
|
+
with self._http_client() as client:
|
|
611
|
+
response = client.get(
|
|
612
|
+
url="/api/v1/users/",
|
|
613
|
+
)
|
|
614
|
+
response.raise_for_status()
|
|
615
|
+
return [UserSummary.model_validate(user) for user in response.json()]
|
|
556
616
|
|
|
557
617
|
_default_client = Client()
|
|
558
618
|
|
|
@@ -593,6 +653,8 @@ _generate_singleton_methods(
|
|
|
593
653
|
[
|
|
594
654
|
"login",
|
|
595
655
|
|
|
656
|
+
"list_users",
|
|
657
|
+
|
|
596
658
|
"get_activities",
|
|
597
659
|
|
|
598
660
|
"get_activity",
|
|
@@ -611,5 +673,10 @@ _generate_singleton_methods(
|
|
|
611
673
|
|
|
612
674
|
"get_sports",
|
|
613
675
|
"get_tags",
|
|
676
|
+
|
|
677
|
+
"switch_user",
|
|
678
|
+
"switch_back",
|
|
679
|
+
"delegated_client",
|
|
680
|
+
"principal_client",
|
|
614
681
|
]
|
|
615
682
|
)
|
sweatstack/openapi_schemas.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# generated by datamodel-codegen:
|
|
2
2
|
# filename: openapi.json
|
|
3
|
-
# timestamp: 2025-
|
|
3
|
+
# timestamp: 2025-03-03T13:21:38+00:00
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
@@ -59,16 +59,6 @@ class BodySaveOrUpdateIntegrationProviderTenantsTenantIdIntegrationProvidersInte
|
|
|
59
59
|
redirect_url: str = Field(..., title='Redirect Url')
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
class BodyTokenOauthTokenPost(BaseModel):
|
|
63
|
-
grant_type: Literal['authorization_code'] = Field(..., title='Grant Type')
|
|
64
|
-
code: str = Field(..., title='Code')
|
|
65
|
-
client_id: str = Field(..., title='Client Id')
|
|
66
|
-
client_secret: Optional[str] = Field(None, title='Client Secret')
|
|
67
|
-
code_verifier: Optional[str] = Field(None, title='Code Verifier')
|
|
68
|
-
refresh_token: Optional[str] = Field(None, title='Refresh Token')
|
|
69
|
-
tz_offset: Optional[int] = Field(0, title='Tz Offset')
|
|
70
|
-
|
|
71
|
-
|
|
72
62
|
class BodyUpdateApplicationApplicationsApplicationIdPut(BaseModel):
|
|
73
63
|
name: str = Field(..., title='Name')
|
|
74
64
|
description: str = Field(..., title='Description')
|
|
@@ -122,7 +112,6 @@ class GarminFileTypes(Enum):
|
|
|
122
112
|
class GrantType(Enum):
|
|
123
113
|
authorization_code = 'authorization_code'
|
|
124
114
|
refresh_token = 'refresh_token'
|
|
125
|
-
implicit = 'implicit'
|
|
126
115
|
|
|
127
116
|
|
|
128
117
|
class HeartRateSummary(BaseModel):
|
|
@@ -166,6 +155,8 @@ class Scope(Enum):
|
|
|
166
155
|
data_read = 'data:read'
|
|
167
156
|
data_write = 'data:write'
|
|
168
157
|
admin = 'admin'
|
|
158
|
+
openid = 'openid'
|
|
159
|
+
profile = 'profile'
|
|
169
160
|
|
|
170
161
|
|
|
171
162
|
class Smo2Summary(BaseModel):
|
|
@@ -233,13 +224,12 @@ class TemperatureSummary(BaseModel):
|
|
|
233
224
|
|
|
234
225
|
class TokenRequest(BaseModel):
|
|
235
226
|
grant_type: GrantType
|
|
227
|
+
client_id: Optional[str] = Field(None, title='Client Id')
|
|
236
228
|
code: Optional[str] = Field(None, title='Code')
|
|
229
|
+
client_secret: Optional[str] = Field(None, title='Client Secret')
|
|
230
|
+
code_verifier: Optional[str] = Field(None, title='Code Verifier')
|
|
237
231
|
refresh_token: Optional[str] = Field(None, title='Refresh Token')
|
|
238
|
-
|
|
239
|
-
client_secret: Optional[SecretStr] = Field(None, title='Client Secret')
|
|
240
|
-
tz_offset: Optional[int] = Field(
|
|
241
|
-
3600, description='Timezone DST offset in seconds', title='Tz Offset'
|
|
242
|
-
)
|
|
232
|
+
tz_offset: Optional[int] = Field(0, title='Tz Offset')
|
|
243
233
|
|
|
244
234
|
|
|
245
235
|
class TokenResponse(BaseModel):
|
|
@@ -248,6 +238,7 @@ class TokenResponse(BaseModel):
|
|
|
248
238
|
expires_in: int = Field(..., title='Expires In')
|
|
249
239
|
refresh_token: str = Field(..., title='Refresh Token')
|
|
250
240
|
scope: Optional[str] = Field(None, title='Scope')
|
|
241
|
+
id_token: Optional[str] = Field(None, title='Id Token')
|
|
251
242
|
|
|
252
243
|
|
|
253
244
|
class TraceCreateOrUpdate(BaseModel):
|
|
@@ -259,6 +250,22 @@ class TraceCreateOrUpdate(BaseModel):
|
|
|
259
250
|
speed: Optional[confloat(ge=0.0)] = Field(None, title='Speed')
|
|
260
251
|
heart_rate: Optional[conint(ge=0)] = Field(None, title='Heart Rate')
|
|
261
252
|
tags: Optional[List[str]] = Field(None, title='Tags')
|
|
253
|
+
sport: Optional[Sport] = None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class UserInfoResponse(BaseModel):
|
|
257
|
+
sub: str = Field(..., title='Sub')
|
|
258
|
+
given_name: Optional[str] = Field(None, title='Given Name')
|
|
259
|
+
family_name: Optional[str] = Field(None, title='Family Name')
|
|
260
|
+
name: str = Field(..., title='Name')
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class UserSummary(BaseModel):
|
|
264
|
+
id: str = Field(..., title='Id')
|
|
265
|
+
first_name: Optional[str] = Field(..., title='First Name')
|
|
266
|
+
last_name: Optional[str] = Field(..., title='Last Name')
|
|
267
|
+
scopes: List[Scope] = Field(..., title='Scopes')
|
|
268
|
+
display_name: str = Field(..., title='Display Name')
|
|
262
269
|
|
|
263
270
|
|
|
264
271
|
class ValidationError(BaseModel):
|
|
@@ -283,10 +290,16 @@ class BodyAuthorizeOauthAuthorizePost(BaseModel):
|
|
|
283
290
|
redirect_uri: Optional[str] = Field(None, title='Redirect Uri')
|
|
284
291
|
scopes: List[Scope] = Field(..., min_length=1, title='Scopes')
|
|
285
292
|
state: Optional[str] = Field(None, title='State')
|
|
293
|
+
nonce: Optional[str] = Field(None, title='Nonce')
|
|
286
294
|
code_challenge: Optional[str] = Field(None, title='Code Challenge')
|
|
287
295
|
code_challenge_method: Optional[str] = Field(None, title='Code Challenge Method')
|
|
288
296
|
|
|
289
297
|
|
|
298
|
+
class DelegatedTokenRequest(BaseModel):
|
|
299
|
+
sub: str = Field(..., title='Sub')
|
|
300
|
+
scopes: List[Scope] = Field(..., title='Scopes')
|
|
301
|
+
|
|
302
|
+
|
|
290
303
|
class GarminActivityFileData(BaseModel):
|
|
291
304
|
userId: str = Field(..., title='Userid')
|
|
292
305
|
userAccessToken: str = Field(..., title='Useraccesstoken')
|
|
@@ -362,6 +375,7 @@ class TraceDetails(BaseModel):
|
|
|
362
375
|
heart_rate: Optional[conint(ge=0)] = Field(None, title='Heart Rate')
|
|
363
376
|
lap: Optional[Lap] = None
|
|
364
377
|
activity: Optional[ActivitySummary] = None
|
|
378
|
+
sport: Optional[Sport] = None
|
|
365
379
|
|
|
366
380
|
|
|
367
381
|
ActivityDetails.model_rebuild()
|
sweatstack/schemas.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sweatstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.19.0
|
|
4
4
|
Summary: The official Python client for SweatStack
|
|
5
5
|
Author-email: Aart Goossens <aart@gssns.io>
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -8,10 +8,10 @@ Requires-Dist: httpx>=0.28.1
|
|
|
8
8
|
Requires-Dist: pandas>=2.2.3
|
|
9
9
|
Requires-Dist: pyarrow>=19.0.0
|
|
10
10
|
Requires-Dist: pydantic>=2.10.5
|
|
11
|
-
Provides-Extra:
|
|
12
|
-
Requires-Dist: ipython>=8.31.0; extra == '
|
|
13
|
-
Requires-Dist: jupyterlab>=4.3.4; extra == '
|
|
14
|
-
Requires-Dist: matplotlib; extra == '
|
|
11
|
+
Provides-Extra: jupyter
|
|
12
|
+
Requires-Dist: ipython>=8.31.0; extra == 'jupyter'
|
|
13
|
+
Requires-Dist: jupyterlab>=4.3.4; extra == 'jupyter'
|
|
14
|
+
Requires-Dist: matplotlib>=3.10.0; extra == 'jupyter'
|
|
15
15
|
Provides-Extra: streamlit
|
|
16
16
|
Requires-Dist: streamlit>=1.42.0; extra == 'streamlit'
|
|
17
17
|
Description-Content-Type: text/markdown
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
sweatstack/__init__.py,sha256=tiVfgKlswRPaDMEy0gA7u8rveqEYZTA_kyB9lJ3J6Sc,21
|
|
2
2
|
sweatstack/cli.py,sha256=N1NWOgEZR2yaJvIXxo9qvp_jFlypZYb0nujpbVNYQ6A,720
|
|
3
|
-
sweatstack/client.py,sha256=
|
|
3
|
+
sweatstack/client.py,sha256=KmKy84XULwTXpAr3IpNcke8tSbjEySUtQR3Qlio2r0Y,22256
|
|
4
4
|
sweatstack/constants.py,sha256=fGO6ksOv5HeISv9lHRoYm4besW1GTveXS8YD3K0ljg0,41
|
|
5
5
|
sweatstack/ipython_init.py,sha256=zBGUlMFkdpLvsNpOpwrNaKRUpUZhzaICvH8ODJgMPcI,229
|
|
6
6
|
sweatstack/jupyterlab_oauth2_startup.py,sha256=eZ6xi0Sa4hO4vUanimq0SqjduHtiywCURSDNWk_I-7s,1200
|
|
7
|
-
sweatstack/openapi_schemas.py,sha256=
|
|
7
|
+
sweatstack/openapi_schemas.py,sha256=XlgiL7qkfcfoDHcQrIm9e5hvhY98onC0QmZWG69bl-s,13441
|
|
8
8
|
sweatstack/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
sweatstack/schemas.py,sha256=
|
|
9
|
+
sweatstack/schemas.py,sha256=CArM9jgrJDBUT-ZIIiGN_insqaEGV43MX134Ezf4jyA,103
|
|
10
10
|
sweatstack/streamlit.py,sha256=F5oQdWkcJ76Um5fRqFT5QrjpEz8v3OaiH9kMQOYktgo,4466
|
|
11
11
|
sweatstack/sweatshell.py,sha256=MYLNcWbOdceqKJ3S0Pe8dwHXEeYsGJNjQoYUXpMTftA,333
|
|
12
12
|
sweatstack/utils.py,sha256=HtR1NNCKus59vfgfaCSFS-tHA11mtdcuUx5lS6ZX58g,1773
|
|
13
|
-
sweatstack
|
|
14
|
-
sweatstack-0.
|
|
15
|
-
sweatstack-0.
|
|
16
|
-
sweatstack-0.
|
|
13
|
+
sweatstack/Sweat Stack examples/Getting started.ipynb,sha256=k2hiSffWecoQ0VxjdpDcgFzBXDQiYEebhnAYlu8cgX8,6335204
|
|
14
|
+
sweatstack-0.19.0.dist-info/METADATA,sha256=vaOKnv63psIaEa6E-ZygllTXhqfILyIhw2L4w4BqhnE,2962
|
|
15
|
+
sweatstack-0.19.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
16
|
+
sweatstack-0.19.0.dist-info/entry_points.txt,sha256=kCzOUQI3dqbTpEYqtgYDeiKFaqaA7BMlV6D24BMzCFU,208
|
|
17
|
+
sweatstack-0.19.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|