sweatstack 0.86.0__tar.gz → 0.87.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 (67) hide show
  1. {sweatstack-0.86.0 → sweatstack-0.87.0}/.claude/settings.local.json +2 -1
  2. {sweatstack-0.86.0 → sweatstack-0.87.0}/CHANGELOG.md +7 -0
  3. {sweatstack-0.86.0 → sweatstack-0.87.0}/PKG-INFO +1 -1
  4. {sweatstack-0.86.0 → sweatstack-0.87.0}/pyproject.toml +1 -1
  5. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/client.py +20 -20
  6. sweatstack-0.87.0/tests/test_segmentation.py +70 -0
  7. {sweatstack-0.86.0 → sweatstack-0.87.0}/uv.lock +1 -1
  8. {sweatstack-0.86.0 → sweatstack-0.87.0}/.claude/skills/sweatstack-python/SKILL.md +0 -0
  9. {sweatstack-0.86.0 → sweatstack-0.87.0}/.claude/skills/sweatstack-python/client.md +0 -0
  10. {sweatstack-0.86.0 → sweatstack-0.87.0}/.claude/skills/sweatstack-python/data-models.md +0 -0
  11. {sweatstack-0.86.0 → sweatstack-0.87.0}/.claude/skills/sweatstack-python/fastapi.md +0 -0
  12. {sweatstack-0.86.0 → sweatstack-0.87.0}/.claude/skills/sweatstack-python/streamlit.md +0 -0
  13. {sweatstack-0.86.0 → sweatstack-0.87.0}/.gitignore +0 -0
  14. {sweatstack-0.86.0 → sweatstack-0.87.0}/.python-version +0 -0
  15. {sweatstack-0.86.0 → sweatstack-0.87.0}/AGENTS.md +0 -0
  16. {sweatstack-0.86.0 → sweatstack-0.87.0}/CONTRIBUTING.md +0 -0
  17. {sweatstack-0.86.0 → sweatstack-0.87.0}/DEVELOPMENT.md +0 -0
  18. {sweatstack-0.86.0 → sweatstack-0.87.0}/LICENSE +0 -0
  19. {sweatstack-0.86.0 → sweatstack-0.87.0}/Makefile +0 -0
  20. {sweatstack-0.86.0 → sweatstack-0.87.0}/README.md +0 -0
  21. {sweatstack-0.86.0 → sweatstack-0.87.0}/docs/conf.py +0 -0
  22. {sweatstack-0.86.0 → sweatstack-0.87.0}/docs/everything.rst +0 -0
  23. {sweatstack-0.86.0 → sweatstack-0.87.0}/docs/index.rst +0 -0
  24. {sweatstack-0.86.0 → sweatstack-0.87.0}/examples/fastapi_webhooks_example.py +0 -0
  25. {sweatstack-0.86.0 → sweatstack-0.87.0}/examples/send_webhook.py +0 -0
  26. {sweatstack-0.86.0 → sweatstack-0.87.0}/plans/001a_tests.md +0 -0
  27. {sweatstack-0.86.0 → sweatstack-0.87.0}/plans/001b_metadata.md +0 -0
  28. {sweatstack-0.86.0 → sweatstack-0.87.0}/plans/001c_dailies.md +0 -0
  29. {sweatstack-0.86.0 → sweatstack-0.87.0}/plans/002_TYPED_EXCEPTIONS.md +0 -0
  30. {sweatstack-0.86.0 → sweatstack-0.87.0}/plans/003_trace_test_linking.md +0 -0
  31. {sweatstack-0.86.0 → sweatstack-0.87.0}/plans/004_codebase_hygiene.md +0 -0
  32. {sweatstack-0.86.0 → sweatstack-0.87.0}/plans/005_ost_sport_bridge.md +0 -0
  33. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/Sweat Stack examples/Getting started.ipynb +0 -0
  34. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/__init__.py +0 -0
  35. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/cli.py +0 -0
  36. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/constants.py +0 -0
  37. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/exceptions.py +0 -0
  38. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/fastapi/__init__.py +0 -0
  39. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/fastapi/access_token_cache.py +0 -0
  40. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/fastapi/config.py +0 -0
  41. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/fastapi/dependencies.py +0 -0
  42. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/fastapi/models.py +0 -0
  43. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/fastapi/routes.py +0 -0
  44. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/fastapi/session.py +0 -0
  45. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/fastapi/token_stores.py +0 -0
  46. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/fastapi/webhooks.py +0 -0
  47. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/ipython_init.py +0 -0
  48. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/jupyterlab_oauth2_startup.py +0 -0
  49. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/openapi_schemas.py +0 -0
  50. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/py.typed +0 -0
  51. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/schemas.py +0 -0
  52. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/streamlit.py +0 -0
  53. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/sweatshell.py +0 -0
  54. {sweatstack-0.86.0 → sweatstack-0.87.0}/src/sweatstack/utils.py +0 -0
  55. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/__init__.py +0 -0
  56. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_access_token_cache.py +0 -0
  57. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_dailies.py +0 -0
  58. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_dtype_conversion.py +0 -0
  59. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_exceptions.py +0 -0
  60. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_longitudinal_mean_max_after.py +0 -0
  61. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_metadata.py +0 -0
  62. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_public_surface.py +0 -0
  63. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_sport_ost.py +0 -0
  64. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_teams.py +0 -0
  65. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_tests.py +0 -0
  66. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_trace_test_linking.py +0 -0
  67. {sweatstack-0.86.0 → sweatstack-0.87.0}/tests/test_webhooks.py +0 -0
@@ -31,7 +31,8 @@
31
31
  "Bash(sed -n '42,50p' src/sweatstack/client.py)",
32
32
  "Bash(sed -n '1017,1022p' src/sweatstack/client.py)",
33
33
  "Bash(sed -i '' 's/sport=cycling\\\\.road&sport=cycling\\\\.tt/sport=cycling.road\\\\&sport=cycling.time_trial/' docs-dev/changelog.md)",
34
- "Bash(sed -n '25p' docs-dev/changelog.md)"
34
+ "Bash(sed -n '25p' docs-dev/changelog.md)",
35
+ "Bash(uv build *)"
35
36
  ],
36
37
  "deny": []
37
38
  }
@@ -6,6 +6,13 @@ 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
8
 
9
+ ## [0.87.0] - 2026-06-30
10
+
11
+ ### Changed
12
+
13
+ - **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.
14
+
15
+
9
16
  ## [0.86.0] - 2026-06-18
10
17
 
11
18
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sweatstack
3
- Version: 0.86.0
3
+ Version: 0.87.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/getting-started/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sweatstack"
3
- version = "0.86.0"
3
+ version = "0.87.0"
4
4
  description = "The official Python client for SweatStack"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1219,18 +1219,18 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1219
1219
  def get_activity_data(
1220
1220
  self,
1221
1221
  activity_id: str,
1222
- nlec_on: Literal["power", "speed"] | None = None,
1222
+ segmentation_on: Literal["power", "speed"] | None = None,
1223
1223
  metrics: list[Metric | str] | None = None,
1224
1224
  ) -> pd.DataFrame:
1225
1225
  """Gets the raw data for a specific activity.
1226
1226
 
1227
- This method retrieves the time-series data for a given activity, with optional NLEC
1228
- (near-lossless effort codec) downsampling to reduce data points for visualization.
1227
+ This method retrieves the time-series data for a given activity, with optional AISC
1228
+ (Adaptive Intensity Segmentation Codec) downsampling to reduce data points for visualization.
1229
1229
 
1230
1230
  Args:
1231
1231
  activity_id: The unique identifier of the activity.
1232
- nlec_on: Downsample with NLEC (near-lossless effort codec), keyed on
1233
- either "power" or "speed" data. If None, no NLEC is applied.
1232
+ segmentation_on: Downsample with AISC (Adaptive Intensity Segmentation Codec), keyed on
1233
+ either "power" or "speed" data. If None, no AISC is applied.
1234
1234
  metrics: Optional list of metrics to include in the results. Can be a list of Metric enums or strings.
1235
1235
 
1236
1236
  Returns:
@@ -1241,8 +1241,8 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1241
1241
  SweatStackAPIError: If the API request fails for any other reason.
1242
1242
  """
1243
1243
  params = {}
1244
- if nlec_on is not None:
1245
- params["nlec_on"] = nlec_on
1244
+ if segmentation_on is not None:
1245
+ params["segmentation_on"] = segmentation_on
1246
1246
  if metrics is not None:
1247
1247
  params["metrics"] = self._enums_to_strings(metrics)
1248
1248
 
@@ -1260,7 +1260,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1260
1260
  self,
1261
1261
  activity_id: str,
1262
1262
  metric: Literal[Metric.power, Metric.speed] | Literal["power", "speed"],
1263
- nlec: bool = False,
1263
+ segmentation: bool = False,
1264
1264
  ) -> pd.DataFrame:
1265
1265
  """Gets the mean-max data for a specific activity.
1266
1266
 
@@ -1270,7 +1270,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1270
1270
  Args:
1271
1271
  activity_id: The unique identifier of the activity.
1272
1272
  metric: The metric to calculate mean-max values for, either "power" or "speed".
1273
- nlec: Downsample with NLEC (near-lossless effort codec) to reduce data points
1273
+ segmentation: Downsample with AISC (Adaptive Intensity Segmentation Codec) to reduce data points
1274
1274
  for visualization. Defaults to False.
1275
1275
 
1276
1276
  Returns:
@@ -1286,7 +1286,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1286
1286
  url=f"/api/v1/activities/{activity_id}/mean-max",
1287
1287
  params={
1288
1288
  "metric": metric,
1289
- "nlec": nlec,
1289
+ "segmentation": segmentation,
1290
1290
  },
1291
1291
  )
1292
1292
  self._raise_for_status(response)
@@ -1332,7 +1332,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1332
1332
  def get_latest_activity_data(
1333
1333
  self,
1334
1334
  sport: Sport | str | None = None,
1335
- nlec_on: Literal["power", "speed"] | None = None,
1335
+ segmentation_on: Literal["power", "speed"] | None = None,
1336
1336
  metrics: list[Metric | str] | None = None,
1337
1337
  ) -> pd.DataFrame:
1338
1338
  """Gets the data for the latest activity of a specific sport.
@@ -1342,7 +1342,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1342
1342
 
1343
1343
  Args:
1344
1344
  sport: Optional sport to filter by. Can be a Sport enum or string.
1345
- nlec_on: Metric to downsample on with NLEC (near-lossless effort codec); omit to disable.
1345
+ segmentation_on: Metric to downsample on with AISC (Adaptive Intensity Segmentation Codec); omit to disable.
1346
1346
  Can be either "power" or "speed". Defaults to None.
1347
1347
  metrics: Optional list of metrics to include in the results. Can be a list of Metric enums or strings.
1348
1348
 
@@ -1353,13 +1353,13 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1353
1353
  SweatStackAPIError: If the API request fails.
1354
1354
  """
1355
1355
  activity = self.get_latest_activity(sport=sport)
1356
- return self.get_activity_data(activity.id, nlec_on, metrics=metrics)
1356
+ return self.get_activity_data(activity.id, segmentation_on, metrics=metrics)
1357
1357
 
1358
1358
  def get_latest_activity_mean_max(
1359
1359
  self,
1360
1360
  metric: Literal[Metric.power, Metric.speed] | Literal["power", "speed"],
1361
1361
  sport: Sport | str | None = None,
1362
- nlec: bool = False,
1362
+ segmentation: bool = False,
1363
1363
  ) -> pd.DataFrame:
1364
1364
  """Gets the mean-max curve for the latest activity of a specific sport.
1365
1365
 
@@ -1369,7 +1369,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1369
1369
  Args:
1370
1370
  metric: The metric to calculate the mean-max curve for. Can be either "power" or "speed".
1371
1371
  sport: Optional sport to filter by. Can be a Sport enum or string.
1372
- nlec: Downsample the mean-max curve with NLEC (near-lossless effort codec).
1372
+ segmentation: Downsample the mean-max curve with AISC (Adaptive Intensity Segmentation Codec).
1373
1373
  Defaults to False.
1374
1374
 
1375
1375
  Returns:
@@ -1379,7 +1379,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1379
1379
  SweatStackAPIError: If the API request fails.
1380
1380
  """
1381
1381
  activity = self.get_latest_activity(sport=sport)
1382
- return self.get_activity_mean_max(activity.id, metric, nlec)
1382
+ return self.get_activity_mean_max(activity.id, metric, segmentation)
1383
1383
 
1384
1384
  def get_longitudinal_data(
1385
1385
  self,
@@ -1389,7 +1389,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1389
1389
  start: date | str,
1390
1390
  end: date | str | None = None,
1391
1391
  metrics: list[Metric | str] | None = None,
1392
- nlec_on: Literal[Metric.power, Metric.speed] | Literal["power", "speed"] | None = None,
1392
+ segmentation_on: Literal[Metric.power, Metric.speed] | Literal["power", "speed"] | None = None,
1393
1393
  ) -> pd.DataFrame:
1394
1394
  """Gets longitudinal data for activities within a specified date range.
1395
1395
 
@@ -1402,7 +1402,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1402
1402
  start: The start date for the data range. Can be a date object or string in ISO format.
1403
1403
  end: Optional end date for the data range. Can be a date object or string in ISO format.
1404
1404
  metrics: Optional list of metrics to include in the results. Can be a list of Metric enums or strings.
1405
- nlec_on: Metric to downsample on with NLEC (near-lossless effort codec); omit to disable.
1405
+ segmentation_on: Metric to downsample on with AISC (Adaptive Intensity Segmentation Codec); omit to disable.
1406
1406
  Can be either "power" or "speed". Defaults to None.
1407
1407
 
1408
1408
  Returns:
@@ -1431,8 +1431,8 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
1431
1431
  params["end"] = end
1432
1432
  if metrics is not None:
1433
1433
  params["metrics"] = self._enums_to_strings(metrics)
1434
- if nlec_on is not None:
1435
- params["nlec_on"] = self._enums_to_strings([nlec_on])[0]
1434
+ if segmentation_on is not None:
1435
+ params["segmentation_on"] = self._enums_to_strings([segmentation_on])[0]
1436
1436
 
1437
1437
  if self._cache_enabled():
1438
1438
  cache_key = self._generate_cache_key("longitudinal_data", **params)
@@ -0,0 +1,70 @@
1
+ """AISC (Adaptive Intensity Segmentation Codec) parameters are sent on the wire.
2
+
3
+ Guards the `segmentation_on` / `segmentation` query-param contract (renamed from `nlec_on` / `nlec`
4
+ in 0.87.0) and the clean break — the old names must no longer be accepted.
5
+ """
6
+
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import pandas as pd
10
+ import pytest
11
+
12
+ from sweatstack.client import Client
13
+
14
+
15
+ @pytest.fixture
16
+ def client():
17
+ c = Client.__new__(Client)
18
+ c.url = "https://test.sweatstack.no"
19
+ c._access_token = None
20
+ c.streamlit_compatible = False
21
+ c.skip_token_expiry_check = True
22
+ return c
23
+
24
+
25
+ def _params_sent(client, call):
26
+ """Run `call`, mocking out HTTP + parquet, and return the query params sent to the server."""
27
+ response = MagicMock(status_code=200, content=b"")
28
+ http = MagicMock()
29
+ http.__enter__ = MagicMock(return_value=http)
30
+ http.__exit__ = MagicMock(return_value=False)
31
+ http.get.return_value = response
32
+
33
+ with patch.object(client, "_http_client", return_value=http), \
34
+ patch.object(client, "_raise_for_status"), \
35
+ patch.object(client, "_postprocess_dataframe", side_effect=lambda df: df), \
36
+ patch.object(client, "_cache_enabled", return_value=False), \
37
+ patch("sweatstack.client.pd.read_parquet", return_value=pd.DataFrame()):
38
+ call()
39
+ return http.get.call_args.kwargs["params"]
40
+
41
+
42
+ def test_get_activity_data_sends_segmentation_on(client):
43
+ params = _params_sent(client, lambda: client.get_activity_data("a", segmentation_on="power"))
44
+ assert params["segmentation_on"] == "power"
45
+
46
+
47
+ def test_get_activity_data_omits_segmentation_when_unset(client):
48
+ params = _params_sent(client, lambda: client.get_activity_data("a"))
49
+ assert "segmentation_on" not in params
50
+
51
+
52
+ def test_get_longitudinal_data_sends_segmentation_on(client):
53
+ params = _params_sent(
54
+ client,
55
+ lambda: client.get_longitudinal_data(sports=["running"], start="2024-01-01", segmentation_on="power"),
56
+ )
57
+ assert params["segmentation_on"] == "power"
58
+
59
+
60
+ def test_get_activity_mean_max_sends_segmentation(client):
61
+ params = _params_sent(client, lambda: client.get_activity_mean_max("a", "power", segmentation=True))
62
+ assert params["segmentation"] is True
63
+
64
+
65
+ def test_old_nlec_kwargs_are_rejected(client):
66
+ # Clean break: the pre-0.87.0 names must raise, not silently no-op.
67
+ with pytest.raises(TypeError):
68
+ client.get_activity_data("a", nlec_on="power")
69
+ with pytest.raises(TypeError):
70
+ client.get_activity_mean_max("a", "power", nlec=True)
@@ -2600,7 +2600,7 @@ wheels = [
2600
2600
 
2601
2601
  [[package]]
2602
2602
  name = "sweatstack"
2603
- version = "0.86.0"
2603
+ version = "0.87.0"
2604
2604
  source = { editable = "." }
2605
2605
  dependencies = [
2606
2606
  { name = "email-validator" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes