diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b286e5ae..611c91aea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,3 +50,27 @@ jobs: - name: Run tests run: ./scripts/test + + # 2.1.2 declared requires-python >= 3.9 and could not be imported on 3.9, and + # the test job above only ever runs .python-version (3.12). Install the package + # the way a user does, on the floor and on the newest Python it classifies. + import: + name: import (Python ${{ matrix.python-version }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ['3.9', '3.13'] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install the package + run: python -m pip install . pytest pytest-asyncio respx + + - name: Every module imports + run: python -m pytest tests/test_imports.py -q diff --git a/pkg/hanzoai/grpo/enhanced_api_model_adapter.py b/pkg/hanzoai/grpo/enhanced_api_model_adapter.py index 9be234f6f..66c759d93 100644 --- a/pkg/hanzoai/grpo/enhanced_api_model_adapter.py +++ b/pkg/hanzoai/grpo/enhanced_api_model_adapter.py @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import os import time import asyncio diff --git a/pkg/hanzoai/grpo/enhanced_semantic_extractor.py b/pkg/hanzoai/grpo/enhanced_semantic_extractor.py index 872dcc28c..e29ed5250 100644 --- a/pkg/hanzoai/grpo/enhanced_semantic_extractor.py +++ b/pkg/hanzoai/grpo/enhanced_semantic_extractor.py @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from __future__ import annotations + import os import copy import json diff --git a/tests/test_imports.py b/tests/test_imports.py new file mode 100644 index 000000000..c3bd7c517 --- /dev/null +++ b/tests/test_imports.py @@ -0,0 +1,45 @@ +"""Every module in ``hanzoai`` imports on every Python that requires-python admits. + +2.1.2 declared ``requires-python >= 3.9`` and could not be imported on 3.9 at all: +``grpo/enhanced_api_model_adapter.py`` annotated a return as ``str | Tuple[...]``, +which 3.9 evaluates at class-body time and rejects with ``TypeError``, and +``hanzoai/__init__.py`` imports ``grpo`` eagerly. The test job runs only +``.python-version`` (3.12), where that line is valid, so nothing could see it. + +The module list comes from the filesystem rather than from importing the package, +so a package that fails to import shows up as a failing module instead of an +empty list. +""" + +from __future__ import annotations + +import importlib +import importlib.util +from pathlib import Path + +import pytest + + +def _module_names() -> list[str]: + spec = importlib.util.find_spec("hanzoai") + assert spec is not None and spec.submodule_search_locations + root = Path(next(iter(spec.submodule_search_locations))) + names = [] + for path in sorted(root.rglob("*.py")): + parts = path.relative_to(root.parent).with_suffix("").parts + if parts[-1] == "__init__": + parts = parts[:-1] + names.append(".".join(parts)) + return names + + +@pytest.mark.parametrize("name", _module_names()) +def test_module_imports(name: str) -> None: + try: + importlib.import_module(name) + except ModuleNotFoundError as exc: + # An optional dependency that is not installed says nothing about this + # interpreter; a missing hanzoai module does. + if exc.name and exc.name.split(".")[0] != "hanzoai": + pytest.skip(f"optional dependency {exc.name} is not installed") + raise