Skip to content

Commit 26cefa5

Browse files
committed
fix(release): keep sdk core pin in lockstep
1 parent 4abac12 commit 26cefa5

7 files changed

Lines changed: 103 additions & 7 deletions

File tree

.github/workflows/ci-pythinker-cli.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,8 @@ jobs:
261261
--root-pyproject pyproject.toml \
262262
--pythinker-core-pyproject packages/pythinker-core/pyproject.toml \
263263
--pythinker-host-pyproject packages/pythinker-host/pyproject.toml \
264-
--pythinker-review-pyproject packages/pythinker-review/pyproject.toml
264+
--pythinker-review-pyproject packages/pythinker-review/pyproject.toml \
265+
--pythinker-sdk-pyproject sdks/pythinker-sdk/pyproject.toml
265266
266267
- name: Check pythinker-code version alignment
267268
if: steps.version.outputs.bump == 'true'

.github/workflows/release-pythinker-cli.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ jobs:
6767
--root-pyproject pyproject.toml \
6868
--pythinker-core-pyproject packages/pythinker-core/pyproject.toml \
6969
--pythinker-host-pyproject packages/pythinker-host/pyproject.toml \
70-
--pythinker-review-pyproject packages/pythinker-review/pyproject.toml
70+
--pythinker-review-pyproject packages/pythinker-review/pyproject.toml \
71+
--pythinker-sdk-pyproject sdks/pythinker-sdk/pyproject.toml
7172
7273
# Hard release gate: every PyPI release must ship a matching README +
7374
# CHANGELOG update. README.md must contain a "What's New in <version>"

scripts/check_pythinker_dependency_versions.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def main() -> int:
4646
parser.add_argument("--pythinker-core-pyproject", type=Path, required=True)
4747
parser.add_argument("--pythinker-host-pyproject", type=Path, required=True)
4848
parser.add_argument("--pythinker-review-pyproject", type=Path, required=True)
49+
parser.add_argument("--pythinker-sdk-pyproject", type=Path, required=True)
4950
args = parser.parse_args()
5051

5152
try:
@@ -63,6 +64,7 @@ def main() -> int:
6364
return 1
6465

6566
errors: list[str] = []
67+
package_versions: dict[str, str] = {}
6668
for name, pyproject_path in (
6769
("pythinker-core", args.pythinker_core_pyproject),
6870
("pythinker-host", args.pythinker_host_pyproject),
@@ -73,6 +75,7 @@ def main() -> int:
7375
except ValueError as exc:
7476
errors.append(str(exc))
7577
continue
78+
package_versions[name] = package_version
7679

7780
pinned_version = find_pinned_dependency(deps, name)
7881
if pinned_version is None:
@@ -85,6 +88,28 @@ def main() -> int:
8588
f"but {pyproject_path} has {package_version}."
8689
)
8790

91+
try:
92+
sdk_project = load_project_table(args.pythinker_sdk_pyproject)
93+
except ValueError as exc:
94+
errors.append(str(exc))
95+
else:
96+
sdk_deps = sdk_project.get("dependencies", [])
97+
if not isinstance(sdk_deps, list):
98+
errors.append(
99+
f"project.dependencies must be a list in {args.pythinker_sdk_pyproject}"
100+
)
101+
elif core_version := package_versions.get("pythinker-core"):
102+
sdk_core_pin = find_pinned_dependency(sdk_deps, "pythinker-core")
103+
if sdk_core_pin is None:
104+
errors.append(
105+
f"Missing pinned dependency for pythinker-core in {args.pythinker_sdk_pyproject}."
106+
)
107+
elif sdk_core_pin != core_version:
108+
errors.append(
109+
f"pythinker-sdk core dependency mismatch: sdk depends on {sdk_core_pin}, "
110+
f"but {args.pythinker_core_pyproject} has {core_version}."
111+
)
112+
88113
if errors:
89114
for error in errors:
90115
print(f"error: {error}", file=sys.stderr)

scripts/release.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
CORE_PYPROJECT = REPO_ROOT / "packages" / "pythinker-core" / "pyproject.toml"
2828
HOST_PYPROJECT = REPO_ROOT / "packages" / "pythinker-host" / "pyproject.toml"
2929
REVIEW_PYPROJECT = REPO_ROOT / "packages" / "pythinker-review" / "pyproject.toml"
30+
SDK_PYPROJECT = REPO_ROOT / "sdks" / "pythinker-sdk" / "pyproject.toml"
3031

3132
# Single source for the three hand-authored changelog files. validate() asserts
3233
# the `## Unreleased` anchor in ALL of them before any write, and rewrite()
@@ -206,6 +207,7 @@ def rewrite(target: str, *, bump_core: str | None, bump_host: str | None) -> Non
206207
if bump_core:
207208
set_root_version(CORE_PYPROJECT, bump_core)
208209
set_dependency_pin(ROOT_PYPROJECT, "pythinker-core", bump_core)
210+
set_dependency_pin(SDK_PYPROJECT, "pythinker-core", bump_core)
209211
if bump_host:
210212
set_root_version(HOST_PYPROJECT, bump_host)
211213
set_dependency_pin(ROOT_PYPROJECT, "pythinker-host", bump_host)
@@ -247,6 +249,8 @@ def rewrite(target: str, *, bump_core: str | None, bump_host: str | None) -> Non
247249
"packages/pythinker-host/pyproject.toml",
248250
"--pythinker-review-pyproject",
249251
"packages/pythinker-review/pyproject.toml",
252+
"--pythinker-sdk-pyproject",
253+
"sdks/pythinker-sdk/pyproject.toml",
250254
],
251255
["uv", "sync", "--frozen", "--all-extras", "--all-packages"],
252256
["uv", "run", "pytest", "tests/test_version_lockstep.py", "-q"],

sdks/pythinker-sdk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ classifiers = [
1919
"Topic :: Software Development :: Libraries :: Python Modules",
2020
"Topic :: Scientific/Engineering :: Artificial Intelligence",
2121
]
22-
dependencies = ["pythinker-core==1.1.1", "mcp>=1.23,<2", "httpx>=0.28.1"]
22+
dependencies = ["pythinker-core==1.2.0", "mcp>=1.23,<2", "httpx>=0.28.1"]
2323

2424
[project.urls]
2525
Homepage = "https://github.com/Pythoughts-labs/pythinker-code"

tests/test_release_py.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ def test_dep_check_passes_when_review_pin_matches(tmp_path: Path) -> None:
4444
review = _write(
4545
tmp_path, "review.toml", '[project]\nname="pythinker-review"\nversion="0.1.0"\n'
4646
)
47+
sdk = _write(
48+
tmp_path,
49+
"sdk.toml",
50+
'[project]\nname="pythinker-sdk"\nversion="1.1.0"\n'
51+
'dependencies=["pythinker-core==1.1.1"]\n',
52+
)
4753
result = _run_dep_check(
4854
"--root-pyproject",
4955
str(root),
@@ -53,6 +59,8 @@ def test_dep_check_passes_when_review_pin_matches(tmp_path: Path) -> None:
5359
str(host),
5460
"--pythinker-review-pyproject",
5561
str(review),
62+
"--pythinker-sdk-pyproject",
63+
str(sdk),
5664
)
5765
assert result.returncode == 0, result.stderr
5866

@@ -70,6 +78,12 @@ def test_dep_check_fails_when_review_pin_drifts(tmp_path: Path) -> None:
7078
review = _write(
7179
tmp_path, "review.toml", '[project]\nname="pythinker-review"\nversion="0.2.0"\n'
7280
)
81+
sdk = _write(
82+
tmp_path,
83+
"sdk.toml",
84+
'[project]\nname="pythinker-sdk"\nversion="1.1.0"\n'
85+
'dependencies=["pythinker-core==1.1.1"]\n',
86+
)
7387
result = _run_dep_check(
7488
"--root-pyproject",
7589
str(root),
@@ -79,11 +93,48 @@ def test_dep_check_fails_when_review_pin_drifts(tmp_path: Path) -> None:
7993
str(host),
8094
"--pythinker-review-pyproject",
8195
str(review),
96+
"--pythinker-sdk-pyproject",
97+
str(sdk),
8298
)
8399
assert result.returncode == 1
84100
assert "pythinker-review version mismatch" in result.stderr
85101

86102

103+
def test_dep_check_fails_when_sdk_core_pin_drifts(tmp_path: Path) -> None:
104+
root = _write(
105+
tmp_path,
106+
"root.toml",
107+
'[project]\nname="pythinker-code"\nversion="0.29.0"\n'
108+
'dependencies=["pythinker-core[contrib]==1.2.0","pythinker-host==1.0.0",'
109+
'"pythinker-review==0.1.0"]\n',
110+
)
111+
core = _write(tmp_path, "core.toml", '[project]\nname="pythinker-core"\nversion="1.2.0"\n')
112+
host = _write(tmp_path, "host.toml", '[project]\nname="pythinker-host"\nversion="1.0.0"\n')
113+
review = _write(
114+
tmp_path, "review.toml", '[project]\nname="pythinker-review"\nversion="0.1.0"\n'
115+
)
116+
sdk = _write(
117+
tmp_path,
118+
"sdk.toml",
119+
'[project]\nname="pythinker-sdk"\nversion="1.1.0"\n'
120+
'dependencies=["pythinker-core==1.1.1"]\n',
121+
)
122+
result = _run_dep_check(
123+
"--root-pyproject",
124+
str(root),
125+
"--pythinker-core-pyproject",
126+
str(core),
127+
"--pythinker-host-pyproject",
128+
str(host),
129+
"--pythinker-review-pyproject",
130+
str(review),
131+
"--pythinker-sdk-pyproject",
132+
str(sdk),
133+
)
134+
assert result.returncode == 1
135+
assert "pythinker-sdk core dependency mismatch" in result.stderr
136+
137+
87138
def test_parse_semver_accepts_xyz() -> None:
88139
assert release_tool.parse_semver("0.28.0") == (0, 28, 0)
89140

tests/test_version_lockstep.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@ def _version(rel: str) -> str:
1313
return tomllib.load(fh)["project"]["version"]
1414

1515

16-
def _root_deps() -> list[str]:
17-
with (REPO_ROOT / "pyproject.toml").open("rb") as fh:
16+
def _deps(rel: str) -> list[str]:
17+
with (REPO_ROOT / rel).open("rb") as fh:
1818
return tomllib.load(fh)["project"]["dependencies"]
1919

2020

21-
def _pin(name: str) -> str:
22-
for dep in _root_deps():
21+
def _root_deps() -> list[str]:
22+
return _deps("pyproject.toml")
23+
24+
25+
def _pin_in(deps: list[str], name: str) -> str:
26+
for dep in deps:
2327
head = dep.split("==", 1)
2428
if len(head) == 2 and head[0].split("[")[0] == name:
2529
return head[1].split(";")[0].strip()
@@ -39,10 +43,20 @@ def test_subpackage_pins_match_versions() -> None:
3943
assert _pin("pythinker-review") == _version("packages/pythinker-review/pyproject.toml")
4044

4145

46+
def _pin(name: str) -> str:
47+
return _pin_in(_root_deps(), name)
48+
49+
4250
def test_review_is_frozen_at_0_1_0() -> None:
4351
assert _pin("pythinker-review") == "0.1.0"
4452

4553

54+
def test_sdk_core_pin_matches_core_version() -> None:
55+
assert _pin_in(_deps("sdks/pythinker-sdk/pyproject.toml"), "pythinker-core") == _version(
56+
"packages/pythinker-core/pyproject.toml"
57+
)
58+
59+
4660
def test_readme_heading_and_pip_snippet() -> None:
4761
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
4862
assert f"What's New in {VERSION}" in readme

0 commit comments

Comments
 (0)