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/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 ActivityDetails, ActivitySummary, Sport, TraceDetails
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 Client(OAuth2Mixin):
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
  )
@@ -1,6 +1,6 @@
1
1
  # generated by datamodel-codegen:
2
2
  # filename: openapi.json
3
- # timestamp: 2025-02-13T12:03:28+00:00
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
- client_id: Optional[str] = Field(None, title='Client Id')
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,3 +1,3 @@
1
1
  from .openapi_schemas import (
2
- ActivityDetails, ActivitySummary, Sport, TraceDetails
2
+ ActivityDetails, ActivitySummary, Sport, TraceDetails, UserSummary
3
3
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sweatstack
3
- Version: 0.17.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: jupyterlab
12
- Requires-Dist: ipython>=8.31.0; extra == 'jupyterlab'
13
- Requires-Dist: jupyterlab>=4.3.4; extra == 'jupyterlab'
14
- Requires-Dist: matplotlib; extra == 'jupyterlab'
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=A6PGQFd2hCupJ1UMjjvRyUx18mRQe-ZaHgE8snGKY_Q,20008
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=vtYHa6A0kADFbd_jK1O7Kbn8YkbPfzaIwsRDDYjYeSA,13038
7
+ sweatstack/openapi_schemas.py,sha256=XlgiL7qkfcfoDHcQrIm9e5hvhY98onC0QmZWG69bl-s,13441
8
8
  sweatstack/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- sweatstack/schemas.py,sha256=CdkeV6IRmIuvxae7C5dz-hVlb6hkzEYfqKHHgVJprmY,90
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-0.17.0.dist-info/METADATA,sha256=fCrgu9umxH57CPonX4eKReH9OXaMpNbJ3pz2-3dy8oM,2966
14
- sweatstack-0.17.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
- sweatstack-0.17.0.dist-info/entry_points.txt,sha256=kCzOUQI3dqbTpEYqtgYDeiKFaqaA7BMlV6D24BMzCFU,208
16
- sweatstack-0.17.0.dist-info/RECORD,,
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,,