sweatstack 0.36.0__tar.gz → 0.38.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.36.0 → sweatstack-0.38.0}/PKG-INFO +1 -1
  2. {sweatstack-0.36.0 → sweatstack-0.38.0}/pyproject.toml +1 -1
  3. sweatstack-0.38.0/src/sweatstack/schemas.py +104 -0
  4. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/streamlit.py +22 -5
  5. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/utils.py +1 -26
  6. sweatstack-0.36.0/src/sweatstack/schemas.py +0 -5
  7. {sweatstack-0.36.0 → sweatstack-0.38.0}/.gitignore +0 -0
  8. {sweatstack-0.36.0 → sweatstack-0.38.0}/.python-version +0 -0
  9. {sweatstack-0.36.0 → sweatstack-0.38.0}/DEVELOPMENT.md +0 -0
  10. {sweatstack-0.36.0 → sweatstack-0.38.0}/Makefile +0 -0
  11. {sweatstack-0.36.0 → sweatstack-0.38.0}/README.md +0 -0
  12. {sweatstack-0.36.0 → sweatstack-0.38.0}/playground/.ipynb_checkpoints/Untitled-checkpoint.ipynb +0 -0
  13. {sweatstack-0.36.0 → sweatstack-0.38.0}/playground/README.md +0 -0
  14. {sweatstack-0.36.0 → sweatstack-0.38.0}/playground/Sweat Stack examples/Getting started.ipynb +0 -0
  15. {sweatstack-0.36.0 → sweatstack-0.38.0}/playground/Untitled.ipynb +0 -0
  16. {sweatstack-0.36.0 → sweatstack-0.38.0}/playground/hello.py +0 -0
  17. {sweatstack-0.36.0 → sweatstack-0.38.0}/playground/pyproject.toml +0 -0
  18. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/Sweat Stack examples/Getting started.ipynb +0 -0
  19. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/__init__.py +0 -0
  20. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/cli.py +0 -0
  21. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/client.py +0 -0
  22. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/constants.py +0 -0
  23. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/ipython_init.py +0 -0
  24. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/jupyterlab_oauth2_startup.py +0 -0
  25. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/openapi_schemas.py +0 -0
  26. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/py.typed +0 -0
  27. {sweatstack-0.36.0 → sweatstack-0.38.0}/src/sweatstack/sweatshell.py +0 -0
  28. {sweatstack-0.36.0 → sweatstack-0.38.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sweatstack
3
- Version: 0.36.0
3
+ Version: 0.38.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.36.0"
3
+ version = "0.38.0"
4
4
  description = "The official Python client for SweatStack"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,104 @@
1
+ from enum import Enum
2
+ from typing import List, Union
3
+
4
+ from .openapi_schemas import (
5
+ ActivityDetails, ActivitySummary, Metric, Scope, Sport,
6
+ TraceDetails, UserInfoResponse, UserSummary
7
+ )
8
+
9
+
10
+ def parent_sport(sport: Sport) -> Sport:
11
+ """Returns the parent sport of a given sport.
12
+
13
+ For sports with a hierarchical structure (e.g., 'cycling.road'), returns the parent sport
14
+ ('cycling'). If the sport has no parent (is already a root sport), returns the sport itself.
15
+
16
+ Args:
17
+ sport (Sport): The sport enum value to find the parent of.
18
+
19
+ Returns:
20
+ Sport: The parent sport enum value, or the original sport if it has no parent.
21
+ """
22
+ parts = sport.value.split(".")
23
+ if len(parts) == 1:
24
+ return sport
25
+ return sport.__class__(".".join(parts[:-1]))
26
+
27
+
28
+ def root_sport(sport: Sport) -> Sport:
29
+ """Returns the root sport of a given sport.
30
+
31
+ For sports with a hierarchical structure (e.g., 'cycling.road' or 'cycling.road.gravel'),
32
+ returns the root sport ('cycling'). If the sport is already a root sport, returns the sport itself.
33
+
34
+ Args:
35
+ sport (Sport): The sport enum value to find the root of.
36
+
37
+ Returns:
38
+ Sport: The root sport enum value.
39
+ """
40
+ return sport.__class__(sport.value.split(".")[0])
41
+
42
+
43
+ def is_root_sport(sport: Sport) -> bool:
44
+ """Determines if a sport is a root sport.
45
+
46
+ A root sport is one that doesn't have a parent sport in the hierarchy
47
+ (e.g., 'cycling' is a root sport, while 'cycling.road' is not).
48
+
49
+ Args:
50
+ sport (Sport): The sport enum value to check.
51
+
52
+ Returns:
53
+ bool: True if the sport is a root sport, False otherwise.
54
+ """
55
+ return sport == root_sport(sport)
56
+
57
+
58
+ def is_sub_sport_of(sport: Sport, sport_or_sports: Union[Sport, List[Sport]]) -> bool:
59
+ """Determines if a sport is a sub-sport of another sport or list of sports.
60
+
61
+ For example, 'cycling.road' is a sub-sport of 'cycling', but not of 'running'.
62
+
63
+ Args:
64
+ sport (Sport): The sport to check.
65
+ sport_or_sports (Union[Sport, List[Sport]]): A sport or list of sports to check against.
66
+
67
+ Returns:
68
+ bool: True if the sport is a sub-sport of any of the provided sports, False otherwise.
69
+
70
+ Raises:
71
+ ValueError: If sport_or_sports is not a Sport or a list of Sports.
72
+ """
73
+ if isinstance(sport_or_sports, Sport):
74
+ return sport.value.startswith(sport_or_sports.value)
75
+ elif isinstance(sport_or_sports, (list, tuple)):
76
+ return any(is_sub_sport_of(sport, sport) for sport in sport_or_sports)
77
+ else:
78
+ raise ValueError(f"Invalid type for sport_or_sports: {type(sport_or_sports)}")
79
+
80
+
81
+ def display_name(sport: Sport) -> str:
82
+ """Returns a human-readable display name for a sport.
83
+
84
+ This function converts a Sport enum value into a formatted string suitable for display.
85
+
86
+ Args:
87
+ sport (Sport): The sport enum value to format.
88
+
89
+ Returns:
90
+ str: A human-readable display name for the sport.
91
+ """
92
+ parts = sport.value.split(".")
93
+ base_sport = parts[0]
94
+ base_sport = base_sport.replace("_", " ")
95
+ if len(parts) == 1:
96
+ return base_sport
97
+ the_rest = " ".join(parts[1:]).replace("_", " ")
98
+ return f"{base_sport} ({the_rest})"
99
+
100
+
101
+ Sport.root_sport = root_sport
102
+ Sport.parent_sport = parent_sport
103
+ Sport.is_sub_sport_of = is_sub_sport_of
104
+ Sport.display_name = display_name
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import urllib.parse
3
3
  from datetime import date
4
+ from typing import List, Union
4
5
  try:
5
6
  import streamlit as st
6
7
  except ImportError:
@@ -13,21 +14,37 @@ import httpx
13
14
 
14
15
  from .client import Client
15
16
  from .constants import DEFAULT_URL
16
- from .schemas import Metric, Sport
17
+ from .schemas import Metric, Scope, Sport
17
18
  from .utils import format_sport
18
19
 
19
20
  class StreamlitAuth:
20
- def __init__(self, client_id=None, client_secret=None, scope=None, redirect_uri=None):
21
+ def __init__(
22
+ self,
23
+ client_id=None,
24
+ client_secret=None,
25
+ scopes: List[Union[str, Scope]]=None,
26
+ redirect_uri=None,
27
+ ):
21
28
  """
22
29
  Args:
23
30
  client_id: The client ID to use. If not provided, the SWEATSTACK_CLIENT_ID environment variable will be used.
24
31
  client_secret: The client secret to use. If not provided, the SWEATSTACK_CLIENT_SECRET environment variable will be used.
25
- scope: The scope to use. If not provided, the SWEATSTACK_SCOPE environment variable will be used.
32
+ scopes: The scopes to use. If not provided, the SWEATSTACK_SCOPES environment variable will be used. Defaults to data:read, profile.
26
33
  redirect_uri: The redirect URI to use. If not provided, the SWEATSTACK_REDIRECT_URI environment variable will be used.
27
34
  """
28
35
  self.client_id = client_id or os.environ.get("SWEATSTACK_CLIENT_ID")
29
36
  self.client_secret = client_secret or os.environ.get("SWEATSTACK_CLIENT_SECRET")
30
- self.scope = scope or os.environ.get("SWEATSTACK_SCOPE")
37
+
38
+ if scopes is not None:
39
+ self.scopes = [Scope(scope.strip().lower()) if isinstance(scope, str) else scope
40
+ for scope in scopes] if scopes else []
41
+ elif os.environ.get("SWEATSTACK_SCOPES"):
42
+ scopes = os.environ.get("SWEATSTACK_SCOPES").split(",")
43
+ self.scopes = [Scope(scope.strip().lower()) if isinstance(scope, str) else scope
44
+ for scope in scopes]
45
+ else:
46
+ self.scopes = [Scope.data_read, Scope.profile]
47
+
31
48
  self.redirect_uri = redirect_uri or os.environ.get("SWEATSTACK_REDIRECT_URI")
32
49
 
33
50
  self.api_key = st.session_state.get("sweatstack_api_key")
@@ -81,7 +98,7 @@ class StreamlitAuth:
81
98
  params = {
82
99
  "client_id": self.client_id,
83
100
  "redirect_uri": self.redirect_uri,
84
- "scope": "data:read",
101
+ "scope": ",".join([scope.value for scope in self.scopes]),
85
102
  "prompt": "none",
86
103
  }
87
104
  path = "/oauth/authorize"
@@ -52,29 +52,4 @@ def make_dataframe_streamlit_compatible(df: pd.DataFrame) -> pd.DataFrame:
52
52
  lambda x: x.value if isinstance(x, Enum) else x
53
53
  )
54
54
 
55
- return df_copy if df_copy is not None else df
56
-
57
-
58
- def format_sport(sport: Sport):
59
- """Formats a sport enum value into a human-readable string.
60
-
61
- This function takes a Sport enum and converts it to a formatted string representation.
62
- For example, "cycling.road" becomes "cycling (road)" and "running" remains "running".
63
- Underscores in sport names are replaced with spaces.
64
-
65
- Args:
66
- sport: A Sport enum value to format.
67
-
68
- Returns:
69
- str: A human-readable formatted string representation of the sport.
70
- """
71
- parts = sport.value.split(".")
72
- base_sport = parts[0]
73
- base_sport = base_sport.replace("_", " ")
74
-
75
- if len(parts) == 1:
76
- return base_sport
77
-
78
- remainder = " ".join(parts[1:]).replace("_", " ")
79
-
80
- return f"{base_sport} ({remainder})"
55
+ return df_copy if df_copy is not None else df
@@ -1,5 +0,0 @@
1
- from enum import Enum
2
-
3
- from .openapi_schemas import (
4
- ActivityDetails, ActivitySummary, Metric, Sport, TraceDetails, UserInfoResponse, UserSummary
5
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes