From 9af609fb795e5515961f7863633adcbdff066114 Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Wed, 8 Apr 2026 18:41:51 -0400 Subject: [PATCH] ci(checks): add vulture dead-code validation Run vulture in the GitHub checks workflow and expose a matching make target for local development. This keeps dead-code detection aligned between CI and local runs. Clean up unused parameters and dead plumbing in the Python SDK so the new check passes at 100 percent confidence without changing behavior. --- .github/workflows/checks.yaml | 17 +++++++++++++++++ py/Makefile | 8 ++++++-- py/requirements-dev.txt | 1 + py/src/braintrust/framework.py | 4 ++-- py/src/braintrust/integrations/base.py | 2 +- .../integrations/langchain/callbacks.py | 2 +- py/src/braintrust/logger.py | 7 +++---- 7 files changed, 31 insertions(+), 10 deletions(-) diff --git a/.github/workflows/checks.yaml b/.github/workflows/checks.yaml index 4d4b1812..e3f92a6a 100644 --- a/.github/workflows/checks.yaml +++ b/.github/workflows/checks.yaml @@ -80,6 +80,21 @@ jobs: run: | mise exec python@${{ matrix.python-version }} -- python ./py/scripts/nox-matrix.py ${{ matrix.shard }} 4 + vulture: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - name: Set up mise + uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1 + with: + cache: true + experimental: true + - name: Install dev dependencies + run: mise exec -- make -C py install-dev + - name: Run vulture dead-code check + run: mise exec -- make -C py vulture + adk-py: uses: ./.github/workflows/adk-py-test.yaml @@ -116,6 +131,7 @@ jobs: - ensure-pinned-actions - smoke - nox + - vulture - adk-py - langchain-py - upload-wheel @@ -141,6 +157,7 @@ jobs: check_result "ensure-pinned-actions" "${{ needs.ensure-pinned-actions.result }}" check_result "smoke" "${{ needs.smoke.result }}" check_result "nox" "${{ needs.nox.result }}" + check_result "vulture" "${{ needs.vulture.result }}" check_result "adk-py" "${{ needs.adk-py.result }}" check_result "langchain-py" "${{ needs.langchain-py.result }}" check_result "upload-wheel" "${{ needs.upload-wheel.result }}" diff --git a/py/Makefile b/py/Makefile index 56becdfe..5677f3a0 100644 --- a/py/Makefile +++ b/py/Makefile @@ -2,7 +2,7 @@ PYTHON ?= python UV := $(PYTHON) -m uv UV_VERSION := $(shell awk '$$1=="uv" { print $$2 }' ../.tool-versions) -.PHONY: lint pylint test test-wheel _template-version clean fixup build verify-build verify help install-build-deps install-dev install-optional test-core _check-git-clean bench bench-compare +.PHONY: lint pylint vulture test test-wheel _template-version clean fixup build verify-build verify help install-build-deps install-dev install-optional test-core _check-git-clean bench bench-compare clean: rm -rf build dist @@ -17,6 +17,9 @@ lint: fixup pylint: nox -s pylint +vulture: + $(PYTHON) -m vulture src/braintrust --min-confidence 100 --exclude "*/test_*.py,*/_test_*.py" + test: nox -x @@ -58,7 +61,7 @@ _check-git-clean: verify-build: _check-git-clean build test-wheel -verify: lint test +verify: lint vulture test install-build-deps: $(if $(UV_VERSION),,$(error Failed to read uv version from ../.tool-versions)) @@ -85,6 +88,7 @@ help: @echo " install-dev - Install package in development mode with all dependencies" @echo " lint - Run pylint checks" @echo " pylint - Run pylint without pre-commit hooks" + @echo " vulture - Detect unused/dead code" @echo " test - Run all tests" @echo " test-core - Run core tests only" @echo " test-wheel - Run tests against built wheel" diff --git a/py/requirements-dev.txt b/py/requirements-dev.txt index 14fb3c47..04799aaf 100644 --- a/py/requirements-dev.txt +++ b/py/requirements-dev.txt @@ -5,6 +5,7 @@ pre-commit==4.5.1 pydoc-markdown==4.8.2 pylint==4.0.5 pyperf==2.10.0 +vulture==2.16 pytest==9.0.2 pytest-asyncio==1.3.0 pytest-forked==1.6.0 diff --git a/py/src/braintrust/framework.py b/py/src/braintrust/framework.py index 378aa852..5aa9172a 100644 --- a/py/src/braintrust/framework.py +++ b/py/src/braintrust/framework.py @@ -203,7 +203,7 @@ def tags(self) -> Sequence[str]: """ @abc.abstractmethod - def report_progress(self, progress: TaskProgressEvent) -> None: + def report_progress(self, progress: TaskProgressEvent) -> None: # noqa: F841 """ Report progress that will show up in the playground. """ @@ -459,7 +459,7 @@ class EvalResultWithSummary(SerializableDataClass, Generic[Input, Output]): summary: ExperimentSummary results: list[EvalResult[Input, Output]] - def _repr_pretty_(self, p, cycle): + def _repr_pretty_(self, p, _cycle): p.text(f'EvalResultWithSummary(summary="...", results=[...])') diff --git a/py/src/braintrust/integrations/base.py b/py/src/braintrust/integrations/base.py index 68d2936a..21094fa7 100644 --- a/py/src/braintrust/integrations/base.py +++ b/py/src/braintrust/integrations/base.py @@ -194,7 +194,7 @@ def applies(cls, module: Any | None, version: str | None, *, target: Any | None @classmethod @abstractmethod - def patch_class(cls, target_class: type[Any]) -> bool | None: + def patch_class(cls, target_class: type[Any]) -> bool | None: # noqa: F841 """Patch one discovered class. Return ``False`` to skip marking the class as patched. Any other return diff --git a/py/src/braintrust/integrations/langchain/callbacks.py b/py/src/braintrust/integrations/langchain/callbacks.py index 50650da0..1c553742 100644 --- a/py/src/braintrust/integrations/langchain/callbacks.py +++ b/py/src/braintrust/integrations/langchain/callbacks.py @@ -552,7 +552,7 @@ def on_text( def on_retry( self, - retry_state: RetryCallState, + _retry_state: RetryCallState, *, run_id: UUID, parent_run_id: UUID | None = None, diff --git a/py/src/braintrust/logger.py b/py/src/braintrust/logger.py index f7c56d2b..ef7095ac 100644 --- a/py/src/braintrust/logger.py +++ b/py/src/braintrust/logger.py @@ -1445,7 +1445,7 @@ def _register_dropped_item_count(self, num_items): self._queue_drop_logging_state["last_logged_timestamp"] = time_now @staticmethod - def _write_payload_to_dir(payload_dir, payload, debug_logging_adjective=None): + def _write_payload_to_dir(payload_dir, payload): payload_file = os.path.join(payload_dir, f"payload_{time.time()}_{str(uuid.uuid4())[:8]}.json") try: os.makedirs(payload_dir, exist_ok=True) @@ -2839,7 +2839,7 @@ def _validate_and_sanitize_experiment_log_partial_args(event: Mapping[str, Any]) # Note that this only checks properties that are expected of a complete event. # _validate_and_sanitize_experiment_log_partial_args should still be invoked # (after handling special fields like 'id'). -def _validate_and_sanitize_experiment_log_full_args(event: Mapping[str, Any], has_dataset: bool) -> Mapping[str, Any]: +def _validate_and_sanitize_experiment_log_full_args(event: Mapping[str, Any]) -> Mapping[str, Any]: input = event.get("input") inputs = event.get("inputs") if (input is not None and inputs is not None) or (input is None and inputs is None): @@ -3868,8 +3868,7 @@ def log( metadata=metadata, metrics=metrics, id=id, - ), - self.dataset is not None, + ) ) span = self._start_span_impl(start_time=self.last_start_time, lookup_span_parent=False, **event) self.last_start_time = span.end()