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.
Files changed (114) hide show
  1. {sweatstack-0.93.0 → sweatstack-0.95.0}/AGENTS.md +6 -2
  2. {sweatstack-0.93.0 → sweatstack-0.95.0}/CHANGELOG.md +234 -5
  3. {sweatstack-0.93.0 → sweatstack-0.95.0}/DEVELOPMENT.md +3 -1
  4. {sweatstack-0.93.0 → sweatstack-0.95.0}/PKG-INFO +5 -2
  5. {sweatstack-0.93.0 → sweatstack-0.95.0}/README.md +3 -1
  6. sweatstack-0.95.0/UPGRADING.md +62 -0
  7. sweatstack-0.95.0/plans/010_credentials_one_source.md +497 -0
  8. sweatstack-0.95.0/plans/011_user_agent_on_every_request.md +228 -0
  9. {sweatstack-0.93.0 → sweatstack-0.95.0}/pyproject.toml +2 -1
  10. {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/SKILL.md +7 -2
  11. {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/api.md +10 -4
  12. {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/data-models.md +1 -1
  13. {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/fastapi.md +1 -1
  14. {sweatstack-0.93.0 → sweatstack-0.95.0}/skills/sweatstack-python/streamlit.md +9 -7
  15. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/__init__.py +1 -0
  16. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/_renames.py +3 -1
  17. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/_transport.py +19 -0
  18. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/client.py +124 -76
  19. sweatstack-0.95.0/src/sweatstack/constants.py +19 -0
  20. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/config.py +3 -2
  21. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/dependencies.py +4 -1
  22. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/routes.py +3 -2
  23. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/openapi_schemas.py +127 -64
  24. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/oauth.py +15 -8
  25. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/profile.py +2 -2
  26. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/teams.py +5 -3
  27. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/streamlit.py +133 -35
  28. sweatstack-0.95.0/tests/conftest.py +26 -0
  29. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_account_status.py +5 -5
  30. sweatstack-0.95.0/tests/test_changelog.py +73 -0
  31. sweatstack-0.95.0/tests/test_credentials.py +261 -0
  32. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_identity.py +1 -5
  33. sweatstack-0.95.0/tests/test_streamlit_auth.py +199 -0
  34. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_transport.py +72 -5
  35. sweatstack-0.95.0/tests/test_user_agent.py +131 -0
  36. {sweatstack-0.93.0 → sweatstack-0.95.0}/uv.lock +1 -1
  37. sweatstack-0.93.0/src/sweatstack/constants.py +0 -1
  38. {sweatstack-0.93.0 → sweatstack-0.95.0}/.git-blame-ignore-revs +0 -0
  39. {sweatstack-0.93.0 → sweatstack-0.95.0}/.github/workflows/ci.yml +0 -0
  40. {sweatstack-0.93.0 → sweatstack-0.95.0}/.gitignore +0 -0
  41. {sweatstack-0.93.0 → sweatstack-0.95.0}/.gitleaks.toml +0 -0
  42. {sweatstack-0.93.0 → sweatstack-0.95.0}/.python-version +0 -0
  43. {sweatstack-0.93.0 → sweatstack-0.95.0}/CONTRIBUTING.md +0 -0
  44. {sweatstack-0.93.0 → sweatstack-0.95.0}/LICENSE +0 -0
  45. {sweatstack-0.93.0 → sweatstack-0.95.0}/Makefile +0 -0
  46. {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/__init__.py +0 -0
  47. {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/__init__.py +0 -0
  48. {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/agent.py +0 -0
  49. {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/cases.py +0 -0
  50. {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/evaluators.py +0 -0
  51. {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/harness.py +0 -0
  52. {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/lifecycle.py +0 -0
  53. {sweatstack-0.93.0 → sweatstack-0.95.0}/evals/agent/run.py +0 -0
  54. {sweatstack-0.93.0 → sweatstack-0.95.0}/examples/fastapi_webhooks_example.py +0 -0
  55. {sweatstack-0.93.0 → sweatstack-0.95.0}/examples/send_webhook.py +0 -0
  56. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/001a_tests.md +0 -0
  57. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/001b_metadata.md +0 -0
  58. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/001c_dailies.md +0 -0
  59. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/002_TYPED_EXCEPTIONS.md +0 -0
  60. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/003_trace_test_linking.md +0 -0
  61. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/004_codebase_hygiene.md +0 -0
  62. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/005_ost_sport_bridge.md +0 -0
  63. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/006_output_backends.md +0 -0
  64. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/007_account_status_userinfo_portal.md +0 -0
  65. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/008_sdk_documentation.md +0 -0
  66. {sweatstack-0.93.0 → sweatstack-0.95.0}/plans/009_road_to_1_0.md +0 -0
  67. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/_docs.py +0 -0
  68. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/_frames.py +0 -0
  69. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/cli.py +0 -0
  70. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/exceptions.py +0 -0
  71. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/__init__.py +0 -0
  72. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/access_token_cache.py +0 -0
  73. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/models.py +0 -0
  74. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/session.py +0 -0
  75. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/token_stores.py +0 -0
  76. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/fastapi/webhooks.py +0 -0
  77. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/py.typed +0 -0
  78. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/__init__.py +0 -0
  79. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/_app_metadata.py +0 -0
  80. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/_base.py +0 -0
  81. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/activities.py +0 -0
  82. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/dailies.py +0 -0
  83. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/portal.py +0 -0
  84. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/tests.py +0 -0
  85. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/traces.py +0 -0
  86. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/resources/users.py +0 -0
  87. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/schemas.py +0 -0
  88. {sweatstack-0.93.0 → sweatstack-0.95.0}/src/sweatstack/utils.py +0 -0
  89. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/__init__.py +0 -0
  90. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_access_token_cache.py +0 -0
  91. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_dailies.py +0 -0
  92. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_docs_check.py +0 -0
  93. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_docstrings.py +0 -0
  94. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_dtype_conversion.py +0 -0
  95. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_exceptions.py +0 -0
  96. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_frames.py +0 -0
  97. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_longitudinal_mean_max_after.py +0 -0
  98. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_metadata.py +0 -0
  99. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_output.py +0 -0
  100. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_portal.py +0 -0
  101. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_public_hygiene.py +0 -0
  102. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_public_surface.py +0 -0
  103. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_renames.py +0 -0
  104. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_resources.py +0 -0
  105. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_segmentation.py +0 -0
  106. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_skill.py +0 -0
  107. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_sport_ost.py +0 -0
  108. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_teams.py +0 -0
  109. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_tests.py +0 -0
  110. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_trace_test_linking.py +0 -0
  111. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_webhooks.py +0 -0
  112. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_wire_names.py +0 -0
  113. {sweatstack-0.93.0 → sweatstack-0.95.0}/tests/test_write_timezone_validation.py +0 -0
  114. {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
- | Before | After |
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
- ### Migration
461
- Hand the following prompt to a coding agent, or apply it by hand:
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.93.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/) and [upgrade notes](https://docs.sweatstack.no/learn/python/upgrading/)
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/) and [upgrade notes](https://docs.sweatstack.no/learn/python/upgrading/)
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] -->