From 0ca605b5ccb8344c848dce09ea9a41e877045d9f Mon Sep 17 00:00:00 2001 From: Evan Sultanik Date: Wed, 16 Sep 2026 12:05:03 -0400 Subject: [PATCH 1/2] Unpin the backend env var in tests that watch the automatic choice Two tests in test_batch_distance.py assert that a batch reaches the numpy backend when nothing pins a backend. Both read the ambient environment, so exporting GRAPHTAGE_BATCH_BACKEND=python made them fail: FAILED TestBackendSelection::test_environment_variable_overrides_the_automatic_choice FAILED TestCostOracle::test_one_big_pair_goes_to_a_vectorized_backend The product was correct in both cases -- the variable did exactly what it promises. The tests simply could not observe the unpinned baseline they were written to check, so anyone exercising the other backend saw two spurious failures from tests that never mention the variable. A new no_backend_pin() helper removes the variable for the span that watches the automatic choice, leaving the halves that pin a backend explicitly alone. Verified against both mutations: ignoring the variable at its two read sites in batch_distance.py still fails the first test, and raising VECTORIZED_MIN_CELLS still fails the second. The suite now passes with the variable set to python, set to numpy, and unset. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01F2sHz5c5TvMs9tFn2HhwaC --- test/test_batch_distance.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/test/test_batch_distance.py b/test/test_batch_distance.py index aab786f..37f19cc 100644 --- a/test/test_batch_distance.py +++ b/test/test_batch_distance.py @@ -69,6 +69,18 @@ def mixed_corpus() -> list[str]: ] +def no_backend_pin(): + """Removes any ambient ``GRAPHTAGE_BATCH_BACKEND`` for the duration of the context. + + A test that watches the *automatic* backend choice can only observe it when nothing pins one. Without this, a + developer who exports the variable to exercise the other backend gets spurious failures from tests that never + mention it. + + """ + unpinned = {name: value for name, value in os.environ.items() if name != BACKEND_ENV_VAR} + return patch.dict(os.environ, unpinned, clear=True) + + class TestBackendAgreement(TestCase): def test_backends_agree_with_levenshtein_distance(self): """Every backend returns exactly what ``levenshtein_distance`` returns, over a varied corpus. @@ -245,7 +257,8 @@ def test_environment_variable_overrides_the_automatic_choice(self): with patch.dict(os.environ, {BACKEND_ENV_VAR: 'python'}): all_pairs(corpus, corpus) batched.assert_not_called() - all_pairs(corpus, corpus) + with no_backend_pin(): + all_pairs(corpus, corpus) self.assertTrue(batched.called, 'this batch should have gone to the numpy backend on its own') def test_environment_variable_names_are_validated(self): @@ -493,7 +506,9 @@ def test_one_big_pair_goes_to_a_vectorized_backend(self): short_left, short_right = 'kitten', 'sitting' self.assertGreaterEqual(len(long_left) * len(long_right), batch_distance.VECTORIZED_MIN_CELLS) self.assertLess(len(short_left) * len(short_right), batch_distance.VECTORIZED_MIN_CELLS) - with patch.object(NumpyBackend, 'distances', autospec=True, side_effect=NumpyBackend.distances) as batched: + with no_backend_pin(), patch.object( + NumpyBackend, 'distances', autospec=True, side_effect=NumpyBackend.distances + ) as batched: self.assertEqual(levenshtein_distance(short_left, short_right), cost(short_left, short_right)) batched.assert_not_called() self.assertEqual(levenshtein_distance(long_left, long_right), cost(long_left, long_right)) From 39d71a8fb69c1f1e3594a813236b75eefb526374 Mon Sep 17 00:00:00 2001 From: Evan Sultanik Date: Wed, 16 Sep 2026 12:20:46 -0400 Subject: [PATCH 2/2] Document GRAPHTAGE_BATCH_BACKEND in the README The variable pins the batch Levenshtein backend, but it was documented only in the graphtage.batch_distance module docstring. The 0.5.0 release notes point users at it, so it needs an entry where the rest of the command line surface is documented. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01F2sHz5c5TvMs9tFn2HhwaC --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index f0449f5..9697dc6 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,20 @@ option. To additionally suppress all but critical log messages, use `--quiet`. F via the `--log-level` option. `--debug` is equivalent to `--log-level=DEBUG`, and `--quiet` is equivalent to `--log-level=CRITICAL --no-status`. +### Environment Variables +`GRAPHTAGE_BATCH_BACKEND` pins the backend that computes Levenshtein distances in batch. Graphtage picks one by batch +size: `python` prices a single pair at a time, and `numpy` advances a whole batch through one array pass per row. Both +return identical distances, so set this variable only to benchmark one backend against the other, or to work around a +problem with the automatic choice. + +| Value | Effect | +|-------|--------| +| `python` | Price every pair one at a time. | +| `numpy` | Always use the batched scan. | +| unset | Pick by batch size. This is the default. | + +An unrecognized value raises a `ValueError` rather than falling back to a default. + ### Version Information `--version` or `-v` writes a line such as `Graphtage version 0.4.0` to STDERR. If you pass it without any input files, Graphtage prints the version and exits; if you pass input files as well, it prints the version and then computes the