Skip to content

Commit 680f030

Browse files
chore(deps): daily dependency update (#309)
Co-authored-by: Abhijeet Prasad <abhijeet@braintrustdata.com>
1 parent e3e9f63 commit 680f030

203 files changed

Lines changed: 20329 additions & 18109 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/dependency-updates.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ jobs:
1919
cache: true
2020
experimental: true
2121

22+
- name: Update matrix latest pins
23+
working-directory: py
24+
run: python3 scripts/update-matrix-latest.py
25+
2226
- name: Upgrade lockfile
2327
working-directory: py
2428
run: uv lock --upgrade
@@ -38,7 +42,7 @@ jobs:
3842
except ModuleNotFoundError:
3943
import tomli as tomllib
4044
41-
diff = subprocess.check_output(["git", "diff", "uv.lock"], text=True)
45+
diff = subprocess.check_output(["git", "diff", "--", "pyproject.toml", "uv.lock"], text=True)
4246
if not diff:
4347
print("changed=false")
4448
raise SystemExit(0)
@@ -49,9 +53,16 @@ jobs:
4953
5054
matrix = pyproject.get("tool", {}).get("braintrust", {}).get("matrix", {})
5155
52-
# Extract the base package name from each matrix requirement string
56+
# Extract the base package name from provider-related matrix requirement strings.
57+
# Exclude pure test/infra pins that do not affect cassette coverage.
58+
provider_matrix = {
59+
key: versions
60+
for key, versions in matrix.items()
61+
if key not in {"pytest-matrix", "braintrust-core"}
62+
}
63+
5364
provider_pkgs = set()
54-
for _prefix, versions in matrix.items():
65+
for _prefix, versions in provider_matrix.items():
5566
for req in versions.values():
5667
# req looks like "openai==1.92.0" or "pydantic-ai==1.82.0"
5768
pkg = req.split("==")[0].split(">=")[0].split("<=")[0].strip()
@@ -74,7 +85,7 @@ jobs:
7485
with:
7586
title: "chore(deps): daily dependency update"
7687
body: |
77-
Automated daily dependency update via `uv lock --upgrade`.
88+
Automated daily dependency update via `python scripts/update-matrix-latest.py && uv lock --upgrade`.
7889
7990
${{ steps.labels.outputs.needs_rerecord == 'true' && '⚠️ **Provider SDK packages changed.** A human needs to re-record cassettes locally before merging.' || '✅ Only test infrastructure deps changed. Safe to merge if CI passes.' }}
8091
branch: deps/daily-update-${{ steps.date.outputs.date }}

py/pyproject.toml

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -236,19 +236,19 @@ conflicts = [
236236
#
237237
# Each key is a provider prefix. The value is a dict mapping human-readable
238238
# version tags to the pip requirement string. "latest" is pinned to an
239-
# explicit version and bumped by the weekly dependency-update workflow.
239+
# explicit version and bumped by the daily dependency-update workflow.
240240
# ---------------------------------------------------------------------------
241241

242242
[tool.braintrust.matrix]
243243

244244
[tool.braintrust.matrix.openai]
245-
latest = "openai==2.31.0"
245+
latest = "openai==2.32.0"
246246
"1.92.0" = "openai==1.92.0"
247247
"1.77.0" = "openai==1.77.0"
248248
"1.71.0" = "openai==1.71.0"
249249

250250
[tool.braintrust.matrix.anthropic]
251-
latest = "anthropic==0.95.0"
251+
latest = "anthropic==0.96.0"
252252
"0.48.0" = "anthropic==0.48.0"
253253

254254
[tool.braintrust.matrix.openai-agents]
@@ -260,7 +260,7 @@ latest = "litellm==1.83.8"
260260
"1.74.0" = "litellm==1.74.0"
261261

262262
[tool.braintrust.matrix.claude-agent-sdk]
263-
latest = "claude-agent-sdk==0.1.59"
263+
latest = "claude-agent-sdk==0.1.60"
264264
"0.1.10" = "claude-agent-sdk==0.1.10"
265265

266266
[tool.braintrust.matrix.agno]
@@ -273,11 +273,11 @@ latest = "agentscope==1.0.18"
273273
"1.0.0" = "agentscope==1.0.0"
274274

275275
[tool.braintrust.matrix.pydantic-ai-integration]
276-
latest = "pydantic-ai==1.82.0"
276+
latest = "pydantic-ai==1.83.0"
277277
"1.10.0" = "pydantic-ai==1.10.0"
278278

279279
[tool.braintrust.matrix.pydantic-ai-wrap-openai]
280-
latest = "pydantic-ai==1.82.0"
280+
latest = "pydantic-ai==1.83.0"
281281
"1.0.1" = "pydantic-ai==1.0.1"
282282
"0.1.9" = "pydantic-ai==0.1.9"
283283

@@ -298,19 +298,19 @@ latest = "google-adk==1.30.0"
298298
"1.14.1" = "google-adk==1.14.1"
299299

300300
[tool.braintrust.matrix.langchain-core]
301-
latest = "langchain-core==1.2.30"
301+
latest = "langchain-core==1.2.31"
302302
"0.3.28" = "langchain-core==0.3.28"
303303

304304
[tool.braintrust.matrix.openrouter]
305305
latest = "openrouter==0.9.1"
306306
"0.6.0" = "openrouter==0.6.0"
307307

308308
[tool.braintrust.matrix.mistralai]
309-
latest = "mistralai==2.3.2"
309+
latest = "mistralai==2.4.0"
310310
"1.12.4" = "mistralai==1.12.4"
311311

312312
[tool.braintrust.matrix.temporalio]
313-
latest = "temporalio==1.25.0"
313+
latest = "temporalio==1.26.0"
314314
"1.20.0" = "temporalio==1.20.0"
315315
"1.19.0" = "temporalio==1.19.0"
316316

py/scripts/update-matrix-latest.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#!/usr/bin/env python3
2+
"""Update ``[tool.braintrust.matrix].*.latest`` pins in ``pyproject.toml``.
3+
4+
This script reads the current matrix table, fetches the newest published version
5+
for each package from PyPI, and rewrites only the ``latest = ...`` lines in the
6+
matching matrix sections.
7+
8+
Usage:
9+
python scripts/update-matrix-latest.py # apply updates in place
10+
python scripts/update-matrix-latest.py --dry-run
11+
"""
12+
13+
import argparse
14+
import json
15+
import pathlib
16+
import re
17+
import sys
18+
import urllib.error
19+
import urllib.parse
20+
import urllib.request
21+
22+
23+
if sys.version_info >= (3, 11):
24+
import tomllib
25+
else:
26+
try:
27+
import tomllib
28+
except ModuleNotFoundError:
29+
import tomli as tomllib # type: ignore[no-redef]
30+
31+
32+
_PROJECT_DIR = pathlib.Path(__file__).resolve().parent.parent
33+
_PYPROJECT = _PROJECT_DIR / "pyproject.toml"
34+
_MATRIX_SECTION_PREFIX = "[tool.braintrust.matrix."
35+
_LATEST_LINE_RE = re.compile(r'^(?P<indent>\s*)latest\s*=\s*"(?P<req>[^"]+)"(?P<suffix>\s*(?:#.*)?)$')
36+
_EXACT_REQ_RE = re.compile(r"^(?P<name>[A-Za-z0-9_.-]+)==(?P<version>[^\s]+)$")
37+
_USER_AGENT = "braintrust-sdk-python dependency updater"
38+
_TIMEOUT_SECS = 30
39+
40+
41+
class UpdateError(RuntimeError):
42+
"""Raised when the matrix latest pins cannot be updated safely."""
43+
44+
45+
def _load_matrix() -> dict[str, str]:
46+
pyproject = tomllib.loads(_PYPROJECT.read_text())
47+
matrix = pyproject.get("tool", {}).get("braintrust", {}).get("matrix", {})
48+
49+
latest_reqs: dict[str, str] = {}
50+
for key, versions in matrix.items():
51+
latest_req = versions.get("latest")
52+
if latest_req:
53+
latest_reqs[key] = latest_req
54+
return latest_reqs
55+
56+
57+
def _parse_exact_req(req: str) -> tuple[str, str]:
58+
match = _EXACT_REQ_RE.fullmatch(req.strip())
59+
if not match:
60+
raise UpdateError(f"Expected an exact 'package==version' requirement, got: {req}")
61+
return match.group("name"), match.group("version")
62+
63+
64+
def _fetch_latest_version(package_name: str) -> str:
65+
url = f"https://pypi.org/pypi/{urllib.parse.quote(package_name)}/json"
66+
request = urllib.request.Request(url, headers={"User-Agent": _USER_AGENT})
67+
68+
try:
69+
with urllib.request.urlopen(request, timeout=_TIMEOUT_SECS) as response:
70+
payload = json.load(response)
71+
except urllib.error.HTTPError as exc:
72+
raise UpdateError(f"Failed to fetch {package_name} metadata from PyPI: HTTP {exc.code}") from exc
73+
except urllib.error.URLError as exc:
74+
raise UpdateError(f"Failed to fetch {package_name} metadata from PyPI: {exc.reason}") from exc
75+
76+
version = str(payload.get("info", {}).get("version", "")).strip()
77+
if not version:
78+
raise UpdateError(f"PyPI did not return a version for {package_name}")
79+
return version
80+
81+
82+
def _compute_updates() -> dict[str, tuple[str, str]]:
83+
latest_reqs = _load_matrix()
84+
package_cache: dict[str, str] = {}
85+
updates: dict[str, tuple[str, str]] = {}
86+
87+
for matrix_key, current_req in latest_reqs.items():
88+
package_name, current_version = _parse_exact_req(current_req)
89+
latest_version = package_cache.get(package_name)
90+
if latest_version is None:
91+
latest_version = _fetch_latest_version(package_name)
92+
package_cache[package_name] = latest_version
93+
94+
if latest_version != current_version:
95+
updates[matrix_key] = (current_req, f"{package_name}=={latest_version}")
96+
97+
return updates
98+
99+
100+
def _rewrite_pyproject(text: str, replacements: dict[str, tuple[str, str]]) -> tuple[str, list[str]]:
101+
if not replacements:
102+
return text, []
103+
104+
lines = text.splitlines(keepends=True)
105+
touched_keys: list[str] = []
106+
current_matrix_key: str | None = None
107+
108+
for index, line in enumerate(lines):
109+
stripped = line.strip()
110+
if stripped.startswith("[") and stripped.endswith("]"):
111+
current_matrix_key = None
112+
if stripped.startswith(_MATRIX_SECTION_PREFIX):
113+
current_matrix_key = stripped[len(_MATRIX_SECTION_PREFIX) : -1]
114+
continue
115+
116+
if current_matrix_key is None or current_matrix_key not in replacements:
117+
continue
118+
119+
line_body = line[:-1] if line.endswith("\n") else line
120+
newline = "\n" if line.endswith("\n") else ""
121+
match = _LATEST_LINE_RE.fullmatch(line_body)
122+
if not match:
123+
continue
124+
125+
old_req, new_req = replacements[current_matrix_key]
126+
if match.group("req") != old_req:
127+
raise UpdateError(
128+
f"Expected latest pin for matrix key {current_matrix_key!r} to be {old_req!r}, "
129+
f"found {match.group('req')!r}"
130+
)
131+
132+
lines[index] = f'{match.group("indent")}latest = "{new_req}"{match.group("suffix")}{newline}'
133+
touched_keys.append(current_matrix_key)
134+
current_matrix_key = None
135+
136+
missing = sorted(set(replacements) - set(touched_keys))
137+
if missing:
138+
raise UpdateError(f"Did not find latest lines for matrix key(s): {', '.join(missing)}")
139+
140+
return "".join(lines), touched_keys
141+
142+
143+
def main() -> None:
144+
parser = argparse.ArgumentParser(description=__doc__)
145+
parser.add_argument("--dry-run", action="store_true", help="Show updates without writing pyproject.toml")
146+
args = parser.parse_args()
147+
148+
replacements = _compute_updates()
149+
if not replacements:
150+
print("No matrix latest pins to update.")
151+
return
152+
153+
original_text = _PYPROJECT.read_text()
154+
updated_text, touched_keys = _rewrite_pyproject(original_text, replacements)
155+
156+
for matrix_key in touched_keys:
157+
old_req, new_req = replacements[matrix_key]
158+
print(f"{matrix_key}: {old_req} -> {new_req}")
159+
160+
if args.dry_run:
161+
return
162+
163+
_PYPROJECT.write_text(updated_text)
164+
print(f"Updated {_PYPROJECT.relative_to(_PROJECT_DIR)}")
165+
166+
167+
if __name__ == "__main__":
168+
main()
7 Bytes
Loading

0 commit comments

Comments
 (0)