sweatstack 0.12.0__py3-none-any.whl → 0.13.0__py3-none-any.whl
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.
- sweatstack/client.py +1 -1
- sweatstack/constants.py +1 -0
- sweatstack/streamlit.py +116 -0
- {sweatstack-0.12.0.dist-info → sweatstack-0.13.0.dist-info}/METADATA +34 -2
- {sweatstack-0.12.0.dist-info → sweatstack-0.13.0.dist-info}/RECORD +7 -5
- {sweatstack-0.12.0.dist-info → sweatstack-0.13.0.dist-info}/WHEEL +0 -0
- {sweatstack-0.12.0.dist-info → sweatstack-0.13.0.dist-info}/entry_points.txt +0 -0
sweatstack/client.py
CHANGED
|
@@ -20,9 +20,9 @@ import pandas as pd
|
|
|
20
20
|
from .schemas import ActivityDetails, ActivitySummary, Sport, TraceDetails
|
|
21
21
|
from .utils import decode_jwt_body
|
|
22
22
|
|
|
23
|
+
|
|
23
24
|
AUTH_SUCCESSFUL_RESPONSE = "<!DOCTYPE html><html><body><h1>Authentication successful. You can now close this window.</h1></body></html>"
|
|
24
25
|
OAUTH2_CLIENT_ID = "5382f68b0d254378"
|
|
25
|
-
DEFAULT_URL = "https://app.sweatstack.no"
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class OAuth2Mixin:
|
sweatstack/constants.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DEFAULT_URL = "https://app.sweatstack.no"
|
sweatstack/streamlit.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import base64
|
|
3
|
+
import os
|
|
4
|
+
import secrets
|
|
5
|
+
import urllib.parse
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
import streamlit as st
|
|
9
|
+
except ImportError:
|
|
10
|
+
raise ImportError(
|
|
11
|
+
"Streamlit features require streamlit to be installed. "
|
|
12
|
+
"You can install it with:\n\n"
|
|
13
|
+
"pip install 'sweatstack[streamlit]'\n\n"
|
|
14
|
+
)
|
|
15
|
+
import httpx
|
|
16
|
+
from streamlit_cookies_controller import CookieController
|
|
17
|
+
from sweatstack import Client
|
|
18
|
+
|
|
19
|
+
from .constants import DEFAULT_URL
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
cookie_controller = CookieController()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class StreamlitAuth:
|
|
26
|
+
def __init__(self, set_env_var=False, client_id=None, client_secret=None, scope=None, redirect_uri=None):
|
|
27
|
+
"""
|
|
28
|
+
Args:
|
|
29
|
+
set_env_var: Whether to set the SWEATSTACK_API_KEY environment variable. Default is False.
|
|
30
|
+
client_id: The client ID to use. If not provided, the SWEATSTACK_CLIENT_ID environment variable will be used.
|
|
31
|
+
client_secret: The client secret to use. If not provided, the SWEATSTACK_CLIENT_SECRET environment variable will be used.
|
|
32
|
+
scope: The scope to use. If not provided, the SWEATSTACK_SCOPE environment variable will be used.
|
|
33
|
+
redirect_uri: The redirect URI to use. If not provided, the SWEATSTACK_REDIRECT_URI environment variable will be used.
|
|
34
|
+
"""
|
|
35
|
+
self.set_env_var = set_env_var
|
|
36
|
+
self.client_id = client_id or os.environ.get("SWEATSTACK_CLIENT_ID")
|
|
37
|
+
self.client_secret = client_secret or os.environ.get("SWEATSTACK_CLIENT_SECRET")
|
|
38
|
+
self.scope = scope or os.environ.get("SWEATSTACK_SCOPE")
|
|
39
|
+
self.redirect_uri = redirect_uri or os.environ.get("SWEATSTACK_REDIRECT_URI")
|
|
40
|
+
|
|
41
|
+
self.api_key = cookie_controller.get("sweatstack_api_key")
|
|
42
|
+
self.client = Client(self.api_key)
|
|
43
|
+
|
|
44
|
+
if self.api_key and self.set_env_var:
|
|
45
|
+
os.environ["SWEATSTACK_API_KEY"] = self.api_key
|
|
46
|
+
|
|
47
|
+
def _show_sweatstack_logout(self):
|
|
48
|
+
if st.button("Logout"):
|
|
49
|
+
self.api_key = None
|
|
50
|
+
self.client = Client()
|
|
51
|
+
cookie_controller.remove("sweatstack_api_key")
|
|
52
|
+
if self.set_env_var:
|
|
53
|
+
os.environ.pop("SWEATSTACK_API_KEY")
|
|
54
|
+
|
|
55
|
+
def _show_sweatstack_login(self):
|
|
56
|
+
st.link_button("Login", self._get_authorization_url_implicit())
|
|
57
|
+
|
|
58
|
+
def _get_authorization_url_implicit(self):
|
|
59
|
+
code_verifier = secrets.token_urlsafe(32)
|
|
60
|
+
cookie_controller.set("code_verifier", code_verifier)
|
|
61
|
+
code_challenge = hashlib.sha256(code_verifier.encode("ascii")).digest()
|
|
62
|
+
code_challenge = base64.urlsafe_b64encode(code_challenge).rstrip(b"=").decode("ascii")
|
|
63
|
+
|
|
64
|
+
params = {
|
|
65
|
+
"client_id": self.client_id,
|
|
66
|
+
"redirect_uri": self.redirect_uri,
|
|
67
|
+
"code_challenge": code_challenge,
|
|
68
|
+
"scope": "data:read",
|
|
69
|
+
}
|
|
70
|
+
path = "/oauth/authorize"
|
|
71
|
+
authorization_url = urllib.parse.urljoin(DEFAULT_URL, path + "?" + urllib.parse.urlencode(params))
|
|
72
|
+
|
|
73
|
+
return authorization_url
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _exchange_token_implicit(self, code):
|
|
77
|
+
code_verifier = cookie_controller.get("code_verifier")
|
|
78
|
+
token_data = {
|
|
79
|
+
"grant_type": "authorization_code",
|
|
80
|
+
"client_id": self.client_id,
|
|
81
|
+
"code": code,
|
|
82
|
+
"code_verifier": code_verifier
|
|
83
|
+
}
|
|
84
|
+
response = httpx.post(
|
|
85
|
+
f"{DEFAULT_URL}/oauth/token",
|
|
86
|
+
data=token_data,
|
|
87
|
+
)
|
|
88
|
+
try:
|
|
89
|
+
response.raise_for_status()
|
|
90
|
+
except httpx.HTTPStatusError as e:
|
|
91
|
+
raise Exception(f"SweatStack Python login failed. Please try again.") from e
|
|
92
|
+
token_response = response.json()
|
|
93
|
+
|
|
94
|
+
self.api_key = token_response.get("access_token")
|
|
95
|
+
cookie_controller.set("sweatstack_api_key", self.api_key)
|
|
96
|
+
if self.set_env_var:
|
|
97
|
+
os.environ["SWEATSTACK_API_KEY"] = self.api_key
|
|
98
|
+
|
|
99
|
+
self.client = Client(self.api_key)
|
|
100
|
+
|
|
101
|
+
cookie_controller.remove("code_verifier")
|
|
102
|
+
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
def is_authenticated(self):
|
|
106
|
+
return self.api_key is not None
|
|
107
|
+
|
|
108
|
+
def authenticate(self):
|
|
109
|
+
if self.is_authenticated():
|
|
110
|
+
self._show_sweatstack_logout()
|
|
111
|
+
elif code := st.query_params.get("code"):
|
|
112
|
+
self._exchange_token_implicit(code)
|
|
113
|
+
st.query_params.clear()
|
|
114
|
+
st.rerun()
|
|
115
|
+
else:
|
|
116
|
+
self._show_sweatstack_login()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sweatstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.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
|
|
@@ -12,6 +12,9 @@ Provides-Extra: jupyterlab
|
|
|
12
12
|
Requires-Dist: ipython>=8.31.0; extra == 'jupyterlab'
|
|
13
13
|
Requires-Dist: jupyterlab>=4.3.4; extra == 'jupyterlab'
|
|
14
14
|
Requires-Dist: matplotlib; extra == 'jupyterlab'
|
|
15
|
+
Provides-Extra: streamlit
|
|
16
|
+
Requires-Dist: streamlit-cookies-controller>=0.0.4; extra == 'streamlit'
|
|
17
|
+
Requires-Dist: streamlit>=1.42.0; extra == 'streamlit'
|
|
15
18
|
Description-Content-Type: text/markdown
|
|
16
19
|
|
|
17
20
|
# SweatStack Python client library
|
|
@@ -82,4 +85,33 @@ activities = client.list_activities()
|
|
|
82
85
|
|
|
83
86
|
Although both interfaces are feature-equivalent, they serve different purposes:
|
|
84
87
|
- The singleton interface is the default and recommended interface. It is intended for most use cases and is the easiest to use.
|
|
85
|
-
- The class-based interface is intended for more advanced use cases, such as when you need to authenticate multiple users at the same time or in multi-threaded applications.
|
|
88
|
+
- The class-based interface is intended for more advanced use cases, such as when you need to authenticate multiple users at the same time or in multi-threaded applications.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## Streamlit integration
|
|
92
|
+
|
|
93
|
+
The `sweatstack.streamlit` module provides a Streamlit integration for SweatStack. This requires the optional `streamlit` dependency that can be installed with:
|
|
94
|
+
```
|
|
95
|
+
uv pip install 'sweatstack[streamlit]'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `StreamlitAuth` class is a Streamlit component that handles the OAuth2 authentication flow. It provides a `st.authenticate()` function that can be used to authenticate the user.
|
|
99
|
+
|
|
100
|
+
Example usage:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from sweatstack.streamlit import StreamlitAuth
|
|
104
|
+
|
|
105
|
+
auth = StreamlitAuth()
|
|
106
|
+
|
|
107
|
+
with st.sidebar:
|
|
108
|
+
st.authenticate()
|
|
109
|
+
|
|
110
|
+
if not auth.is_authenticated():
|
|
111
|
+
st.write("User is not authenticated")
|
|
112
|
+
st.stop()
|
|
113
|
+
|
|
114
|
+
st.write("User is authenticated")
|
|
115
|
+
|
|
116
|
+
auth.client.get_latest_activity()
|
|
117
|
+
```
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
sweatstack/__init__.py,sha256=tiVfgKlswRPaDMEy0gA7u8rveqEYZTA_kyB9lJ3J6Sc,21
|
|
2
2
|
sweatstack/cli.py,sha256=N1NWOgEZR2yaJvIXxo9qvp_jFlypZYb0nujpbVNYQ6A,720
|
|
3
|
-
sweatstack/client.py,sha256
|
|
3
|
+
sweatstack/client.py,sha256=gPWThdmLj3s6pKafzEfzV19PDb5x8AKLhuVlO50nmv8,19090
|
|
4
|
+
sweatstack/constants.py,sha256=fGO6ksOv5HeISv9lHRoYm4besW1GTveXS8YD3K0ljg0,41
|
|
4
5
|
sweatstack/ipython_init.py,sha256=zBGUlMFkdpLvsNpOpwrNaKRUpUZhzaICvH8ODJgMPcI,229
|
|
5
6
|
sweatstack/jupyterlab_oauth2_startup.py,sha256=eZ6xi0Sa4hO4vUanimq0SqjduHtiywCURSDNWk_I-7s,1200
|
|
6
7
|
sweatstack/openapi_schemas.py,sha256=vtYHa6A0kADFbd_jK1O7Kbn8YkbPfzaIwsRDDYjYeSA,13038
|
|
7
8
|
sweatstack/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
9
|
sweatstack/schemas.py,sha256=CdkeV6IRmIuvxae7C5dz-hVlb6hkzEYfqKHHgVJprmY,90
|
|
10
|
+
sweatstack/streamlit.py,sha256=cF2LZa2HY43lxwUby7XR5U71h7oibMtAurSVoxNDheM,4294
|
|
9
11
|
sweatstack/sweatshell.py,sha256=MYLNcWbOdceqKJ3S0Pe8dwHXEeYsGJNjQoYUXpMTftA,333
|
|
10
12
|
sweatstack/utils.py,sha256=0DcvHV1EOHQnN5OlfYo2DRrw8a9-6YkxTjLVCfi5ylE,277
|
|
11
|
-
sweatstack-0.
|
|
12
|
-
sweatstack-0.
|
|
13
|
-
sweatstack-0.
|
|
14
|
-
sweatstack-0.
|
|
13
|
+
sweatstack-0.13.0.dist-info/METADATA,sha256=NTEPb9QMqBXEtiiZulqlnR0DPJHr2ElgGPdamJqG7Vc,3039
|
|
14
|
+
sweatstack-0.13.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
sweatstack-0.13.0.dist-info/entry_points.txt,sha256=kCzOUQI3dqbTpEYqtgYDeiKFaqaA7BMlV6D24BMzCFU,208
|
|
16
|
+
sweatstack-0.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|