sweatstack 0.14.0__tar.gz → 0.14.2__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.14.0
3
+ Version: 0.14.2
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.14.0"
3
+ version = "0.14.2"
4
4
  description = "The official Python client for SweatStack"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -19,7 +19,7 @@ import pandas as pd
19
19
 
20
20
  from .constants import DEFAULT_URL
21
21
  from .schemas import ActivityDetails, ActivitySummary, Sport, TraceDetails
22
- from .utils import decode_jwt_body
22
+ from .utils import decode_jwt_body, make_dataframe_streamlit_compatible
23
23
 
24
24
 
25
25
  AUTH_SUCCESSFUL_RESPONSE = "<!DOCTYPE html><html><body><h1>Authentication successful. You can now close this window.</h1></body></html>"
@@ -109,10 +109,12 @@ class Client(OAuth2Mixin):
109
109
  api_key: str | None = None,
110
110
  refresh_token: str | None = None,
111
111
  url: str | None = None,
112
+ streamlit_compatible: bool = False,
112
113
  ):
113
114
  self.api_key = api_key
114
115
  self.refresh_token = refresh_token
115
116
  self.url = url
117
+ self.streamlit_compatible = streamlit_compatible
116
118
 
117
119
  def _do_token_refresh(self, tz_offset: int) -> str:
118
120
  with self._http_client() as client:
@@ -247,6 +249,12 @@ class Client(OAuth2Mixin):
247
249
  params["limit"] = min(default_limit, limit - num_returned)
248
250
  params["offset"] += default_limit
249
251
 
252
+ def _postprocess_dataframe(self, df: pd.DataFrame) -> pd.DataFrame:
253
+ if self.streamlit_compatible:
254
+ return make_dataframe_streamlit_compatible(df)
255
+ else:
256
+ return df
257
+
250
258
  def get_activities(
251
259
  self,
252
260
  *,
@@ -265,7 +273,8 @@ class Client(OAuth2Mixin):
265
273
  limit=limit,
266
274
  )
267
275
  if as_dataframe:
268
- return pd.DataFrame([activity.model_dump() for activity in generator])
276
+ df = pd.DataFrame([activity.model_dump() for activity in generator])
277
+ return self._postprocess_dataframe(df)
269
278
  else:
270
279
  return generator
271
280
 
@@ -308,7 +317,8 @@ class Client(OAuth2Mixin):
308
317
 
309
318
  response.raise_for_status()
310
319
 
311
- return pd.read_parquet(BytesIO(response.content))
320
+ df = pd.read_parquet(BytesIO(response.content))
321
+ return self._postprocess_dataframe(df)
312
322
 
313
323
  def get_activity_mean_max(
314
324
  self,
@@ -325,7 +335,8 @@ class Client(OAuth2Mixin):
325
335
  },
326
336
  )
327
337
  response.raise_for_status()
328
- return pd.read_parquet(BytesIO(response.content))
338
+ df = pd.read_parquet(BytesIO(response.content))
339
+ return self._postprocess_dataframe(df)
329
340
 
330
341
  def get_latest_activity_data(
331
342
  self,
@@ -379,7 +390,8 @@ class Client(OAuth2Mixin):
379
390
  )
380
391
  response.raise_for_status()
381
392
 
382
- return pd.read_parquet(BytesIO(response.content))
393
+ df = pd.read_parquet(BytesIO(response.content))
394
+ return self._postprocess_dataframe(df)
383
395
 
384
396
  def get_longitudinal_mean_max(
385
397
  self,
@@ -405,7 +417,8 @@ class Client(OAuth2Mixin):
405
417
  )
406
418
  response.raise_for_status()
407
419
 
408
- return pd.read_parquet(BytesIO(response.content))
420
+ df = pd.read_parquet(BytesIO(response.content))
421
+ return self._postprocess_dataframe(df)
409
422
 
410
423
  def _get_traces_generator(
411
424
  self,
@@ -488,7 +501,7 @@ class Client(OAuth2Mixin):
488
501
  if "lap" in data.columns:
489
502
  data = self._normalize_dataframe_column(data, "lap")
490
503
 
491
- return data
504
+ return self._postprocess_dataframe(data)
492
505
 
493
506
  def create_trace(
494
507
  self,
@@ -61,7 +61,7 @@ class StreamlitAuth:
61
61
  "code": code,
62
62
  }
63
63
  response = httpx.post(
64
- f"{DEFAULT_URL}/oauth/token",
64
+ f"{DEFAULT_URL}/api/v1/oauth/token",
65
65
  data=token_data,
66
66
  )
67
67
  try:
@@ -73,7 +73,7 @@ class StreamlitAuth:
73
73
  self.api_key = token_response.get("access_token")
74
74
  st.session_state["sweatstack_api_key"] = self.api_key
75
75
 
76
- self.client = Client(self.api_key)
76
+ self.client = Client(self.api_key, streamlit_compatible=True)
77
77
 
78
78
  return
79
79
 
@@ -0,0 +1,53 @@
1
+ import base64
2
+ import json
3
+ from enum import Enum
4
+
5
+ import pandas as pd
6
+
7
+
8
+ def decode_jwt_body(jwt: str) -> dict:
9
+ payload = jwt.split(".")[1]
10
+
11
+ padding = len(payload) % 4
12
+ if padding:
13
+ payload += "=" * (4 - padding)
14
+
15
+ decoded = base64.urlsafe_b64decode(payload)
16
+ return json.loads(decoded)
17
+
18
+
19
+ def make_dataframe_streamlit_compatible(df):
20
+ """
21
+ Converts all columns containing enum values in a DataFrame to their respective string values.
22
+
23
+ Args:
24
+ df (pd.DataFrame): The DataFrame to process
25
+
26
+ Returns:
27
+ pd.DataFrame: A new DataFrame with enum values converted to strings
28
+ """
29
+ df_copy = None
30
+
31
+ for column in df.columns:
32
+ # Check if the column contains enum values
33
+ if df[column].dtype == 'object':
34
+ # First check if it's a list column of enums
35
+ if df[column].notna().any():
36
+ first_value = df[column].dropna().iloc[0]
37
+
38
+ # Handle list of enums
39
+ if isinstance(first_value, list) and first_value and isinstance(first_value[0], Enum):
40
+ if df_copy is None:
41
+ df_copy = df.copy()
42
+ df_copy[column] = df_copy[column].apply(
43
+ lambda x: [item.value if isinstance(item, Enum) else item for item in x] if isinstance(x, list) else x
44
+ )
45
+ # Handle single enum values
46
+ elif isinstance(first_value, Enum):
47
+ if df_copy is None:
48
+ df_copy = df.copy()
49
+ df_copy[column] = df_copy[column].apply(
50
+ lambda x: x.value if isinstance(x, Enum) else x
51
+ )
52
+
53
+ return df_copy if df_copy is not None else df
@@ -1,13 +0,0 @@
1
- import base64
2
- import json
3
-
4
-
5
- def decode_jwt_body(jwt: str) -> dict:
6
- payload = jwt.split(".")[1]
7
-
8
- padding = len(payload) % 4
9
- if padding:
10
- payload += "=" * (4 - padding)
11
-
12
- decoded = base64.urlsafe_b64decode(payload)
13
- return json.loads(decoded)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes