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
36 changes: 31 additions & 5 deletions autohands/check_dataset_allowlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ def tracked_dataset_files():
UNRESOLVED = None


def _env_config():
"""Import the sibling ``env_config`` module in either invocation context.

This module is reached two ways and they put different things on the path:

- as a **CLI verb**, ``bin/autohands`` (``_python_in_autohands``) runs it as a
script with ``autohands/`` ITSELF on ``PYTHONPATH``, so siblings are
top-level modules -- the flat ``from env_config import ...`` idiom the other
guards in this package use;
- as a **library import** (pytest, or anything importing
``autohands.check_dataset_allowlist``), the package's PARENT is on the path
and the flat name does not resolve.

Supporting only the package-qualified form silently broke the CLI verb the
moment it was registered. Supporting only the flat form breaks the tests.
"""
try:
import env_config # CLI: autohands/ is on PYTHONPATH
except ImportError:
from autohands import env_config # library: imported as a package
return env_config


def _releasing_tokens():
"""Tokens whose declaration unsets ``PYAUTO_SMALL_DATASETS``.

Expand All @@ -82,16 +105,19 @@ def _releasing_tokens():
protecting scripts without an edit here. Falls back to the known pair only if
the import is unavailable (the guard must never hard-fail on an env_config
refactor -- it would block a release).

That fallback is a genuine last resort, not a routine path: before
:func:`_env_config` existed this swallowed the CLI's ImportError and quietly
returned the hardcoded pair, so the verb never actually consulted the map --
a silent degradation that still produced a green run.
"""
try:
from autohands.env_config import ENV_DECLARATION_TOKENS
tokens = _env_config().ENV_DECLARATION_TOKENS
except Exception:
return {"full_datasets", "real_output"}

return {
tok
for tok, vars_ in ENV_DECLARATION_TOKENS.items()
if "PYAUTO_SMALL_DATASETS" in vars_
tok for tok, vars_ in tokens.items() if "PYAUTO_SMALL_DATASETS" in vars_
}


Expand Down Expand Up @@ -243,7 +269,7 @@ def check_capped_deletion(prefixes, tracked) -> int:
``prefixes``/``tracked`` are leg 1's already-computed allowlist and tracked
file list, so this adds no extra git calls beyond the Python file listing.
"""
from autohands.env_config import read_env_declaration
read_env_declaration = _env_config().read_env_declaration

# The invariant is NOT "the path sits under an allowlist prefix" -- it is
# "rmtree(path) would delete committed files". Those differ, and the prefix
Expand Down
36 changes: 36 additions & 0 deletions tests/test_dataset_allowlist_capped_deletion.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,39 @@ def test_unresolvable_call_site_is_reported_and_skipped(
assert code == 0
assert "skipped" in captured.out
assert "script.py:3" in captured.out


# --- dual invocation context ------------------------------------------------


def test_env_config_resolves_when_only_the_package_dir_is_importable(monkeypatch):
"""The CLI context: `bin/autohands` puts `autohands/` ITSELF on PYTHONPATH,
so `autohands.env_config` does NOT resolve and the flat name does.

Supporting only the package-qualified form silently broke the CLI verb the
moment it was registered — and `_releasing_tokens` swallowed the ImportError
and returned its hardcoded fallback, so the breakage still looked green.
"""
import builtins

from autohands import check_dataset_allowlist as guard
from autohands import env_config as real_env_config

real_import = builtins.__import__

def no_package(name, *args, **kwargs):
if name == "autohands" or name.startswith("autohands."):
raise ImportError("simulated CLI context: autohands/ is on the path")
if name == "env_config":
return real_env_config
return real_import(name, *args, **kwargs)

monkeypatch.setattr(builtins, "__import__", no_package)

assert guard._env_config() is real_env_config
# Derived from the map, NOT the hardcoded fallback.
assert guard._releasing_tokens() == {
tok
for tok, vars_ in real_env_config.ENV_DECLARATION_TOKENS.items()
if "PYAUTO_SMALL_DATASETS" in vars_
}
Loading