sweatstack 0.93.0__tar.gz → 0.95.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.93.0 → sweatstack-0.95.0}/AGENTS.md +6 -2
- {sweatstack-0.93.0 → sweatstack-0.95.0}/CHANGELOG.md +234 -5
- {sweatstack-0.93.0 → sweatstack-0.95.0}/DEVELOPMENT.md +3 -1
- {sweatstack-0.93.0 → sweatstack-0.95.0}/PKG-INFO +5 -2
- {sweatstack-0.93.0 → sweatstack-0.95.0}/README.md +3 -1
- sweatstack-0.95.0/UPGRADING.md +62 -0
- sweatstack-0.95.0/plans/010_credentials_one_source.md +497 -0
- sweatstack-0.95.0/plans/011_user_agent_on_every_request.md +228 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/pyproject.toml +2 -1
- {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/SKILL.md +7 -2
- {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/api.md +10 -4
- {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/data-models.md +1 -1
- {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/fastapi.md +1 -1
- {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/streamlit.md +9 -7
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/__init__.py +1 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/_renames.py +3 -1
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/_transport.py +19 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/client.py +124 -76
- sweatstack-0.95.0/src/sweatstack/constants.py +19 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/config.py +3 -2
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/dependencies.py +4 -1
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/routes.py +3 -2
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/openapi_schemas.py +127 -64
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/oauth.py +15 -8
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/profile.py +2 -2
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/teams.py +5 -3
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/streamlit.py +133 -35
- sweatstack-0.95.0/tests/conftest.py +26 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_account_status.py +5 -5
- sweatstack-0.95.0/tests/test_changelog.py +73 -0
- sweatstack-0.95.0/tests/test_credentials.py +261 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_identity.py +1 -5
- sweatstack-0.95.0/tests/test_streamlit_auth.py +199 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_transport.py +72 -5
- sweatstack-0.95.0/tests/test_user_agent.py +131 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/uv.lock +1 -1
- sweatstack-0.93.0/src/sweatstack/constants.py +0 -1
- {sweatstack-0.93.0 → sweatstack-0.95.0}/.git-blame-ignore-revs +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/.github/workflows/ci.yml +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/.gitignore +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/.gitleaks.toml +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/.python-version +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/CONTRIBUTING.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/LICENSE +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/Makefile +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/__init__.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/__init__.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/agent.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/cases.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/evaluators.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/harness.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/lifecycle.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/run.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/examples/fastapi_webhooks_example.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/examples/send_webhook.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/001a_tests.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/001b_metadata.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/001c_dailies.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/002_TYPED_EXCEPTIONS.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/003_trace_test_linking.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/004_codebase_hygiene.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/005_ost_sport_bridge.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/006_output_backends.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/007_account_status_userinfo_portal.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/008_sdk_documentation.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/009_road_to_1_0.md +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/_docs.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/_frames.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/cli.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/exceptions.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/__init__.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/access_token_cache.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/models.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/session.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/token_stores.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/webhooks.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/py.typed +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/__init__.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/_app_metadata.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/_base.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/activities.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/dailies.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/portal.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/tests.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/traces.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/users.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/schemas.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/utils.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/__init__.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_access_token_cache.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_dailies.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_docs_check.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_docstrings.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_dtype_conversion.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_exceptions.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_frames.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_longitudinal_mean_max_after.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_metadata.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_output.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_portal.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_public_hygiene.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_public_surface.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_renames.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_resources.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_segmentation.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_skill.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_sport_ost.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_teams.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_tests.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_trace_test_linking.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_webhooks.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_wire_names.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_write_timezone_validation.py +0 -0
- {sweatstack-0.93.0 → sweatstack-0.95.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,131 @@ 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.95.0] - 2026-10-13
|
|
15
|
+
|
|
16
|
+
Scopes are categories now. Request what your app uses. Breaking: the default scopes change
|
|
17
|
+
without an error, and the metabolic map and capability names move; see **Upgrading** below, or
|
|
18
|
+
hand the migration prompt to your coding agent.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- The default scope of `Client.login()`, `client.oauth.authorization_url()`, the FastAPI plugin and
|
|
23
|
+
the Streamlit helper is one constant, `sweatstack.constants.DEFAULT_SCOPE`:
|
|
24
|
+
`openid profile email activities:read offline_access`. Add `wellness:read`,
|
|
25
|
+
`reproductive_health:read`, `workouts:write` or `sharing:read` when your app uses them.
|
|
26
|
+
- `client.teams.authorize()` grants `activities:read` by default; only the delegable scopes are
|
|
27
|
+
ever granted.
|
|
28
|
+
- `client.oauth.userinfo()` needs the `openid` scope; the name claims need `profile`, the email
|
|
29
|
+
needs `email`.
|
|
30
|
+
- `client.profile.status()` needs no scope.
|
|
31
|
+
- `Application.default_scopes` and `Application.purposes` replace `capabilities` on
|
|
32
|
+
`client.applications` (the old field is still accepted and translated).
|
|
33
|
+
- The metabolic map responses are the markers themselves: no `inputs`, no `markers` wrapper; the
|
|
34
|
+
VO2 map has `vo2max_relative`.
|
|
35
|
+
- The `dailies` capability and its issue codes are `wellness`, `wellness_not_granted` and
|
|
36
|
+
`wellness_no_source`.
|
|
37
|
+
|
|
38
|
+
### Added
|
|
39
|
+
|
|
40
|
+
- `sweatstack.__version__` is the installed version.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- Token calls from the FastAPI and Streamlit integrations (login, refresh and delegated tokens)
|
|
45
|
+
now send the `python-sweatstack/<version>` User-Agent, like every other request the SDK
|
|
46
|
+
makes. Before, they identified themselves as plain httpx.
|
|
47
|
+
|
|
48
|
+
<!-- --8<-- [start:upgrading-0-95] -->
|
|
49
|
+
### Upgrading
|
|
50
|
+
|
|
51
|
+
| 0.94 | 0.95 |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `scope="data:read data:write profile offline_access"`, or no `scope=` at all | **Silent:** the default is `DEFAULT_SCOPE` (`openid profile email activities:read offline_access`). Add the categories you use: `wellness`, `reproductive_health`, `workouts` and `sharing`, each as `:read` or `:write` |
|
|
54
|
+
| `Scope.data_read` in `teams.authorize(scopes=...)`, or no `scopes=` | **Silent:** `"activities:read"` is the default, and only the delegable categories are granted; anything else is dropped |
|
|
55
|
+
| `userinfo()` with `profile` | `userinfo()` with `openid profile email`; without `openid` it raises `SweatStackAuthError` |
|
|
56
|
+
| `response.markers.fatmax` on a metabolic map | `response.fatmax` |
|
|
57
|
+
| `response.inputs.body_mass` on a metabolic map | not published; `response.vo2max_relative` on the VO2 map |
|
|
58
|
+
| `status.capabilities["dailies"]` | `status.capabilities["wellness"]` |
|
|
59
|
+
|
|
60
|
+
An app registered before the platform release keeps working with `data:read` and `data:write`
|
|
61
|
+
for six months; see the platform changelog.
|
|
62
|
+
|
|
63
|
+
**Migration prompt.** Paste this into your coding agent:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
Upgrade this codebase to sweatstack 0.95, which replaces the data:read and data:write scope
|
|
67
|
+
bundles with per-category scopes. The upgrade table is the "Upgrading" section of the 0.95.0
|
|
68
|
+
entry in https://github.com/SweatStack/sweatstack-python/blob/main/CHANGELOG.md
|
|
69
|
+
(also at https://docs.sweatstack.no/learn/python/upgrading/). Read it first.
|
|
70
|
+
|
|
71
|
+
Find every place the app requests or grants scopes (Client.login(), oauth.authorization_url(),
|
|
72
|
+
sweatstack.fastapi.configure(scopes=...), StreamlitAuth(scopes=...) and SWEATSTACK_SCOPES,
|
|
73
|
+
teams.authorize(scopes=...)) and:
|
|
74
|
+
|
|
75
|
+
1. Replace data:read and data:write with the categories the app uses: activities, wellness,
|
|
76
|
+
reproductive_health, workouts and sharing, each as :read or :write. Start from
|
|
77
|
+
sweatstack.constants.DEFAULT_SCOPE (openid profile email activities:read offline_access)
|
|
78
|
+
and add to it. Do not keep the old bundles.
|
|
79
|
+
2. Where the app calls userinfo(), request openid, plus profile and email for the claims it
|
|
80
|
+
shows.
|
|
81
|
+
3. On the metabolic maps, read response.<marker> instead of response.markers.<marker>, drop
|
|
82
|
+
any use of response.inputs, and read status.capabilities["wellness"] where the app read
|
|
83
|
+
["dailies"].
|
|
84
|
+
|
|
85
|
+
Then run the code and the tests. A category the app forgot surfaces as SweatStackAuthError
|
|
86
|
+
on the first call that needs it; add that category, not a bundle.
|
|
87
|
+
```
|
|
88
|
+
<!-- --8<-- [end:upgrading-0-95] -->
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## [0.94.0] - 2026-10-12
|
|
92
|
+
|
|
93
|
+
A client's tokens come from one source. Upgrade every app that other people sign in to.
|
|
94
|
+
|
|
95
|
+
### Security
|
|
96
|
+
|
|
97
|
+
- A `Client` in a process shared by several users (Streamlit, FastAPI, any server) could pick
|
|
98
|
+
up a refresh token, or a whole token pair, that another user's client had saved on that
|
|
99
|
+
machine, and act as that user. Credentials now come from a single source: the arguments you
|
|
100
|
+
pass, else the environment, else the tokens saved by `authenticate()`. A client never
|
|
101
|
+
combines halves from different sources, and writes the saved tokens only when they came from
|
|
102
|
+
there. The Streamlit and FastAPI integrations never read or write saved tokens.
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- Streamlit apps behind a proxy no longer fail after the access token expires. With a proxy
|
|
107
|
+
that forwards `X-SweatStack-Refresh-Token` (the SweatStack Streamlit template does), the
|
|
108
|
+
session renews its own token and keeps the renewed pair across reruns. Streamlit apps in
|
|
109
|
+
OAuth mode refresh once per token lifetime instead of once per rerun.
|
|
110
|
+
- A refresh response that carries a new refresh token replaces the old one.
|
|
111
|
+
- `StreamlitAuth.select_user()` with a user that gets no refresh token (admin access) no longer
|
|
112
|
+
keeps the previous user's refresh token.
|
|
113
|
+
|
|
114
|
+
### Changed
|
|
115
|
+
|
|
116
|
+
- `Client(..., ambient_credentials=False)` builds a client that never reads the environment or
|
|
117
|
+
the saved tokens and never writes them. Use it for per-user clients in your own server.
|
|
118
|
+
- `oauth.exchange_code()` no longer saves the tokens on the machine by default; pass
|
|
119
|
+
`persist=True` for that. `authenticate()` still saves them.
|
|
120
|
+
- `StreamlitAuth.behind_proxy()` checks token expiry again. Without a forwarded refresh token,
|
|
121
|
+
calls after expiry raise `SweatStackTokenRefreshError` with a reload hint, where the API
|
|
122
|
+
returned a 401.
|
|
123
|
+
|
|
124
|
+
<!-- --8<-- [start:upgrading-0-94] -->
|
|
125
|
+
### Upgrading
|
|
126
|
+
|
|
127
|
+
| 0.93 | 0.94 |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `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 |
|
|
130
|
+
| `oauth.exchange_code(...)` saved the tokens on this machine | **Silent:** pass `persist=True` to keep that |
|
|
131
|
+
| `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 |
|
|
132
|
+
<!-- --8<-- [end:upgrading-0-94] -->
|
|
8
133
|
|
|
9
134
|
## [0.93.0] - 2026-10-09
|
|
10
135
|
|
|
@@ -24,6 +149,17 @@ Issues are only about the data your app uses. Requires the SweatStack API releas
|
|
|
24
149
|
app declares it uses (`list[Capability]`; omit it for the default, `activities` and
|
|
25
150
|
`activity_history`). SweatStack reports issues only for these.
|
|
26
151
|
|
|
152
|
+
<!-- --8<-- [start:upgrading-0-93] -->
|
|
153
|
+
### Upgrading
|
|
154
|
+
|
|
155
|
+
| 0.92 | 0.93 |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `issue.status` is a `CapabilityStatus` | `IssueStatus`: `action_required` or `syncing`. The `.value` strings are unchanged |
|
|
158
|
+
| 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 |
|
|
159
|
+
| 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 |
|
|
160
|
+
| An app that uses dailies or workouts | Declare them in the app's `capabilities` (`ApplicationCreate.capabilities`); issues are reported only for declared capabilities |
|
|
161
|
+
<!-- --8<-- [end:upgrading-0-93] -->
|
|
162
|
+
|
|
27
163
|
## [0.92.0] - 2026-10-08
|
|
28
164
|
|
|
29
165
|
Portal links are created with the user's token, when the user acts. Requires the SweatStack API
|
|
@@ -43,12 +179,15 @@ release that replaces `action_url` with `destination` (see the platform changelo
|
|
|
43
179
|
close the page. `return_url` must equal or sit under one of your app's registered redirect
|
|
44
180
|
URIs.
|
|
45
181
|
|
|
182
|
+
<!-- --8<-- [start:upgrading-0-92] -->
|
|
46
183
|
### Upgrading
|
|
47
184
|
|
|
48
|
-
|
|
|
185
|
+
| 0.91 | 0.92 |
|
|
49
186
|
|---|---|
|
|
50
187
|
| `user.issue.action_url` | `client.portal.sessions.create(user.issue.destination, return_url=...).url` |
|
|
51
188
|
| `Client(client_id=..., client_secret=...).portal.sessions.create(...)` | the same call on the client holding the user's token |
|
|
189
|
+
| `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 |
|
|
190
|
+
<!-- --8<-- [end:upgrading-0-92] -->
|
|
52
191
|
|
|
53
192
|
## [0.91.0] - 2026-10-08
|
|
54
193
|
|
|
@@ -143,7 +282,8 @@ removed name raises an `AttributeError` that names its replacement.
|
|
|
143
282
|
|
|
144
283
|
Removed names raise an `AttributeError` naming the replacement, so running your code points at
|
|
145
284
|
each change. The module-level functions moved the same way: `sweatstack.get_activities()` is
|
|
146
|
-
`sweatstack.activities.list()`.
|
|
285
|
+
`sweatstack.activities.list()`. Only `from sweatstack import get_activities` fails with Python's
|
|
286
|
+
plain `ImportError`; look the name up in the table.
|
|
147
287
|
|
|
148
288
|
| 0.90 | 0.91 |
|
|
149
289
|
|---|---|
|
|
@@ -294,6 +434,17 @@ and TypeError (a renamed or keyword-only argument). Do not add compatibility shi
|
|
|
294
434
|
- **BREAKING:** `by` on `get_longitudinal_mean_max`, and its `DeprecationWarning`. Every curve is
|
|
295
435
|
duration-oriented.
|
|
296
436
|
|
|
437
|
+
<!-- --8<-- [start:upgrading-0-90] -->
|
|
438
|
+
### Upgrading
|
|
439
|
+
|
|
440
|
+
| 0.89 | 0.90 |
|
|
441
|
+
|---|---|
|
|
442
|
+
| 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]` |
|
|
443
|
+
| The full curve | `durations="all"`, or a list of seconds |
|
|
444
|
+
| `get_activity_mean_max(id, "power", True)` or `segmentation=True` | Drop the argument |
|
|
445
|
+
| `get_longitudinal_mean_max(..., by="duration")` or `by="intensity"` | Drop `by=`; every curve is by duration |
|
|
446
|
+
<!-- --8<-- [end:upgrading-0-90] -->
|
|
447
|
+
|
|
297
448
|
## [0.89.0] - 2026-09-28
|
|
298
449
|
|
|
299
450
|
Frames on your terms. Every method that returns a collection takes `output=`:
|
|
@@ -359,6 +510,7 @@ Four breaking changes come with it; the upgrade is mechanical, see **Upgrading**
|
|
|
359
510
|
previously re-indexed by the client.
|
|
360
511
|
- pandas frames keep the float64 / nanosecond dtype policy. Polars and Arrow do not upcast.
|
|
361
512
|
|
|
513
|
+
<!-- --8<-- [start:upgrading-0-89] -->
|
|
362
514
|
### Upgrading
|
|
363
515
|
|
|
364
516
|
1. Change the install line: `uv add "sweatstack[pandas]"` (or `[polars]`, or `[arrow]` for
|
|
@@ -370,6 +522,10 @@ Four breaking changes come with it; the upgrade is mechanical, see **Upgrading**
|
|
|
370
522
|
4. Search for `.index`, `.loc[<timestamp>]`, `.resample(`, `.plot()` on frames from the
|
|
371
523
|
time-series, mean-max, AWD and dailies methods. Where the index mattered, add
|
|
372
524
|
`.set_index("timestamp")` (or the metric name, or `"date"`) right after the call.
|
|
525
|
+
5. **Silent:** `as_dataframe=True` on `get_activities` and `get_traces` kept nested fields as
|
|
526
|
+
dict columns (`df["summary"][0]["power"]`). `output="pandas"` flattens them to dotted
|
|
527
|
+
columns (`df["summary.power.mean"]`); Polars and Arrow give structs (`df.unnest("summary")`).
|
|
528
|
+
<!-- --8<-- [end:upgrading-0-89] -->
|
|
373
529
|
|
|
374
530
|
## [0.88.0] - 2026-08-06
|
|
375
531
|
|
|
@@ -383,12 +539,30 @@ Four breaking changes come with it; the upgrade is mechanical, see **Upgrading**
|
|
|
383
539
|
|
|
384
540
|
- 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.
|
|
385
541
|
|
|
542
|
+
<!-- --8<-- [start:upgrading-0-88] -->
|
|
543
|
+
### Upgrading
|
|
544
|
+
|
|
545
|
+
| 0.87 | 0.88 |
|
|
546
|
+
|---|---|
|
|
547
|
+
| `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` |
|
|
548
|
+
| `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 |
|
|
549
|
+
<!-- --8<-- [end:upgrading-0-88] -->
|
|
550
|
+
|
|
386
551
|
## [0.87.0] - 2026-06-30
|
|
387
552
|
|
|
388
553
|
### Changed
|
|
389
554
|
|
|
390
555
|
- **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.
|
|
391
556
|
|
|
557
|
+
<!-- --8<-- [start:upgrading-0-87] -->
|
|
558
|
+
### Upgrading
|
|
559
|
+
|
|
560
|
+
| 0.86 | 0.87 |
|
|
561
|
+
|---|---|
|
|
562
|
+
| `nlec_on=` on `get_activity_data`, `get_latest_activity_data`, `get_longitudinal_data` | `segmentation_on=` |
|
|
563
|
+
| `nlec=` on `get_activity_mean_max`, `get_latest_activity_mean_max` | `segmentation=` (removed in 0.90) |
|
|
564
|
+
<!-- --8<-- [end:upgrading-0-87] -->
|
|
565
|
+
|
|
392
566
|
|
|
393
567
|
## [0.86.0] - 2026-06-18
|
|
394
568
|
|
|
@@ -396,6 +570,15 @@ Four breaking changes come with it; the upgrade is mechanical, see **Upgrading**
|
|
|
396
570
|
|
|
397
571
|
- **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.
|
|
398
572
|
|
|
573
|
+
<!-- --8<-- [start:upgrading-0-86] -->
|
|
574
|
+
### Upgrading
|
|
575
|
+
|
|
576
|
+
| 0.85 | 0.86 |
|
|
577
|
+
|---|---|
|
|
578
|
+
| `adaptive_sampling_on=` on `get_activity_data`, `get_latest_activity_data`, `get_longitudinal_data` | `nlec_on=` (`segmentation_on=` from 0.87) |
|
|
579
|
+
| `adaptive_sampling=` on `get_activity_mean_max`, `get_latest_activity_mean_max` | `nlec=` (`segmentation=` from 0.87; removed in 0.90) |
|
|
580
|
+
<!-- --8<-- [end:upgrading-0-86] -->
|
|
581
|
+
|
|
399
582
|
|
|
400
583
|
## [0.85.0] - 2026-06-17
|
|
401
584
|
|
|
@@ -457,8 +640,21 @@ This is a **breaking change** for code that uses `Sport`.
|
|
|
457
640
|
- `open-sport-taxonomy[pydantic]` is now a runtime dependency. Response models consume `sport` via OST's
|
|
458
641
|
permissive `SportField`, so sports newer than the bundled taxonomy are preserved rather than rejected.
|
|
459
642
|
|
|
460
|
-
|
|
461
|
-
|
|
643
|
+
<!-- --8<-- [start:upgrading-0-81] -->
|
|
644
|
+
### Upgrading
|
|
645
|
+
|
|
646
|
+
| 0.80 | 0.81 |
|
|
647
|
+
|---|---|
|
|
648
|
+
| `Sport.cycling_road` and other enum members | `Sport("cycling.road")`; `Sport.parse(value)` for external input |
|
|
649
|
+
| `sport.value` | `str(sport)` (with modifiers) or `sport.code` |
|
|
650
|
+
| `sport.display_name()` | `sport.label` |
|
|
651
|
+
| `sport.parent_sport()` | `sport.parent` |
|
|
652
|
+
| `sport.is_sub_sport_of(x)` | `sport.is_subsport_of(x)` (one `Sport`; `any(...)` for a list) |
|
|
653
|
+
| `sport.root_sport()`, `sport.is_root_sport()` | `Sport(sport.code.split(".")[0])`, `"." not in sport.code and not sport.modifiers` |
|
|
654
|
+
| `for s in Sport` | `for s in Sport.all()` |
|
|
655
|
+
| 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` |
|
|
656
|
+
|
|
657
|
+
**Migration prompt.** Paste this into your coding agent:
|
|
462
658
|
|
|
463
659
|
```text
|
|
464
660
|
Migrate this codebase to sweatstack 0.81.0, which replaces its custom `Sport` enum with the
|
|
@@ -492,6 +688,7 @@ OpenSportTaxonomy type (`open_sport_taxonomy.Sport`). `from sweatstack import Sp
|
|
|
492
688
|
After editing, run the test suite and fix any remaining references. Do not add a compatibility shim;
|
|
493
689
|
migrate call sites to the OST API directly.
|
|
494
690
|
```
|
|
691
|
+
<!-- --8<-- [end:upgrading-0-81] -->
|
|
495
692
|
|
|
496
693
|
|
|
497
694
|
## [0.80.0] - 2026-06-12
|
|
@@ -521,6 +718,17 @@ migrate call sites to the OST API directly.
|
|
|
521
718
|
### Changed
|
|
522
719
|
- Minimum Python version is now declared as `>=3.10` (the code already required 3.10+ syntax; the previous `>=3.9` declaration was incorrect).
|
|
523
720
|
- Exception types in docstrings now reference the typed hierarchy introduced in 0.76.0 (`SweatStackAPIError`, `SweatStackNotFoundError`, `SweatStackAuthError`, `SweatStackBadRequestError`) instead of the now-incorrect `HTTPStatusError`.
|
|
721
|
+
- **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`.
|
|
722
|
+
|
|
723
|
+
<!-- --8<-- [start:upgrading-0-78] -->
|
|
724
|
+
### Upgrading
|
|
725
|
+
|
|
726
|
+
| 0.77 | 0.78 |
|
|
727
|
+
|---|---|
|
|
728
|
+
| `from sweatstack import make_dataframe_streamlit_compatible` (also `convert_to_standard_dtypes`, `decode_jwt_body`) | `from sweatstack.utils import make_dataframe_streamlit_compatible` |
|
|
729
|
+
| `sweatstack.DEFAULT_URL` | `sweatstack.constants.DEFAULT_URL` |
|
|
730
|
+
| Python 3.9 | Python 3.10 or later |
|
|
731
|
+
<!-- --8<-- [end:upgrading-0-78] -->
|
|
524
732
|
|
|
525
733
|
|
|
526
734
|
## [0.77.1] - 2026-05-19
|
|
@@ -566,6 +774,18 @@ migrate call sites to the OST API directly.
|
|
|
566
774
|
### Removed
|
|
567
775
|
- `httpx` is no longer part of the public error surface. Consumers do not need to import `httpx` to handle errors.
|
|
568
776
|
|
|
777
|
+
<!-- --8<-- [start:upgrading-0-76] -->
|
|
778
|
+
### Upgrading
|
|
779
|
+
|
|
780
|
+
| 0.75 | 0.76 |
|
|
781
|
+
|---|---|
|
|
782
|
+
| `except httpx.HTTPStatusError as e` | `except SweatStackAPIError as e`, or a subclass: `SweatStackNotFoundError` (404), `SweatStackAuthError` (401, 403), `SweatStackRateLimitError` (429), `SweatStackBadRequestError` (other 4xx), `SweatStackServerError` (5xx) |
|
|
783
|
+
| `e.response.status_code`, `e.request.url` | `e.status_code`, `e.url`; also `e.method`, `e.request_id`, `e.body` |
|
|
784
|
+
| `except ValueError` around a call, for a 422 | `except SweatStackBadRequestError` |
|
|
785
|
+
| `except httpx.ConnectError`, `httpx.TimeoutException` | `except SweatStackConnectionError` |
|
|
786
|
+
| `from sweatstack import TokenRefreshError` | `from sweatstack import SweatStackTokenRefreshError` |
|
|
787
|
+
<!-- --8<-- [end:upgrading-0-76] -->
|
|
788
|
+
|
|
569
789
|
|
|
570
790
|
## [0.75.0] - 2026-04-23
|
|
571
791
|
|
|
@@ -600,7 +820,16 @@ migrate call sites to the OST API directly.
|
|
|
600
820
|
- `AttributeError` on Python <3.11 when the API returns an error response (`add_note` is a Python 3.11+ feature).
|
|
601
821
|
|
|
602
822
|
### Removed
|
|
603
|
-
- `SWEATSTACK_LOCAL_CACHE` and `SWEATSTACK_CACHE_DIR` environment variables. Use `sweatstack.enable_cache()` instead.
|
|
823
|
+
- **Breaking:** `SWEATSTACK_LOCAL_CACHE` and `SWEATSTACK_CACHE_DIR` environment variables. Use `sweatstack.enable_cache()` instead.
|
|
824
|
+
|
|
825
|
+
<!-- --8<-- [start:upgrading-0-72] -->
|
|
826
|
+
### Upgrading
|
|
827
|
+
|
|
828
|
+
| 0.71 | 0.72 |
|
|
829
|
+
|---|---|
|
|
830
|
+
| `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 |
|
|
831
|
+
| `SWEATSTACK_CACHE_DIR=/some/dir` | `sweatstack.enable_cache("/some/dir")` |
|
|
832
|
+
<!-- --8<-- [end:upgrading-0-72] -->
|
|
604
833
|
|
|
605
834
|
|
|
606
835
|
## [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.95.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] -->
|