Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ repos:
language: python
pass_filenames: false
files: (pyproject\.toml|cassettes/)
- id: sync-pytest-pin
name: sync pytest pin (matrix -> dep group)
entry: python py/scripts/sync-pytest-pin.py --check
language: python
pass_filenames: false
files: ^py/pyproject\.toml$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.5
hooks:
Expand Down
6 changes: 5 additions & 1 deletion py/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
PYTHON ?= python

.PHONY: lint pylint test test-wheel _template-version clean fixup build verify-build verify help install-build-deps install-dev test-core check-stale-cassettes _check-git-clean bench bench-compare
.PHONY: lint pylint test test-wheel _template-version clean fixup build verify-build verify help install-build-deps install-dev test-core check-stale-cassettes sync-pytest-pin _check-git-clean bench bench-compare

clean:
rm -rf build dist
Expand Down Expand Up @@ -33,6 +33,9 @@ test-core:
check-stale-cassettes:
$(PYTHON) scripts/check-stale-cassettes.py

sync-pytest-pin:
$(PYTHON) scripts/sync-pytest-pin.py

bench:
$(PYTHON) -m benchmarks $(BENCH_ARGS)

Expand Down Expand Up @@ -75,6 +78,7 @@ help:
@echo " bench-compare - Compare two benchmark results (BENCH_BASE=... BENCH_NEW=...)"
@echo " build - Build Python package"
@echo " check-stale-cassettes - Detect orphaned cassette version directories"
@echo " sync-pytest-pin - Sync [dependency-groups].test pytest pin from matrix"
@echo " clean - Remove build artifacts"
@echo " help - Show this help message"
@echo " install-build-deps - Install build dependencies for CI"
Expand Down
4 changes: 3 additions & 1 deletion py/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ braintrust = ["py.typed"]

# -- Base test deps (all sessions include this) --------------------------------
test = [
"pytest==9.0.2",
"pytest==9.0.3",
"pytest-asyncio==1.3.0",
"pytest-vcr==1.0.2",
]
Expand Down Expand Up @@ -388,6 +388,8 @@ latest = "temporalio==1.27.0"
"1.19.0" = "temporalio==1.19.0"

[tool.braintrust.matrix.pytest-matrix]
# Canonical pytest pin. The matching entry in [dependency-groups].test is
# kept in sync by py/scripts/sync-pytest-pin.py (enforced by pre-commit).
latest = "pytest==9.0.3"
"8.4.2" = "pytest==8.4.2"

Expand Down
84 changes: 84 additions & 0 deletions py/scripts/sync-pytest-pin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Sync [dependency-groups].test pytest pin from the matrix table.

[tool.braintrust.matrix.pytest-matrix].latest is the canonical pytest pin.
The base [dependency-groups].test entry has to match it (so uv.lock anchors
the same version that test_pytest_plugin(latest) exercises), but TOML has
no variable substitution, so we rewrite it mechanically.

Run without arguments to rewrite the dep-group pin in place. Run with
``--check`` (used by pre-commit) to fail without modifying anything.
"""

import argparse
import pathlib
import re
import sys

import tomllib


PYPROJECT = pathlib.Path(__file__).resolve().parent.parent / "pyproject.toml"


def _compute_new_text(text: str) -> tuple[str, str]:
data = tomllib.loads(text)
canonical = data["tool"]["braintrust"]["matrix"]["pytest-matrix"]["latest"]
if not isinstance(canonical, str) or not canonical.startswith("pytest=="):
raise SystemExit(
f"sync-pytest-pin: [tool.braintrust.matrix.pytest-matrix].latest "
f"must be a 'pytest==X.Y.Z' string, got: {canonical!r}"
)

# Match a pytest pin used as a list element (leading indent + quote). This
# uniquely identifies the [dependency-groups].test entry and does not
# collide with `latest = "pytest==..."` or `"8.4.2" = "pytest==..."` in
# the matrix table (those start with a key, not whitespace+quote).
pattern = re.compile(r'(?m)^(?P<indent>[ \t]+)"pytest==[^"]+"(?P<tail>,?)\s*$')
matches = list(pattern.finditer(text))
if not matches:
raise SystemExit("sync-pytest-pin: no pytest list-element pin found in pyproject.toml")
if len(matches) > 1:
raise SystemExit(
"sync-pytest-pin: multiple pytest list-element pins found; refusing "
"to guess. Update py/scripts/sync-pytest-pin.py."
)

m = matches[0]
new_line = f'{m.group("indent")}"{canonical}"{m.group("tail")}'
new_text = text[: m.start()] + new_line + text[m.end() :]
return new_text, canonical


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--check",
action="store_true",
help="exit non-zero if the dep-group pin is out of sync; do not modify",
)
args = parser.parse_args()

text = PYPROJECT.read_text()
new_text, canonical = _compute_new_text(text)

if new_text == text:
return 0

if args.check:
print(
f"[dependency-groups].test pytest pin is out of sync with "
f"[tool.braintrust.matrix.pytest-matrix].latest ({canonical}).\n"
f"Run: python py/scripts/sync-pytest-pin.py && (cd py && uv lock)",
file=sys.stderr,
)
return 1

PYPROJECT.write_text(new_text)
print(f"sync-pytest-pin: updated [dependency-groups].test -> {canonical}")
print("note: run `cd py && uv lock` to refresh uv.lock", file=sys.stderr)
return 0


if __name__ == "__main__":
sys.exit(main())
36 changes: 18 additions & 18 deletions py/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading