sweatstack 0.92.0__tar.gz → 0.94.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.
- {sweatstack-0.92.0 → sweatstack-0.94.0}/AGENTS.md +6 -2
- {sweatstack-0.92.0 → sweatstack-0.94.0}/CHANGELOG.md +175 -5
- {sweatstack-0.92.0 → sweatstack-0.94.0}/DEVELOPMENT.md +3 -1
- {sweatstack-0.92.0 → sweatstack-0.94.0}/PKG-INFO +5 -2
- {sweatstack-0.92.0 → sweatstack-0.94.0}/README.md +3 -1
- sweatstack-0.94.0/UPGRADING.md +62 -0
- sweatstack-0.94.0/plans/010_credentials_one_source.md +497 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/pyproject.toml +2 -1
- {sweatstack-0.92.0 → sweatstack-0.94.0}/skills/sweatstack-python/SKILL.md +7 -2
- {sweatstack-0.92.0 → sweatstack-0.94.0}/skills/sweatstack-python/api.md +9 -3
- {sweatstack-0.92.0 → sweatstack-0.94.0}/skills/sweatstack-python/streamlit.md +7 -5
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/_renames.py +3 -1
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/client.py +122 -66
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/fastapi/dependencies.py +2 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/openapi_schemas.py +41 -25
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/oauth.py +9 -3
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/profile.py +12 -8
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/schemas.py +1 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/streamlit.py +128 -31
- sweatstack-0.94.0/tests/conftest.py +26 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_account_status.py +17 -7
- sweatstack-0.94.0/tests/test_changelog.py +73 -0
- sweatstack-0.94.0/tests/test_credentials.py +261 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_identity.py +1 -5
- sweatstack-0.94.0/tests/test_streamlit_auth.py +199 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_transport.py +3 -5
- {sweatstack-0.92.0 → sweatstack-0.94.0}/uv.lock +1 -1
- {sweatstack-0.92.0 → sweatstack-0.94.0}/.git-blame-ignore-revs +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/.github/workflows/ci.yml +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/.gitignore +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/.gitleaks.toml +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/.python-version +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/CONTRIBUTING.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/LICENSE +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/Makefile +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/evals/__init__.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/evals/agent/__init__.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/evals/agent/agent.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/evals/agent/cases.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/evals/agent/evaluators.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/evals/agent/harness.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/evals/agent/lifecycle.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/evals/agent/run.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/examples/fastapi_webhooks_example.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/examples/send_webhook.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/001a_tests.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/001b_metadata.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/001c_dailies.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/002_TYPED_EXCEPTIONS.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/003_trace_test_linking.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/004_codebase_hygiene.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/005_ost_sport_bridge.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/006_output_backends.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/007_account_status_userinfo_portal.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/008_sdk_documentation.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/plans/009_road_to_1_0.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/skills/sweatstack-python/data-models.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/skills/sweatstack-python/fastapi.md +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/__init__.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/_docs.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/_frames.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/_transport.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/cli.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/constants.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/exceptions.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/fastapi/__init__.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/fastapi/access_token_cache.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/fastapi/config.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/fastapi/models.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/fastapi/routes.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/fastapi/session.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/fastapi/token_stores.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/fastapi/webhooks.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/py.typed +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/__init__.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/_app_metadata.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/_base.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/activities.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/dailies.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/portal.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/teams.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/tests.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/traces.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/resources/users.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/src/sweatstack/utils.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/__init__.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_access_token_cache.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_dailies.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_docs_check.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_docstrings.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_dtype_conversion.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_exceptions.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_frames.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_longitudinal_mean_max_after.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_metadata.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_output.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_portal.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_public_hygiene.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_public_surface.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_renames.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_resources.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_segmentation.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_skill.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_sport_ost.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_teams.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_tests.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_trace_test_linking.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_webhooks.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_wire_names.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/test_write_timezone_validation.py +0 -0
- {sweatstack-0.92.0 → sweatstack-0.94.0}/tests/typing/output_types.py +0 -0
|
@@ -258,9 +258,13 @@ fields unchanged (users) is an `update` and sends only the fields given.
|
|
|
258
258
|
|
|
259
259
|
Until 1.0, breaking changes are allowed when they ship with an upgrade path. Every one needs:
|
|
260
260
|
|
|
261
|
-
1. A `### Upgrading` section in the CHANGELOG entry, with an old-to-new table.
|
|
261
|
+
1. A `### Upgrading` section in the CHANGELOG entry, with an old-to-new table. Mark a row
|
|
262
|
+
whose change raises nothing as **Silent:**. Wrap the section in
|
|
263
|
+
`<!-- --8<-- [start:upgrading-X-Y] -->` / `[end:...]` markers (the docs include it), and add
|
|
264
|
+
a `## X.Y` section with that include to the docs' Upgrading page. `tests/test_changelog.py`
|
|
265
|
+
checks that every entry saying "breaking" has the section and its markers.
|
|
262
266
|
2. A copy-paste migration prompt for coding agents in the same section, for any change bigger
|
|
263
|
-
than a rename or two.
|
|
267
|
+
than a rename or two. The generic prompt in `UPGRADING.md` covers the rest.
|
|
264
268
|
3. For a removed method or attribute, an entry in `_renames.REMOVED_IN`, so calling it raises an
|
|
265
269
|
`AttributeError` that names the replacement. Entries are permanent.
|
|
266
270
|
`tests/test_renames.py` checks each one against the client, the module and the CHANGELOG.
|
|
@@ -5,6 +5,83 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
Until 1.0 a minor release may contain breaking changes. Every one has an **Upgrading** section
|
|
9
|
+
with a table from old to new. The procedure for moving across several releases, and a prompt
|
|
10
|
+
for a coding agent, are in `UPGRADING.md` next to this file and at
|
|
11
|
+
https://docs.sweatstack.no/learn/python/upgrading/.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## [0.94.0] - 2026-10-12
|
|
15
|
+
|
|
16
|
+
A client's tokens come from one source. Upgrade every app that other people sign in to.
|
|
17
|
+
|
|
18
|
+
### Security
|
|
19
|
+
|
|
20
|
+
- A `Client` in a process shared by several users (Streamlit, FastAPI, any server) could pick
|
|
21
|
+
up a refresh token, or a whole token pair, that another user's client had saved on that
|
|
22
|
+
machine, and act as that user. Credentials now come from a single source: the arguments you
|
|
23
|
+
pass, else the environment, else the tokens saved by `authenticate()`. A client never
|
|
24
|
+
combines halves from different sources, and writes the saved tokens only when they came from
|
|
25
|
+
there. The Streamlit and FastAPI integrations never read or write saved tokens.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Streamlit apps behind a proxy no longer fail after the access token expires. With a proxy
|
|
30
|
+
that forwards `X-SweatStack-Refresh-Token` (the SweatStack Streamlit template does), the
|
|
31
|
+
session renews its own token and keeps the renewed pair across reruns. Streamlit apps in
|
|
32
|
+
OAuth mode refresh once per token lifetime instead of once per rerun.
|
|
33
|
+
- A refresh response that carries a new refresh token replaces the old one.
|
|
34
|
+
- `StreamlitAuth.select_user()` with a user that gets no refresh token (admin access) no longer
|
|
35
|
+
keeps the previous user's refresh token.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- `Client(..., ambient_credentials=False)` builds a client that never reads the environment or
|
|
40
|
+
the saved tokens and never writes them. Use it for per-user clients in your own server.
|
|
41
|
+
- `oauth.exchange_code()` no longer saves the tokens on the machine by default; pass
|
|
42
|
+
`persist=True` for that. `authenticate()` still saves them.
|
|
43
|
+
- `StreamlitAuth.behind_proxy()` checks token expiry again. Without a forwarded refresh token,
|
|
44
|
+
calls after expiry raise `SweatStackTokenRefreshError` with a reload hint, where the API
|
|
45
|
+
returned a 401.
|
|
46
|
+
|
|
47
|
+
<!-- --8<-- [start:upgrading-0-94] -->
|
|
48
|
+
### Upgrading
|
|
49
|
+
|
|
50
|
+
| 0.93 | 0.94 |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `Client(api_key=...)` without a refresh token renewed with `SWEATSTACK_REFRESH_TOKEN` or the saved refresh token | **Silent:** it never borrows one, and raises `SweatStackTokenRefreshError` on expiry. Pass `refresh_token=` too, or pass neither to use the environment or the saved tokens |
|
|
53
|
+
| `oauth.exchange_code(...)` saved the tokens on this machine | **Silent:** pass `persist=True` to keep that |
|
|
54
|
+
| `StreamlitAuth.behind_proxy()` sent the handshake token as it was after expiry (a 401 from the API) | Forward `X-SweatStack-Refresh-Token` from the proxy and the session renews itself. Without it, calls after expiry raise `SweatStackTokenRefreshError` with a reload hint |
|
|
55
|
+
<!-- --8<-- [end:upgrading-0-94] -->
|
|
56
|
+
|
|
57
|
+
## [0.93.0] - 2026-10-09
|
|
58
|
+
|
|
59
|
+
Issues are only about the data your app uses. Requires the SweatStack API release that adds
|
|
60
|
+
`capabilities` to applications (see the platform changelog).
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- **BREAKING: `issue.status` is an `IssueStatus`**, `action_required` or `syncing`, on
|
|
65
|
+
`oauth.userinfo()` and `profile.status()`. The `unavailable` issues
|
|
66
|
+
(`activity_history_unavailable`, `dailies_unavailable`, `workouts_unavailable`) are gone: what
|
|
67
|
+
no connected source provides is in `capabilities`, where `unavailable` means the user could
|
|
68
|
+
still connect a source that provides it. Three new codes, each `action_required` with a
|
|
69
|
+
`destination`: `activities_blocked_upstream`, `dailies_no_source`, `workouts_no_source`. A
|
|
70
|
+
client on 0.90.0 or later accepts them without an upgrade (`StatusIssueCode` is open).
|
|
71
|
+
- `ApplicationCreate.capabilities` and `ApplicationResponse.capabilities`: the capabilities an
|
|
72
|
+
app declares it uses (`list[Capability]`; omit it for the default, `activities` and
|
|
73
|
+
`activity_history`). SweatStack reports issues only for these.
|
|
74
|
+
|
|
75
|
+
<!-- --8<-- [start:upgrading-0-93] -->
|
|
76
|
+
### Upgrading
|
|
77
|
+
|
|
78
|
+
| 0.92 | 0.93 |
|
|
79
|
+
|---|---|
|
|
80
|
+
| `issue.status` is a `CapabilityStatus` | `IssueStatus`: `action_required` or `syncing`. The `.value` strings are unchanged |
|
|
81
|
+
| Branching on `issue.code` being `activity_history_unavailable`, `dailies_unavailable` or `workouts_unavailable` | **Silent:** these codes are never sent. Read `profile.status().capabilities` instead, where `unavailable` means no connected source provides it |
|
|
82
|
+
| Only the known issue codes | Also `activities_blocked_upstream`, `dailies_no_source` and `workouts_no_source`: each `action_required` with a `destination`; handle them like any other issue |
|
|
83
|
+
| An app that uses dailies or workouts | Declare them in the app's `capabilities` (`ApplicationCreate.capabilities`); issues are reported only for declared capabilities |
|
|
84
|
+
<!-- --8<-- [end:upgrading-0-93] -->
|
|
8
85
|
|
|
9
86
|
## [0.92.0] - 2026-10-08
|
|
10
87
|
|
|
@@ -25,12 +102,15 @@ release that replaces `action_url` with `destination` (see the platform changelo
|
|
|
25
102
|
close the page. `return_url` must equal or sit under one of your app's registered redirect
|
|
26
103
|
URIs.
|
|
27
104
|
|
|
105
|
+
<!-- --8<-- [start:upgrading-0-92] -->
|
|
28
106
|
### Upgrading
|
|
29
107
|
|
|
30
|
-
|
|
|
108
|
+
| 0.91 | 0.92 |
|
|
31
109
|
|---|---|
|
|
32
110
|
| `user.issue.action_url` | `client.portal.sessions.create(user.issue.destination, return_url=...).url` |
|
|
33
111
|
| `Client(client_id=..., client_secret=...).portal.sessions.create(...)` | the same call on the client holding the user's token |
|
|
112
|
+
| `portal.sessions.create(destination)` without `return_url` linking back to your app | **Silent:** the Portal tells the user to close the page; pass `return_url=` (under a registered redirect URI) to link back |
|
|
113
|
+
<!-- --8<-- [end:upgrading-0-92] -->
|
|
34
114
|
|
|
35
115
|
## [0.91.0] - 2026-10-08
|
|
36
116
|
|
|
@@ -125,7 +205,8 @@ removed name raises an `AttributeError` that names its replacement.
|
|
|
125
205
|
|
|
126
206
|
Removed names raise an `AttributeError` naming the replacement, so running your code points at
|
|
127
207
|
each change. The module-level functions moved the same way: `sweatstack.get_activities()` is
|
|
128
|
-
`sweatstack.activities.list()`.
|
|
208
|
+
`sweatstack.activities.list()`. Only `from sweatstack import get_activities` fails with Python's
|
|
209
|
+
plain `ImportError`; look the name up in the table.
|
|
129
210
|
|
|
130
211
|
| 0.90 | 0.91 |
|
|
131
212
|
|---|---|
|
|
@@ -276,6 +357,17 @@ and TypeError (a renamed or keyword-only argument). Do not add compatibility shi
|
|
|
276
357
|
- **BREAKING:** `by` on `get_longitudinal_mean_max`, and its `DeprecationWarning`. Every curve is
|
|
277
358
|
duration-oriented.
|
|
278
359
|
|
|
360
|
+
<!-- --8<-- [start:upgrading-0-90] -->
|
|
361
|
+
### Upgrading
|
|
362
|
+
|
|
363
|
+
| 0.89 | 0.90 |
|
|
364
|
+
|---|---|
|
|
365
|
+
| A mean-max frame with one row per intensity: the metric value, and the `duration` it was held | **Silent:** one row per duration, 19 by default: `duration`, the metric (`power` or `speed`) and `start`; the longitudinal curve adds `activity_id`, `sport` and `after`. The column names still exist, so code keeps running with the axes swapped: `df[df.power >= 300]` becomes `df[df.duration == 300]` |
|
|
366
|
+
| The full curve | `durations="all"`, or a list of seconds |
|
|
367
|
+
| `get_activity_mean_max(id, "power", True)` or `segmentation=True` | Drop the argument |
|
|
368
|
+
| `get_longitudinal_mean_max(..., by="duration")` or `by="intensity"` | Drop `by=`; every curve is by duration |
|
|
369
|
+
<!-- --8<-- [end:upgrading-0-90] -->
|
|
370
|
+
|
|
279
371
|
## [0.89.0] - 2026-09-28
|
|
280
372
|
|
|
281
373
|
Frames on your terms. Every method that returns a collection takes `output=`:
|
|
@@ -341,6 +433,7 @@ Four breaking changes come with it; the upgrade is mechanical, see **Upgrading**
|
|
|
341
433
|
previously re-indexed by the client.
|
|
342
434
|
- pandas frames keep the float64 / nanosecond dtype policy. Polars and Arrow do not upcast.
|
|
343
435
|
|
|
436
|
+
<!-- --8<-- [start:upgrading-0-89] -->
|
|
344
437
|
### Upgrading
|
|
345
438
|
|
|
346
439
|
1. Change the install line: `uv add "sweatstack[pandas]"` (or `[polars]`, or `[arrow]` for
|
|
@@ -352,6 +445,10 @@ Four breaking changes come with it; the upgrade is mechanical, see **Upgrading**
|
|
|
352
445
|
4. Search for `.index`, `.loc[<timestamp>]`, `.resample(`, `.plot()` on frames from the
|
|
353
446
|
time-series, mean-max, AWD and dailies methods. Where the index mattered, add
|
|
354
447
|
`.set_index("timestamp")` (or the metric name, or `"date"`) right after the call.
|
|
448
|
+
5. **Silent:** `as_dataframe=True` on `get_activities` and `get_traces` kept nested fields as
|
|
449
|
+
dict columns (`df["summary"][0]["power"]`). `output="pandas"` flattens them to dotted
|
|
450
|
+
columns (`df["summary.power.mean"]`); Polars and Arrow give structs (`df.unnest("summary")`).
|
|
451
|
+
<!-- --8<-- [end:upgrading-0-89] -->
|
|
355
452
|
|
|
356
453
|
## [0.88.0] - 2026-08-06
|
|
357
454
|
|
|
@@ -365,12 +462,30 @@ Four breaking changes come with it; the upgrade is mechanical, see **Upgrading**
|
|
|
365
462
|
|
|
366
463
|
- Token refresh no longer sends a `tz` field. The server derives all timezone information from the offsets stored with each record, so the client has nothing to pass.
|
|
367
464
|
|
|
465
|
+
<!-- --8<-- [start:upgrading-0-88] -->
|
|
466
|
+
### Upgrading
|
|
467
|
+
|
|
468
|
+
| 0.87 | 0.88 |
|
|
469
|
+
|---|---|
|
|
470
|
+
| `create_trace(timestamp=datetime(2026, 1, 1, 10, 0))`, and `start`/`end` on `create_test`, `update_test`, `update_trace` | Aware: `datetime(2026, 1, 1, 10, 0, tzinfo=ZoneInfo("Europe/Amsterdam"))` or `datetime.now(timezone.utc)`. Naive raises `ValueError` |
|
|
471
|
+
| `activity.start`, `trace.timestamp` shown as the athlete's wall-clock time | **Silent:** these are UTC now. Show `start_local`, `end_local` and `timestamp_local`, which are always present |
|
|
472
|
+
<!-- --8<-- [end:upgrading-0-88] -->
|
|
473
|
+
|
|
368
474
|
## [0.87.0] - 2026-06-30
|
|
369
475
|
|
|
370
476
|
### Changed
|
|
371
477
|
|
|
372
478
|
- **Breaking:** the codec previously named NLEC is now AISC (Adaptive Intensity Segmentation Codec), and its parameters are renamed: `nlec_on` is now `segmentation_on` (on `get_activity_data`, `get_latest_activity_data`, `get_longitudinal_data`) and `nlec` is now `segmentation` (on `get_activity_mean_max`, `get_latest_activity_mean_max`). No backwards-compatible aliases.
|
|
373
479
|
|
|
480
|
+
<!-- --8<-- [start:upgrading-0-87] -->
|
|
481
|
+
### Upgrading
|
|
482
|
+
|
|
483
|
+
| 0.86 | 0.87 |
|
|
484
|
+
|---|---|
|
|
485
|
+
| `nlec_on=` on `get_activity_data`, `get_latest_activity_data`, `get_longitudinal_data` | `segmentation_on=` |
|
|
486
|
+
| `nlec=` on `get_activity_mean_max`, `get_latest_activity_mean_max` | `segmentation=` (removed in 0.90) |
|
|
487
|
+
<!-- --8<-- [end:upgrading-0-87] -->
|
|
488
|
+
|
|
374
489
|
|
|
375
490
|
## [0.86.0] - 2026-06-18
|
|
376
491
|
|
|
@@ -378,6 +493,15 @@ Four breaking changes come with it; the upgrade is mechanical, see **Upgrading**
|
|
|
378
493
|
|
|
379
494
|
- **Breaking:** renamed the adaptive-sampling parameters to NLEC (near-lossless effort codec). `adaptive_sampling_on` is now `nlec_on` (on `get_activity_data`, `get_latest_activity_data`, `get_longitudinal_data`) and `adaptive_sampling` is now `nlec` (on `get_activity_mean_max`, `get_latest_activity_mean_max`). No backwards-compatible aliases; requires the SweatStack server 0.107.0 or later.
|
|
380
495
|
|
|
496
|
+
<!-- --8<-- [start:upgrading-0-86] -->
|
|
497
|
+
### Upgrading
|
|
498
|
+
|
|
499
|
+
| 0.85 | 0.86 |
|
|
500
|
+
|---|---|
|
|
501
|
+
| `adaptive_sampling_on=` on `get_activity_data`, `get_latest_activity_data`, `get_longitudinal_data` | `nlec_on=` (`segmentation_on=` from 0.87) |
|
|
502
|
+
| `adaptive_sampling=` on `get_activity_mean_max`, `get_latest_activity_mean_max` | `nlec=` (`segmentation=` from 0.87; removed in 0.90) |
|
|
503
|
+
<!-- --8<-- [end:upgrading-0-86] -->
|
|
504
|
+
|
|
381
505
|
|
|
382
506
|
## [0.85.0] - 2026-06-17
|
|
383
507
|
|
|
@@ -439,8 +563,21 @@ This is a **breaking change** for code that uses `Sport`.
|
|
|
439
563
|
- `open-sport-taxonomy[pydantic]` is now a runtime dependency. Response models consume `sport` via OST's
|
|
440
564
|
permissive `SportField`, so sports newer than the bundled taxonomy are preserved rather than rejected.
|
|
441
565
|
|
|
442
|
-
|
|
443
|
-
|
|
566
|
+
<!-- --8<-- [start:upgrading-0-81] -->
|
|
567
|
+
### Upgrading
|
|
568
|
+
|
|
569
|
+
| 0.80 | 0.81 |
|
|
570
|
+
|---|---|
|
|
571
|
+
| `Sport.cycling_road` and other enum members | `Sport("cycling.road")`; `Sport.parse(value)` for external input |
|
|
572
|
+
| `sport.value` | `str(sport)` (with modifiers) or `sport.code` |
|
|
573
|
+
| `sport.display_name()` | `sport.label` |
|
|
574
|
+
| `sport.parent_sport()` | `sport.parent` |
|
|
575
|
+
| `sport.is_sub_sport_of(x)` | `sport.is_subsport_of(x)` (one `Sport`; `any(...)` for a list) |
|
|
576
|
+
| `sport.root_sport()`, `sport.is_root_sport()` | `Sport(sport.code.split(".")[0])`, `"." not in sport.code and not sport.modifiers` |
|
|
577
|
+
| `for s in Sport` | `for s in Sport.all()` |
|
|
578
|
+
| Hardcoded `cycling.trainer`, `running.treadmill`, `rowing.ergometer`, `cycling.tt`, `cycling.mountainbike`, `cross_country_skiing[.classic\|.skate]`, `unknown` | **Silent** in string comparisons: `cycling+stationary`, `running+stationary`, `rowing+stationary`, `cycling.time_trial`, `cycling.mountain`, `xc_skiing[.classic\|.skate]`, `generic` |
|
|
579
|
+
|
|
580
|
+
**Migration prompt.** Paste this into your coding agent:
|
|
444
581
|
|
|
445
582
|
```text
|
|
446
583
|
Migrate this codebase to sweatstack 0.81.0, which replaces its custom `Sport` enum with the
|
|
@@ -474,6 +611,7 @@ OpenSportTaxonomy type (`open_sport_taxonomy.Sport`). `from sweatstack import Sp
|
|
|
474
611
|
After editing, run the test suite and fix any remaining references. Do not add a compatibility shim;
|
|
475
612
|
migrate call sites to the OST API directly.
|
|
476
613
|
```
|
|
614
|
+
<!-- --8<-- [end:upgrading-0-81] -->
|
|
477
615
|
|
|
478
616
|
|
|
479
617
|
## [0.80.0] - 2026-06-12
|
|
@@ -503,6 +641,17 @@ migrate call sites to the OST API directly.
|
|
|
503
641
|
### Changed
|
|
504
642
|
- Minimum Python version is now declared as `>=3.10` (the code already required 3.10+ syntax; the previous `>=3.9` declaration was incorrect).
|
|
505
643
|
- Exception types in docstrings now reference the typed hierarchy introduced in 0.76.0 (`SweatStackAPIError`, `SweatStackNotFoundError`, `SweatStackAuthError`, `SweatStackBadRequestError`) instead of the now-incorrect `HTTPStatusError`.
|
|
644
|
+
- **Breaking:** with `__all__` in place, the helpers that leaked to the top level by accident are no longer there: `sweatstack.make_dataframe_streamlit_compatible`, `convert_to_standard_dtypes` and `decode_jwt_body` live in `sweatstack.utils`, `DEFAULT_URL` in `sweatstack.constants`.
|
|
645
|
+
|
|
646
|
+
<!-- --8<-- [start:upgrading-0-78] -->
|
|
647
|
+
### Upgrading
|
|
648
|
+
|
|
649
|
+
| 0.77 | 0.78 |
|
|
650
|
+
|---|---|
|
|
651
|
+
| `from sweatstack import make_dataframe_streamlit_compatible` (also `convert_to_standard_dtypes`, `decode_jwt_body`) | `from sweatstack.utils import make_dataframe_streamlit_compatible` |
|
|
652
|
+
| `sweatstack.DEFAULT_URL` | `sweatstack.constants.DEFAULT_URL` |
|
|
653
|
+
| Python 3.9 | Python 3.10 or later |
|
|
654
|
+
<!-- --8<-- [end:upgrading-0-78] -->
|
|
506
655
|
|
|
507
656
|
|
|
508
657
|
## [0.77.1] - 2026-05-19
|
|
@@ -548,6 +697,18 @@ migrate call sites to the OST API directly.
|
|
|
548
697
|
### Removed
|
|
549
698
|
- `httpx` is no longer part of the public error surface. Consumers do not need to import `httpx` to handle errors.
|
|
550
699
|
|
|
700
|
+
<!-- --8<-- [start:upgrading-0-76] -->
|
|
701
|
+
### Upgrading
|
|
702
|
+
|
|
703
|
+
| 0.75 | 0.76 |
|
|
704
|
+
|---|---|
|
|
705
|
+
| `except httpx.HTTPStatusError as e` | `except SweatStackAPIError as e`, or a subclass: `SweatStackNotFoundError` (404), `SweatStackAuthError` (401, 403), `SweatStackRateLimitError` (429), `SweatStackBadRequestError` (other 4xx), `SweatStackServerError` (5xx) |
|
|
706
|
+
| `e.response.status_code`, `e.request.url` | `e.status_code`, `e.url`; also `e.method`, `e.request_id`, `e.body` |
|
|
707
|
+
| `except ValueError` around a call, for a 422 | `except SweatStackBadRequestError` |
|
|
708
|
+
| `except httpx.ConnectError`, `httpx.TimeoutException` | `except SweatStackConnectionError` |
|
|
709
|
+
| `from sweatstack import TokenRefreshError` | `from sweatstack import SweatStackTokenRefreshError` |
|
|
710
|
+
<!-- --8<-- [end:upgrading-0-76] -->
|
|
711
|
+
|
|
551
712
|
|
|
552
713
|
## [0.75.0] - 2026-04-23
|
|
553
714
|
|
|
@@ -582,7 +743,16 @@ migrate call sites to the OST API directly.
|
|
|
582
743
|
- `AttributeError` on Python <3.11 when the API returns an error response (`add_note` is a Python 3.11+ feature).
|
|
583
744
|
|
|
584
745
|
### Removed
|
|
585
|
-
- `SWEATSTACK_LOCAL_CACHE` and `SWEATSTACK_CACHE_DIR` environment variables. Use `sweatstack.enable_cache()` instead.
|
|
746
|
+
- **Breaking:** `SWEATSTACK_LOCAL_CACHE` and `SWEATSTACK_CACHE_DIR` environment variables. Use `sweatstack.enable_cache()` instead.
|
|
747
|
+
|
|
748
|
+
<!-- --8<-- [start:upgrading-0-72] -->
|
|
749
|
+
### Upgrading
|
|
750
|
+
|
|
751
|
+
| 0.71 | 0.72 |
|
|
752
|
+
|---|---|
|
|
753
|
+
| `SWEATSTACK_LOCAL_CACHE=true` in the environment | `sweatstack.enable_cache()` in code. **Silent:** the variable is ignored, so nothing is cached until you call it |
|
|
754
|
+
| `SWEATSTACK_CACHE_DIR=/some/dir` | `sweatstack.enable_cache("/some/dir")` |
|
|
755
|
+
<!-- --8<-- [end:upgrading-0-72] -->
|
|
586
756
|
|
|
587
757
|
|
|
588
758
|
## [0.71.0] - 2026-03-13
|
|
@@ -156,7 +156,9 @@ minimal feature delta. Commit, then handle the remainder separately.
|
|
|
156
156
|
## Releasing
|
|
157
157
|
|
|
158
158
|
1. Bump `version` in `pyproject.toml` (SemVer).
|
|
159
|
-
2. Add a CHANGELOG entry — see [AGENTS.md → CHANGELOG](AGENTS.md#changelog).
|
|
159
|
+
2. Add a CHANGELOG entry — see [AGENTS.md → CHANGELOG](AGENTS.md#changelog). A breaking
|
|
160
|
+
change also needs its `### Upgrading` section
|
|
161
|
+
([AGENTS.md → Breaking changes](AGENTS.md#breaking-changes)).
|
|
160
162
|
3. `gitleaks git .` reports no leaks (`brew install gitleaks`). Known false
|
|
161
163
|
positives go in `.gitleaks.toml`, each with the reason it's safe.
|
|
162
164
|
4. `make publish`: runs `make check`, builds, and uploads with twine.
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sweatstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.94.0
|
|
4
4
|
Summary: The official Python client for SweatStack
|
|
5
5
|
Project-URL: Homepage, https://sweatstack.no
|
|
6
6
|
Project-URL: Documentation, https://docs.sweatstack.no/learn/python/
|
|
7
7
|
Project-URL: Repository, https://github.com/SweatStack/sweatstack-python
|
|
8
8
|
Project-URL: Issues, https://github.com/SweatStack/sweatstack-python/issues
|
|
9
9
|
Project-URL: Changelog, https://github.com/SweatStack/sweatstack-python/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Upgrading, https://github.com/SweatStack/sweatstack-python/blob/main/UPGRADING.md
|
|
10
11
|
Author-email: Aart Goossens <aart@sweatstack.no>
|
|
11
12
|
License-Expression: MIT
|
|
12
13
|
License-File: LICENSE
|
|
@@ -97,7 +98,9 @@ npx skills add SweatStack/sweatstack-python
|
|
|
97
98
|
- [Python SDK guide](https://docs.sweatstack.no/learn/python/): authentication,
|
|
98
99
|
clients, data output, errors, Streamlit and FastAPI
|
|
99
100
|
- [API reference](https://docs.sweatstack.no/learn/api-reference/): every endpoint and model
|
|
100
|
-
- [Changelog](https://docs.sweatstack.no/learn/python/changelog/)
|
|
101
|
+
- [Changelog](https://docs.sweatstack.no/learn/python/changelog/)
|
|
102
|
+
- [Upgrading](UPGRADING.md): moving from an older release, from any version, with a prompt for
|
|
103
|
+
your coding agent. Also at [docs.sweatstack.no](https://docs.sweatstack.no/learn/python/upgrading/).
|
|
101
104
|
|
|
102
105
|
Sports follow [OpenSportTaxonomy](https://open-sport-taxonomy.sweatstack.no): `sweatstack.Sport`
|
|
103
106
|
is its `Sport` type.
|
|
@@ -53,7 +53,9 @@ npx skills add SweatStack/sweatstack-python
|
|
|
53
53
|
- [Python SDK guide](https://docs.sweatstack.no/learn/python/): authentication,
|
|
54
54
|
clients, data output, errors, Streamlit and FastAPI
|
|
55
55
|
- [API reference](https://docs.sweatstack.no/learn/api-reference/): every endpoint and model
|
|
56
|
-
- [Changelog](https://docs.sweatstack.no/learn/python/changelog/)
|
|
56
|
+
- [Changelog](https://docs.sweatstack.no/learn/python/changelog/)
|
|
57
|
+
- [Upgrading](UPGRADING.md): moving from an older release, from any version, with a prompt for
|
|
58
|
+
your coding agent. Also at [docs.sweatstack.no](https://docs.sweatstack.no/learn/python/upgrading/).
|
|
57
59
|
|
|
58
60
|
Sports follow [OpenSportTaxonomy](https://open-sport-taxonomy.sweatstack.no): `sweatstack.Sport`
|
|
59
61
|
is its `Sport` type.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Upgrading
|
|
2
|
+
|
|
3
|
+
How to move a project to a newer `sweatstack` release, from any older one. Until 1.0 a minor
|
|
4
|
+
release may contain breaking changes, and every one ships with its upgrade path in the
|
|
5
|
+
[CHANGELOG](CHANGELOG.md): an **Upgrading** section with a table from old to new, and for the
|
|
6
|
+
larger releases a prompt for a coding agent. This page is also at
|
|
7
|
+
https://docs.sweatstack.no/learn/python/upgrading/, with every release's section inline.
|
|
8
|
+
|
|
9
|
+
<!-- --8<-- [start:procedure] -->
|
|
10
|
+
## Procedure
|
|
11
|
+
|
|
12
|
+
1. **Find your version.** `uv pip show sweatstack`, or your lockfile.
|
|
13
|
+
2. **Inventory your SDK usage** before changing anything: every call on a `Client` or on the
|
|
14
|
+
`sweatstack` module (`import sweatstack as ss` is common), `StreamlitAuth` and
|
|
15
|
+
`sweatstack.fastapi`, the model fields you read, the frame columns and indexes you rely on,
|
|
16
|
+
the exceptions you catch, and the install line with its extras.
|
|
17
|
+
3. **Read the Upgrading section of every release** between your version and the target; a
|
|
18
|
+
release without one has nothing to do. Work oldest first: each table uses the names of the
|
|
19
|
+
release before it.
|
|
20
|
+
`adaptive_sampling_on` became `nlec_on` in 0.86 and `segmentation_on` in 0.87, so a name may
|
|
21
|
+
change more than once; follow the chain.
|
|
22
|
+
4. **Change the pin and run your code and tests.** A removed name raises an `AttributeError`
|
|
23
|
+
naming its replacement, a renamed or keyword-only argument raises `TypeError`, and
|
|
24
|
+
`from sweatstack import <removed name>` raises Python's plain `ImportError`. The rows marked
|
|
25
|
+
**Silent** don't fail at all: check each one by hand.
|
|
26
|
+
5. **Don't add compatibility shims.** Move every call site to the new name.
|
|
27
|
+
|
|
28
|
+
The install line changes too: Python 3.10 or later (0.78), the frame library as an extra
|
|
29
|
+
(`sweatstack[pandas]`, `[polars]` or `[arrow]`; `[streamlit]` includes pandas) and pyarrow 20 or
|
|
30
|
+
later (0.84, 0.89), and no `[jupyter]` extra (0.91).
|
|
31
|
+
<!-- --8<-- [end:procedure] -->
|
|
32
|
+
|
|
33
|
+
<!-- --8<-- [start:prompt] -->
|
|
34
|
+
## Prompt for a coding agent
|
|
35
|
+
|
|
36
|
+
Fill in the two versions and paste it into your agent. It works for any distance between them.
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
Upgrade this project from sweatstack <OLD> to sweatstack <NEW>.
|
|
40
|
+
|
|
41
|
+
1. Inventory. Before changing anything, list every use of the SDK: calls on a Client or on the
|
|
42
|
+
sweatstack module (also as "ss"), StreamlitAuth and sweatstack.fastapi, model fields read
|
|
43
|
+
from responses, frame columns and indexes, exceptions caught, and the dependency line with
|
|
44
|
+
its extras.
|
|
45
|
+
2. Plan. Read https://docs.sweatstack.no/learn/python/upgrading/ (the same content is
|
|
46
|
+
UPGRADING.md and the "Upgrading" sections of CHANGELOG.md in
|
|
47
|
+
https://github.com/SweatStack/sweatstack-python). Take every release between <OLD> and <NEW>
|
|
48
|
+
that has an Upgrading section, oldest first, and map each row of its table onto the
|
|
49
|
+
inventory. A name may be renamed more than once across releases; follow the chain to the
|
|
50
|
+
final name.
|
|
51
|
+
3. Apply the changes, oldest release first. Do not add compatibility shims.
|
|
52
|
+
4. Install: uv add "sweatstack[<extras>]==<NEW>". The frame library is an extra ([pandas],
|
|
53
|
+
[polars] or [arrow]); [streamlit] includes pandas.
|
|
54
|
+
5. Run the code and the tests. Fix every AttributeError (its message names the replacement),
|
|
55
|
+
ImportError and TypeError. Then check every row marked "Silent" against the inventory:
|
|
56
|
+
those change behaviour without raising.
|
|
57
|
+
6. For the current API, install the SDK's agent skill (npx skills add
|
|
58
|
+
SweatStack/sweatstack-python) or read https://docs.sweatstack.no/llms.txt.
|
|
59
|
+
|
|
60
|
+
Report what you changed, and every item of the inventory you could not map to a table row.
|
|
61
|
+
```
|
|
62
|
+
<!-- --8<-- [end:prompt] -->
|