sweatstack 0.27.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.27.0 → sweatstack-0.30.0}/PKG-INFO +1 -1
  2. {sweatstack-0.27.0 → sweatstack-0.30.0}/pyproject.toml +1 -1
  3. {sweatstack-0.27.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.27.0 → sweatstack-0.30.0}/src/sweatstack/streamlit.py +108 -8
  6. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/utils.py +16 -1
  7. sweatstack-0.27.0/src/sweatstack/schemas.py +0 -3
  8. {sweatstack-0.27.0 → sweatstack-0.30.0}/.gitignore +0 -0
  9. {sweatstack-0.27.0 → sweatstack-0.30.0}/.python-version +0 -0
  10. {sweatstack-0.27.0 → sweatstack-0.30.0}/DEVELOPMENT.md +0 -0
  11. {sweatstack-0.27.0 → sweatstack-0.30.0}/Makefile +0 -0
  12. {sweatstack-0.27.0 → sweatstack-0.30.0}/README.md +0 -0
  13. {sweatstack-0.27.0 → sweatstack-0.30.0}/playground/.ipynb_checkpoints/Untitled-checkpoint.ipynb +0 -0
  14. {sweatstack-0.27.0 → sweatstack-0.30.0}/playground/README.md +0 -0
  15. {sweatstack-0.27.0 → sweatstack-0.30.0}/playground/Sweat Stack examples/Getting started.ipynb +0 -0
  16. {sweatstack-0.27.0 → sweatstack-0.30.0}/playground/Untitled.ipynb +0 -0
  17. {sweatstack-0.27.0 → sweatstack-0.30.0}/playground/hello.py +0 -0
  18. {sweatstack-0.27.0 → sweatstack-0.30.0}/playground/pyproject.toml +0 -0
  19. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/Sweat Stack examples/Getting started.ipynb +0 -0
  20. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/__init__.py +0 -0
  21. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/cli.py +0 -0
  22. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/constants.py +0 -0
  23. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/ipython_init.py +0 -0
  24. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/jupyterlab_oauth2_startup.py +0 -0
  25. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/openapi_schemas.py +0 -0
  26. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/py.typed +0 -0
  27. {sweatstack-0.27.0 → sweatstack-0.30.0}/src/sweatstack/sweatshell.py +0 -0
  28. {sweatstack-0.27.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.27.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.27.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):
@@ -88,6 +89,11 @@ class StreamlitAuth:
88
89
 
89
90
  return authorization_url
90
91
 
92
+ def _set_api_key(self, api_key):
93
+ self.api_key = api_key
94
+ st.session_state["sweatstack_api_key"] = api_key
95
+ self.client = Client(self.api_key, streamlit_compatible=True)
96
+
91
97
  def _exchange_token(self, code):
92
98
  token_data = {
93
99
  "grant_type": "authorization_code",
@@ -107,10 +113,7 @@ class StreamlitAuth:
107
113
  raise Exception(f"SweatStack Python login failed. Please try again.") from e
108
114
  token_response = response.json()
109
115
 
110
- self.api_key = token_response.get("access_token")
111
- st.session_state["sweatstack_api_key"] = self.api_key
112
-
113
- self.client = Client(self.api_key, streamlit_compatible=True)
116
+ self._set_api_key(token_response.get("access_token"))
114
117
 
115
118
  return
116
119
 
@@ -128,4 +131,101 @@ class StreamlitAuth:
128
131
  st.query_params.clear()
129
132
  st.rerun()
130
133
  else:
131
- self._show_sweatstack_login()
134
+ self._show_sweatstack_login()
135
+
136
+ def select_user(self):
137
+ self.switch_to_principal_user()
138
+ other_users = self.client.get_users()
139
+ selected_user = st.selectbox(
140
+ "Select a user",
141
+ other_users,
142
+ format_func=lambda user: user.display_name,
143
+ )
144
+ self.client.switch_user(selected_user)
145
+ self._set_api_key(self.client.api_key)
146
+
147
+ return selected_user
148
+
149
+ def switch_to_principal_user(self):
150
+ self.client.switch_back()
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