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 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))