sweatstack 0.28.0__tar.gz → 0.30.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.
Files changed (28) hide show
  1. {sweatstack-0.28.0 → sweatstack-0.30.0}/PKG-INFO +1 -1
  2. {sweatstack-0.28.0 → sweatstack-0.30.0}/pyproject.toml +1 -1
  3. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/client.py +1 -0
  4. sweatstack-0.30.0/src/sweatstack/schemas.py +5 -0
  5. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/streamlit.py +88 -7
  6. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/utils.py +16 -1
  7. sweatstack-0.28.0/src/sweatstack/schemas.py +0 -3
  8. {sweatstack-0.28.0 → sweatstack-0.30.0}/.gitignore +0 -0
  9. {sweatstack-0.28.0 → sweatstack-0.30.0}/.python-version +0 -0
  10. {sweatstack-0.28.0 → sweatstack-0.30.0}/DEVELOPMENT.md +0 -0
  11. {sweatstack-0.28.0 → sweatstack-0.30.0}/Makefile +0 -0
  12. {sweatstack-0.28.0 → sweatstack-0.30.0}/README.md +0 -0
  13. {sweatstack-0.28.0 → sweatstack-0.30.0}/playground/.ipynb_checkpoints/Untitled-checkpoint.ipynb +0 -0
  14. {sweatstack-0.28.0 → sweatstack-0.30.0}/playground/README.md +0 -0
  15. {sweatstack-0.28.0 → sweatstack-0.30.0}/playground/Sweat Stack examples/Getting started.ipynb +0 -0
  16. {sweatstack-0.28.0 → sweatstack-0.30.0}/playground/Untitled.ipynb +0 -0
  17. {sweatstack-0.28.0 → sweatstack-0.30.0}/playground/hello.py +0 -0
  18. {sweatstack-0.28.0 → sweatstack-0.30.0}/playground/pyproject.toml +0 -0
  19. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/Sweat Stack examples/Getting started.ipynb +0 -0
  20. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/__init__.py +0 -0
  21. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/cli.py +0 -0
  22. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/constants.py +0 -0
  23. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/ipython_init.py +0 -0
  24. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/jupyterlab_oauth2_startup.py +0 -0
  25. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/openapi_schemas.py +0 -0
  26. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/py.typed +0 -0
  27. {sweatstack-0.28.0 → sweatstack-0.30.0}/src/sweatstack/sweatshell.py +0 -0
  28. {sweatstack-0.28.0 → sweatstack-0.30.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sweatstack
3
- Version: 0.28.0
3
+ Version: 0.30.0
4
4
  Summary: The official Python client for SweatStack
5
5
  Author-email: Aart Goossens <aart@gssns.io>
6
6
  Requires-Python: >=3.9
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sweatstack"
3
- version = "0.28.0"
3
+ version = "0.30.0"
4
4
  description = "The official Python client for SweatStack"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -297,6 +297,7 @@ class Client(OAuth2Mixin, DelegationMixin):
297
297
  if end is not None:
298
298
  params["end"] = end.isoformat()
299
299
  if sports is not None:
300
+ sports = [sport.value if isinstance(sport, Sport) else sport for sport in sports]
300
301
  params["sports"] = sports
301
302
  if tags is not None:
302
303
  params["tags"] = tags
@@ -0,0 +1,5 @@
1
+ from enum import Enum
2
+
3
+ from .openapi_schemas import (
4
+ ActivityDetails, ActivitySummary, Metric, Sport, TraceDetails, UserSummary
5
+ )
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  import urllib.parse
3
-
3
+ from datetime import date
4
4
  try:
5
5
  import streamlit as st
6
6
  except ImportError:
@@ -10,10 +10,11 @@ except ImportError:
10
10
  "pip install 'sweatstack[streamlit]'\n\n"
11
11
  )
12
12
  import httpx
13
- from sweatstack import Client
14
13
 
14
+ from .client import Client
15
15
  from .constants import DEFAULT_URL
16
-
16
+ from .schemas import Metric, Sport
17
+ from .utils import format_sport
17
18
 
18
19
  class StreamlitAuth:
19
20
  def __init__(self, client_id=None, client_secret=None, scope=None, redirect_uri=None):
@@ -132,8 +133,8 @@ class StreamlitAuth:
132
133
  else:
133
134
  self._show_sweatstack_login()
134
135
 
135
- def switch_user(self):
136
- self.switch_back()
136
+ def select_user(self):
137
+ self.switch_to_principal_user()
137
138
  other_users = self.client.get_users()
138
139
  selected_user = st.selectbox(
139
140
  "Select a user",
@@ -145,6 +146,86 @@ class StreamlitAuth:
145
146
 
146
147
  return selected_user
147
148
 
148
- def switch_back(self):
149
+ def switch_to_principal_user(self):
149
150
  self.client.switch_back()
150
- self._set_api_key(self.client.api_key)
151
+ self._set_api_key(self.client.api_key)
152
+
153
+ def select_activity(
154
+ self,
155
+ *,
156
+ start: date | None = None,
157
+ end: date | None = None,
158
+ sports: list[Sport] | None = None,
159
+ tags: list[str] | None = None,
160
+ limit: int | None = 100,
161
+ ):
162
+ """
163
+ Select an activity from the user's activities.
164
+ """
165
+
166
+ activities = self.client.get_activities(
167
+ start=start,
168
+ end=end,
169
+ sports=sports,
170
+ tags=tags,
171
+ limit=limit,
172
+ )
173
+ selected_activity = st.selectbox(
174
+ "Select an activity",
175
+ activities,
176
+ format_func=lambda activity: f"{activity.start.date().isoformat()} {format_sport(activity.sport)}",
177
+ )
178
+ return selected_activity
179
+
180
+ def select_sport(self, only_root: bool = False, allow_multiple: bool = False, only_available: bool = True):
181
+ if only_available:
182
+ sports = self.client.get_sports(only_root)
183
+ else:
184
+ if only_root:
185
+ sports = [sport for sport in Sport if "." not in sport.value]
186
+ else:
187
+ sports = Sport
188
+
189
+ if allow_multiple:
190
+ selected_sport = st.multiselect(
191
+ "Select sports",
192
+ sports,
193
+ format_func=format_sport,
194
+ )
195
+ else:
196
+ selected_sport = st.selectbox(
197
+ "Select a sport",
198
+ sports,
199
+ format_func=format_sport,
200
+ )
201
+ return selected_sport
202
+
203
+ def select_tag(self, allow_multiple: bool = False):
204
+ tags = self.client.get_tags()
205
+ if allow_multiple:
206
+ selected_tag = st.multiselect(
207
+ "Select tags",
208
+ tags,
209
+ )
210
+ else:
211
+ selected_tag = st.selectbox(
212
+ "Select a tag",
213
+ tags,
214
+ format_func=lambda tag: tag or "-",
215
+ )
216
+ return selected_tag
217
+
218
+ def select_metric(self, allow_multiple: bool = False):
219
+ if allow_multiple:
220
+ selected_metric = st.multiselect(
221
+ "Select metrics",
222
+ Metric,
223
+ format_func=lambda metric: metric.value,
224
+ )
225
+ else:
226
+ selected_metric = st.selectbox(
227
+ "Select a metric",
228
+ Metric,
229
+ format_func=lambda metric: metric.value,
230
+ )
231
+ return selected_metric
@@ -4,6 +4,8 @@ from enum import Enum
4
4
 
5
5
  import pandas as pd
6
6
 
7
+ from .schemas import Sport
8
+
7
9
 
8
10
  def decode_jwt_body(jwt: str) -> dict:
9
11
  payload = jwt.split(".")[1]
@@ -50,4 +52,17 @@ def make_dataframe_streamlit_compatible(df: pd.DataFrame) -> pd.DataFrame:
50
52
  lambda x: x.value if isinstance(x, Enum) else x
51
53
  )
52
54
 
53
- return df_copy if df_copy is not None else df
55
+ return df_copy if df_copy is not None else df
56
+
57
+
58
+ def format_sport(sport: Sport):
59
+ parts = sport.value.split(".")
60
+ base_sport = parts[0]
61
+ base_sport = base_sport.replace("_", " ")
62
+
63
+ if len(parts) == 1:
64
+ return base_sport
65
+
66
+ remainder = " ".join(parts[1:]).replace("_", " ")
67
+
68
+ return f"{base_sport} ({remainder})"
@@ -1,3 +0,0 @@
1
- from .openapi_schemas import (
2
- ActivityDetails, ActivitySummary, Sport, TraceDetails, UserSummary
3
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes