sweatstack 0.17.0__tar.gz → 0.18.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sweatstack
3
- Version: 0.17.0
3
+ Version: 0.18.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sweatstack"
3
- version = "0.17.0"
3
+ version = "0.18.0"
4
4
  description = "The official Python client for SweatStack"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -18,7 +18,9 @@ import httpx
18
18
  import pandas as pd
19
19
 
20
20
  from .constants import DEFAULT_URL
21
- from .schemas import ActivityDetails, ActivitySummary, Sport, TraceDetails
21
+ from .schemas import (
22
+ ActivityDetails, ActivitySummary, Sport, TraceDetails, UserSummary
23
+ )
22
24
  from .utils import decode_jwt_body, make_dataframe_streamlit_compatible
23
25
 
24
26
 
@@ -62,6 +64,7 @@ class OAuth2Mixin:
62
64
  "redirect_uri": redirect_uri,
63
65
  "code_challenge": code_challenge,
64
66
  "scope": "data:read",
67
+ "prompt": "none",
65
68
  }
66
69
  base_url = self.url
67
70
  path = "/oauth/authorize"
@@ -83,7 +86,7 @@ class OAuth2Mixin:
83
86
  "grant_type": "authorization_code",
84
87
  "client_id": OAUTH2_CLIENT_ID,
85
88
  "code": server.code,
86
- "code_verifier": code_verifier
89
+ "code_verifier": code_verifier,
87
90
  }
88
91
  response = httpx.post(
89
92
  f"{self.url}/api/v1/oauth/token",
@@ -103,7 +106,55 @@ class OAuth2Mixin:
103
106
  raise Exception("SweatStack Python login failed. Please try again.")
104
107
 
105
108
 
106
- class Client(OAuth2Mixin):
109
+ class DelegationMixin:
110
+ def _get_delegated_token(self, user_id: str):
111
+ with self._http_client() as client:
112
+ response = client.post(
113
+ "/api/v1/oauth/delegated-token",
114
+ json={"sub": user_id},
115
+ )
116
+ response.raise_for_status()
117
+
118
+ return response.json()
119
+
120
+ def switch_user(self, user_id: str):
121
+ token_response = self._get_delegated_token(user_id)
122
+ self.api_key = token_response["access_token"]
123
+ self.refresh_token = token_response["refresh_token"]
124
+
125
+ def _get_principal_token(self):
126
+ with self._http_client() as client:
127
+ response = client.get(
128
+ "/api/v1/oauth/principal-token",
129
+ )
130
+ response.raise_for_status()
131
+ return response.json()
132
+
133
+ def switch_back(self):
134
+ token_response = self._get_principal_token()
135
+ self.api_key = token_response["access_token"]
136
+ self.refresh_token = token_response["refresh_token"]
137
+
138
+ def delegated_client(self, user_id: str):
139
+ token_response = self._get_delegated_token(user_id)
140
+ return self.__class__(
141
+ api_key=token_response["access_token"],
142
+ refresh_token=token_response["refresh_token"],
143
+ url=self.url,
144
+ streamlit_compatible=self.streamlit_compatible,
145
+ )
146
+
147
+ def principal_client(self):
148
+ token_response = self._get_principal_token()
149
+ return self.__class__(
150
+ api_key=token_response["access_token"],
151
+ refresh_token=token_response["refresh_token"],
152
+ url=self.url,
153
+ streamlit_compatible=self.streamlit_compatible,
154
+ )
155
+
156
+
157
+ class Client(OAuth2Mixin, DelegationMixin):
107
158
  def __init__(
108
159
  self,
109
160
  api_key: str | None = None,
@@ -553,6 +604,13 @@ class Client(OAuth2Mixin):
553
604
  response.raise_for_status()
554
605
  return response.json()
555
606
 
607
+ def list_users(self) -> list[UserSummary]:
608
+ with self._http_client() as client:
609
+ response = client.get(
610
+ url="/api/v1/users/",
611
+ )
612
+ response.raise_for_status()
613
+ return [UserSummary.model_validate(user) for user in response.json()]
556
614
 
557
615
  _default_client = Client()
558
616
 
@@ -593,6 +651,8 @@ _generate_singleton_methods(
593
651
  [
594
652
  "login",
595
653
 
654
+ "list_users",
655
+
596
656
  "get_activities",
597
657
 
598
658
  "get_activity",
@@ -611,5 +671,10 @@ _generate_singleton_methods(
611
671
 
612
672
  "get_sports",
613
673
  "get_tags",
674
+
675
+ "switch_user",
676
+ "switch_back",
677
+ "delegated_client",
678
+ "principal_client",
614
679
  ]
615
680
  )
@@ -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()
@@ -0,0 +1,3 @@
1
+ from .openapi_schemas import (
2
+ ActivityDetails, ActivitySummary, Sport, TraceDetails, UserSummary
3
+ )
@@ -1857,7 +1857,7 @@ wheels = [
1857
1857
 
1858
1858
  [[package]]
1859
1859
  name = "sweatstack"
1860
- version = "0.16.0"
1860
+ version = "0.17.0"
1861
1861
  source = { editable = "." }
1862
1862
  dependencies = [
1863
1863
  { name = "httpx" },
@@ -1,3 +0,0 @@
1
- from .openapi_schemas import (
2
- ActivityDetails, ActivitySummary, Sport, TraceDetails
3
- )
File without changes
File without changes
File without changes
File without changes
File without changes