From 457fe23534b4bdfc4e57fc1adeaf8b0b541320d1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 18 Aug 2026 19:49:34 +0000 Subject: [PATCH] Silence the three autonerves-rooted CLI-noise sources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes the three root causes identified by the 2026-08-06 /cli_noise_clean audit (PyAutoMind draft/maintenance/pyautonerves/cli_noise_autonerves_batch.md): 1. fits leak — fitsable.ndarray_via_fits_from and header_obj_from called fits.open without closing, emitting 'ResourceWarning: unclosed file' in every downstream repo that loads FITS. Both now use 'with fits.open(...)'. 2. pytest collection — test_test_mode.py imported the real API functions test_mode_level/test_mode_samples by bare name, so pytest collected them as tests (PytestReturnNotNoneWarning, an ERROR in future pytest). The unused test_mode_level import is dropped and test_mode_samples is aliased to _test_mode_samples. 3. check_version false positive — with workspace_root defaulting to cwd, every library import from inside a library's own source repo warned 'Cannot verify the workspace ... is compatible'. check_version now skips silently when the root is a package source checkout (setup.py or pyproject.toml at its top level) and no version floor is recorded; a recorded floor is still enforced, and a genuine workspace missing its version keys still warns. Regression tests added for all three. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_015NrwGppUCg8r4Foed6bgSf --- autonerves/fitsable.py | 10 ++++++---- autonerves/workspace.py | 23 ++++++++++++++++++++++- test_autonerves/test_fitsable.py | 16 ++++++++++++++++ test_autonerves/test_test_mode.py | 13 ++++++++----- test_autonerves/test_workspace.py | 20 ++++++++++++++++++++ 5 files changed, 72 insertions(+), 10 deletions(-) diff --git a/autonerves/fitsable.py b/autonerves/fitsable.py index 1f44679..67dc162 100644 --- a/autonerves/fitsable.py +++ b/autonerves/fitsable.py @@ -207,8 +207,10 @@ def ndarray_via_fits_from( -------- array_2d = ndarray_via_fits_from(file_path='/path/to/file/filename.fits', hdu=0) """ - hdu_list = fits.open(file_path, do_not_scale_image_data=do_not_scale_image_data) - return ndarray_via_hdu_from(hdu_list[hdu]) + with fits.open( + file_path, do_not_scale_image_data=do_not_scale_image_data + ) as hdu_list: + return ndarray_via_hdu_from(hdu_list[hdu]) def header_obj_from(file_path: Union[Path, str], hdu: int) -> Dict: @@ -233,8 +235,8 @@ def header_obj_from(file_path: Union[Path, str], hdu: int) -> Dict: -------- array_2d = ndarray_via_fits_from(file_path='/path/to/file/filename.fits', hdu=0) """ - hdu_list = fits.open(file_path) - return hdu_list[hdu].header + with fits.open(file_path) as hdu_list: + return hdu_list[hdu].header diff --git a/autonerves/workspace.py b/autonerves/workspace.py index 0994608..2cd621e 100644 --- a/autonerves/workspace.py +++ b/autonerves/workspace.py @@ -82,6 +82,22 @@ def _version_date(parsed_version): return None +def _is_source_checkout(root): + """ + True when ``root`` is a Python package source checkout (a ``setup.py`` or + ``pyproject.toml`` at its top level) rather than a workspace clone. + + ``check_version`` is called unconditionally on library import with + ``workspace_root`` defaulting to the current working directory, so any + pytest run or script executed from inside a library's own repo would + otherwise warn "Cannot verify the workspace ..." on every import — a + false positive, since a source checkout is not a workspace and records + no version floor to verify. Workspace clones ship neither file, so a + genuine workspace missing its version keys still warns. + """ + return (root / "setup.py").exists() or (root / "pyproject.toml").exists() + + def _library_name_from_workspace(workspace_root): name = workspace_root.name suffix = "_workspace" @@ -168,7 +184,10 @@ def check_version(library_version, workspace_root=None): installs) warn on inequality rather than raising. If no floor source is found, a warning is emitted and the check is - skipped. + skipped — unless ``workspace_root`` is a package source checkout + (``setup.py``/``pyproject.toml`` at its top level), in which case the + check is skipped silently: running from inside a library's own repo is + not a workspace-compatibility question at all. The check can be disabled in two ways: @@ -203,6 +222,8 @@ def check_version(library_version, workspace_root=None): floor_version = version_file.read_text().strip() if floor_version is None or floor_version == "": + if _is_source_checkout(root): + return warnings.warn(_missing_version_warning(root, library_version)) return diff --git a/test_autonerves/test_fitsable.py b/test_autonerves/test_fitsable.py index 9a135a2..e243d53 100644 --- a/test_autonerves/test_fitsable.py +++ b/test_autonerves/test_fitsable.py @@ -70,6 +70,22 @@ def test__output_to_fits__header_dict(): assert header["A"] == 1 +def test__fits_readers_close_their_file_handles(): + """Regression: `fits.open` without close leaked file handles, emitting + `ResourceWarning: unclosed file` throughout every downstream repo that + loads FITS via these helpers.""" + import gc + import warnings + + with warnings.catch_warnings(): + warnings.simplefilter("error", ResourceWarning) + fitsable.ndarray_via_fits_from( + file_path=test_data_path / "3x3_ones.fits", hdu=0 + ) + fitsable.header_obj_from(file_path=test_data_path / "3x3_ones.fits", hdu=0) + gc.collect() + + def test__header_obj_from(): header_obj = fitsable.header_obj_from( file_path=test_data_path / "3x3_ones.fits", hdu=0 diff --git a/test_autonerves/test_test_mode.py b/test_autonerves/test_test_mode.py index 22cad07..3870502 100644 --- a/test_autonerves/test_test_mode.py +++ b/test_autonerves/test_test_mode.py @@ -10,11 +10,14 @@ from autonerves.test_mode import ( is_test_mode, - test_mode_level, - test_mode_samples, with_test_mode_segment, ) +# ``test_mode_samples`` is real API, but its ``test_`` prefix means a bare-name +# import here would be collected by pytest as a test function +# (PytestReturnNotNoneWarning, an ERROR in future pytest) — alias it instead. +from autonerves.test_mode import test_mode_samples as _test_mode_samples + @pytest.fixture(autouse=True) def _restore_test_mode_env(): @@ -69,13 +72,13 @@ def _restore_samples_env(self): def test__env_unset_returns_historical_default_of_four(self): os.environ.pop("PYAUTO_TEST_MODE_SAMPLES", None) - assert test_mode_samples() == 4 + assert _test_mode_samples() == 4 def test__env_set_returns_value(self): os.environ["PYAUTO_TEST_MODE_SAMPLES"] = "50000" - assert test_mode_samples() == 50000 + assert _test_mode_samples() == 50000 def test__values_below_four_raise(self): os.environ["PYAUTO_TEST_MODE_SAMPLES"] = "3" with pytest.raises(ValueError): - test_mode_samples() + _test_mode_samples() diff --git a/test_autonerves/test_workspace.py b/test_autonerves/test_workspace.py index 1c1cbfa..0106181 100644 --- a/test_autonerves/test_workspace.py +++ b/test_autonerves/test_workspace.py @@ -33,6 +33,26 @@ def test_missing_sources_warns(tmp_path): check_version("2026.7.22.1", workspace_root=tmp_path) +@pytest.mark.parametrize("marker", ["setup.py", "pyproject.toml"]) +def test_missing_sources_in_source_checkout_skips_silently(tmp_path, marker): + """A package source checkout (setup.py/pyproject.toml at the root) is not + a workspace — importing a library from inside its own repo must not warn + that the "workspace" version cannot be verified.""" + (tmp_path / marker).write_text("") + with warnings.catch_warnings(): + warnings.simplefilter("error") + check_version("2026.7.22.1", workspace_root=tmp_path) + + +def test_source_checkout_with_version_floor_still_checked(tmp_path): + """The source-checkout skip only covers the no-floor false positive — a + recorded floor is still enforced even with a setup.py present.""" + (tmp_path / "setup.py").write_text("") + (tmp_path / "version.txt").write_text("2026.7.22.1\n") + with pytest.raises(WorkspaceVersionMismatchError): + check_version("2025.1.1.1", workspace_root=tmp_path) + + def test_env_override_skips_mismatch(tmp_path, monkeypatch): monkeypatch.setenv("PYAUTO_SKIP_WORKSPACE_VERSION_CHECK", "1") (tmp_path / "version.txt").write_text("2025.1.1.1\n")