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