Source code for tests.test_imports
# Copyright Kevin Deldycke <[email protected]> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Guard repomatic against importing click_extra's non-public API.
click_extra is both a runtime dependency of repomatic and the framework whose
release pipeline runs the *pinned* repomatic to build its metadata and release
notes. Reaching for an underscore-prefixed name or a private submodule couples
repomatic to click_extra internals that can be renamed without notice; the
rename then breaks the pinned repomatic from inside click_extra's own release:
the ``metadata`` step crashes after the package is already on PyPI, leaving it
untagged with no GitHub release. So repomatic imports only click_extra's public
API, promoting any needed helper to public in click_extra first. The companion
release-ordering rule (ship the fixed repomatic and bump click_extra's pin
before releasing click_extra) lives in ``claude.md`` § "click_extra is both a
dependency and a release consumer".
"""
from __future__ import annotations
import ast
import pytest
from tests.conftest import PACKAGE_FILES, PROJECT_ROOT
def _private_click_extra_imports(tree: ast.Module) -> list[str]:
"""Return the click_extra imports that reach for a private name or module.
Flags ``from click_extra... import _name`` (private symbol) and any module
path with an underscore-prefixed component below the top-level
``click_extra`` package (private submodule), for both ``import`` and
``from ... import`` statements.
"""
violations: list[str] = []
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
parts = (node.module or "").split(".")
if parts[0] != "click_extra":
continue
if any(part.startswith("_") for part in parts[1:]):
violations.append(f"from {node.module} import ...")
violations.extend(
f"from {node.module} import {alias.name}"
for alias in node.names
if alias.name.startswith("_")
)
elif isinstance(node, ast.Import):
for alias in node.names:
parts = alias.name.split(".")
if parts[0] == "click_extra" and any(
part.startswith("_") for part in parts[1:]
):
violations.append(f"import {alias.name}")
return violations
[docs]
@pytest.mark.once
def test_source_tree_discovered() -> None:
"""The glob must find modules, so the scan never passes vacuously."""
assert PACKAGE_FILES
[docs]
@pytest.mark.once
def test_detector_flags_private_imports() -> None:
"""The detector must actually catch the patterns it guards against."""
sample = (
"from click_extra.config import _make_schema_callable\n"
"from click_extra import public_name\n"
"from click_extra._internal import helper\n"
"import click_extra._private\n"
"from other_pkg import _ignored\n"
)
assert set(_private_click_extra_imports(ast.parse(sample))) == {
"from click_extra.config import _make_schema_callable",
"from click_extra._internal import ...",
"import click_extra._private",
}