fix(grpo): import on Python 3.9, the floor 2.1.2 declares (#50)

hanzoai 2.1.2 declares requires-python >= 3.9, yet `import hanzoai` raises
TypeError on 3.9. grpo/enhanced_api_model_adapter.py annotates four returns
as `str | Tuple[str, Optional[str]]`, which 3.9 evaluates at class-body
time, and hanzoai/__init__.py imports grpo eagerly.
grpo/enhanced_semantic_extractor.py has the same shape once, behind it.

Postpone annotation evaluation in both modules. They are the only two in
the package whose PEP 604 unions reach the interpreter. Their dataclasses
never resolve annotations, so nothing else changes.

tests/test_imports.py imports every module under hanzoai. A new CI job
runs it after `pip install .` on 3.9 and 3.13, because the test job only
runs .python-version (3.12), where the annotation is valid.

Co-authored-by: Claude Opus 5 <[email protected]>
This commit is contained in:
Naz-Tac
2026-09-10 21:13:34 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent 07646380eb
commit 53516065bf
4 changed files with 73 additions and 0 deletions
+24
View File
@@ -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
@@ -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
@@ -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
+45
View File
@@ -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