sweatstack 0.82.0__tar.gz → 0.83.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sweatstack-0.82.0 → sweatstack-0.83.0}/CHANGELOG.md +10 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/PKG-INFO +1 -1
- {sweatstack-0.82.0 → sweatstack-0.83.0}/pyproject.toml +1 -1
- sweatstack-0.83.0/src/sweatstack/cli.py +111 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/client.py +1 -1
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/openapi_schemas.py +307 -202
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_dailies.py +32 -14
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_sport_ost.py +2 -4
- {sweatstack-0.82.0 → sweatstack-0.83.0}/uv.lock +1 -1
- sweatstack-0.82.0/src/sweatstack/cli.py +0 -48
- {sweatstack-0.82.0 → sweatstack-0.83.0}/.claude/settings.local.json +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/.claude/skills/sweatstack-python/SKILL.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/.claude/skills/sweatstack-python/client.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/.claude/skills/sweatstack-python/data-models.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/.claude/skills/sweatstack-python/fastapi.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/.claude/skills/sweatstack-python/streamlit.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/.gitignore +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/.python-version +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/AGENTS.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/CONTRIBUTING.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/DEVELOPMENT.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/LICENSE +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/Makefile +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/README.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/docs/conf.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/docs/everything.rst +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/docs/index.rst +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/examples/fastapi_webhooks_example.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/examples/send_webhook.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/plans/001a_tests.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/plans/001b_metadata.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/plans/001c_dailies.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/plans/002_TYPED_EXCEPTIONS.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/plans/003_trace_test_linking.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/plans/004_codebase_hygiene.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/plans/005_ost_sport_bridge.md +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/Sweat Stack examples/Getting started.ipynb +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/__init__.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/constants.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/exceptions.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/fastapi/__init__.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/fastapi/access_token_cache.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/fastapi/config.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/fastapi/dependencies.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/fastapi/models.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/fastapi/routes.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/fastapi/session.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/fastapi/token_stores.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/fastapi/webhooks.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/ipython_init.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/jupyterlab_oauth2_startup.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/py.typed +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/schemas.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/streamlit.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/sweatshell.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/src/sweatstack/utils.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/__init__.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_access_token_cache.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_dtype_conversion.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_exceptions.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_longitudinal_mean_max_after.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_metadata.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_public_surface.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_teams.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_tests.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_trace_test_linking.py +0 -0
- {sweatstack-0.82.0 → sweatstack-0.83.0}/tests/test_webhooks.py +0 -0
|
@@ -5,6 +5,16 @@ 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
|
+
|
|
9
|
+
## [0.83.0] - 2026-06-16
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- Trace responses now include `test` and `test_match` (server-side test matching).
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- Fixes Dailies response schema.
|
|
16
|
+
|
|
17
|
+
|
|
8
18
|
## [0.82.0] - 2026-06-16
|
|
9
19
|
|
|
10
20
|
### Added
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import re
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from datamodel_code_generator import InputFileType, generate
|
|
8
|
+
from datamodel_code_generator import DataModelType
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _bind_sport_to_ost(path: Path) -> None:
|
|
12
|
+
"""Type every ``sport`` / ``sports`` model field as OpenSportTaxonomy's permissive ``SportField``.
|
|
13
|
+
|
|
14
|
+
The API exposes ``sport`` as a free-form OpenSportTaxonomy string, so datamodel-codegen types these
|
|
15
|
+
fields as plain ``str``. We retype them to ``SportField``, which validates an inbound string to an
|
|
16
|
+
``open_sport_taxonomy.Sport`` and serialises back to the canonical wire string, tolerating sports
|
|
17
|
+
newer than the bundled taxonomy. Any leftover generated ``Sport`` schema is dropped. Anchored on the
|
|
18
|
+
AST (not a text match) and idempotent, so it survives regeneration.
|
|
19
|
+
"""
|
|
20
|
+
src = path.read_text()
|
|
21
|
+
tree = ast.parse(src)
|
|
22
|
+
lines = src.splitlines(keepends=True)
|
|
23
|
+
|
|
24
|
+
drop: set[int] = set() # 0-indexed lines to remove
|
|
25
|
+
replace: dict[int, str] = {} # 0-indexed line -> new text
|
|
26
|
+
|
|
27
|
+
# Drop a leftover generated `Sport` schema (the server may still emit an unused one) ...
|
|
28
|
+
for node in tree.body:
|
|
29
|
+
if isinstance(node, ast.ClassDef) and node.name == "Sport":
|
|
30
|
+
drop.update(range(node.lineno - 1, node.end_lineno))
|
|
31
|
+
# ... and any SportField import from a previous run (re-injected cleanly below).
|
|
32
|
+
for i, line in enumerate(lines):
|
|
33
|
+
if line.startswith("from open_sport_taxonomy.pydantic import SportField"):
|
|
34
|
+
drop.add(i)
|
|
35
|
+
|
|
36
|
+
# Retype every `sport` / `sports` field annotation: str -> SportField (str | None, list[str], ...).
|
|
37
|
+
for node in ast.walk(tree):
|
|
38
|
+
if (isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name)
|
|
39
|
+
and node.target.id in ("sport", "sports")):
|
|
40
|
+
annotation = ast.get_source_segment(src, node.annotation)
|
|
41
|
+
retyped = re.sub(r"\bstr\b", "SportField", annotation)
|
|
42
|
+
if retyped != annotation:
|
|
43
|
+
i = node.lineno - 1
|
|
44
|
+
replace[i] = lines[i].replace(annotation, retyped, 1)
|
|
45
|
+
|
|
46
|
+
out: list[str] = []
|
|
47
|
+
for i, line in enumerate(lines):
|
|
48
|
+
if i in drop:
|
|
49
|
+
continue
|
|
50
|
+
out.append(replace.get(i, line))
|
|
51
|
+
if line.startswith("from __future__ import annotations"):
|
|
52
|
+
out.append("from open_sport_taxonomy.pydantic import SportField # OST sport type (see schemas.py)\n")
|
|
53
|
+
path.write_text("".join(out))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _restore_naive_local_datetimes(path: Path) -> None:
|
|
57
|
+
"""Type local timestamps as ``NaiveDatetime`` rather than ``AwareDatetime``.
|
|
58
|
+
|
|
59
|
+
The API returns *local* timestamps without a timezone, but datamodel-codegen types every
|
|
60
|
+
``date-time`` field as ``AwareDatetime`` -- which rejects a naive value. Retype the local fields
|
|
61
|
+
(those whose name ends in ``_local``) back to ``NaiveDatetime``, and keep ``registered_at``
|
|
62
|
+
accepting either. AST-anchored and idempotent, so it survives regeneration (and replaces the
|
|
63
|
+
manual fixups this file has needed in the past).
|
|
64
|
+
"""
|
|
65
|
+
src = path.read_text()
|
|
66
|
+
tree = ast.parse(src)
|
|
67
|
+
lines = src.splitlines(keepends=True)
|
|
68
|
+
replace: dict[int, str] = {}
|
|
69
|
+
|
|
70
|
+
for node in ast.walk(tree):
|
|
71
|
+
if not (isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name)):
|
|
72
|
+
continue
|
|
73
|
+
name = node.target.id
|
|
74
|
+
annotation = ast.get_source_segment(src, node.annotation)
|
|
75
|
+
if name.endswith("_local") and "AwareDatetime" in annotation:
|
|
76
|
+
retyped = annotation.replace("AwareDatetime", "NaiveDatetime")
|
|
77
|
+
elif name == "registered_at" and annotation == "AwareDatetime":
|
|
78
|
+
retyped = "AwareDatetime | NaiveDatetime"
|
|
79
|
+
else:
|
|
80
|
+
continue
|
|
81
|
+
i = node.lineno - 1
|
|
82
|
+
replace[i] = lines[i].replace(annotation, retyped, 1)
|
|
83
|
+
|
|
84
|
+
if not replace:
|
|
85
|
+
return # already naive (idempotent re-run)
|
|
86
|
+
|
|
87
|
+
src = "".join(replace.get(i, line) for i, line in enumerate(lines))
|
|
88
|
+
if " NaiveDatetime,\n" not in src: # ensure the import exists
|
|
89
|
+
src = src.replace(" AwareDatetime,\n", " AwareDatetime,\n NaiveDatetime,\n", 1)
|
|
90
|
+
path.write_text(src)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def generate_response_models():
|
|
94
|
+
response = httpx.get("http://localhost:8080/openapi.json")
|
|
95
|
+
response.raise_for_status()
|
|
96
|
+
output_directory = Path(__file__).parent
|
|
97
|
+
output = Path(output_directory / "openapi_schemas.py")
|
|
98
|
+
output.unlink(missing_ok=True)
|
|
99
|
+
generate(
|
|
100
|
+
response.text,
|
|
101
|
+
input_file_type=InputFileType.OpenAPI,
|
|
102
|
+
input_filename="openapi.json",
|
|
103
|
+
output=output,
|
|
104
|
+
# set up the output model types
|
|
105
|
+
output_model_type=DataModelType.PydanticV2BaseModel,
|
|
106
|
+
)
|
|
107
|
+
_bind_sport_to_ost(output)
|
|
108
|
+
_restore_naive_local_datetimes(output)
|
|
109
|
+
|
|
110
|
+
model = output.read_text()
|
|
111
|
+
print(model)
|
|
@@ -2181,7 +2181,7 @@ class Client(_OAuth2Mixin, _DelegationMixin, _TokenStorageMixin, _LocalCacheMixi
|
|
|
2181
2181
|
dailies = [DailyResponse.model_validate(item) for item in response.json()]
|
|
2182
2182
|
if as_dataframe:
|
|
2183
2183
|
if not dailies:
|
|
2184
|
-
df = pd.DataFrame(columns=["date", "value", "source"])
|
|
2184
|
+
df = pd.DataFrame(columns=["date", "value", "status", "source"])
|
|
2185
2185
|
df = df.set_index("date")
|
|
2186
2186
|
else:
|
|
2187
2187
|
df = pd.DataFrame([d.model_dump() for d in dailies])
|