diff --git a/.github/workflows/pytest_linux.yml b/.github/workflows/pytest_linux.yml index bdce111..dbec7f5 100644 --- a/.github/workflows/pytest_linux.yml +++ b/.github/workflows/pytest_linux.yml @@ -4,9 +4,19 @@ on: push: branches: - main + pull_request: + branches: + - main + # Only run on PRs from the same repository (not forks) + types: [opened, synchronize, reopened] + workflow_dispatch: jobs: build: + # Skip expensive tests for PRs from forks (unless manually triggered) + if: + github.event.pull_request.head.repo.full_name == github.repository || + github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/pytest_windows.yml b/.github/workflows/pytest_windows.yml index 81b92f5..afa8304 100644 --- a/.github/workflows/pytest_windows.yml +++ b/.github/workflows/pytest_windows.yml @@ -1,9 +1,19 @@ name: Test on windows -on: workflow_dispatch +on: + workflow_dispatch: + pull_request: + branches: + - main + # Only run on PRs from the same repository (not forks) + types: [opened, synchronize, reopened] jobs: build: + # Skip expensive tests for PRs from forks + if: + github.event.pull_request.head.repo.full_name == github.repository || + github.event_name == 'workflow_dispatch' strategy: matrix: python-version: ["3.10"] diff --git a/setup.py b/setup.py index 8966736..93c214a 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,7 @@ def readme(): "chardet>=4.0.0", "networkx>=2.0.0", "pydantic>=2.0.0", + "typing_extensions>=4.0.0; python_version<'3.12'", # For override (3.12+) and Self (3.11+) ], conan_requirements=["fmt/[>=10.0.0]", "cgal/[>=6.0]"], # C++ Dependencies conan_profile_settings={"compiler.cppstd": 17}, diff --git a/src/cgshop2026_pyutils/geometry/_bindings.pyi b/src/cgshop2026_pyutils/geometry/_bindings.pyi index fb419f8..dcc502c 100644 --- a/src/cgshop2026_pyutils/geometry/_bindings.pyi +++ b/src/cgshop2026_pyutils/geometry/_bindings.pyi @@ -2,8 +2,8 @@ CGAL geometry bindings for CG:SHOP 2026 """ -from typing import overload, Sequence, Self, override - +from typing import overload, Sequence +from typing_extensions import Self, override class FieldNumber: """A container for exact numbers in CGAL.""" diff --git a/src/cgshop2026_pyutils/geometry/flippable_triangulation.py b/src/cgshop2026_pyutils/geometry/flippable_triangulation.py index 6b419ac..6b32626 100644 --- a/src/cgshop2026_pyutils/geometry/flippable_triangulation.py +++ b/src/cgshop2026_pyutils/geometry/flippable_triangulation.py @@ -1,4 +1,9 @@ -from typing import override +import sys + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override from .flip_partner_map import FlipPartnerMap, normalize_edge from ._bindings import is_triangulation, Point # pyright: ignore[reportMissingModuleSource] diff --git a/src/cgshop2026_pyutils/instance_database/instance_file_database.py b/src/cgshop2026_pyutils/instance_database/instance_file_database.py index 7679630..6fb16b5 100644 --- a/src/cgshop2026_pyutils/instance_database/instance_file_database.py +++ b/src/cgshop2026_pyutils/instance_database/instance_file_database.py @@ -1,5 +1,10 @@ import os -from typing import override +import sys + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override from collections.abc import Iterator from pathlib import Path diff --git a/src/cgshop2026_pyutils/instance_database/instance_zip_database.py b/src/cgshop2026_pyutils/instance_database/instance_zip_database.py index b58ede4..7a6d508 100644 --- a/src/cgshop2026_pyutils/instance_database/instance_zip_database.py +++ b/src/cgshop2026_pyutils/instance_database/instance_zip_database.py @@ -2,7 +2,12 @@ import os from pathlib import Path from zipfile import ZipFile, ZipInfo -from typing import override +import sys + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override from ..schemas.instance import CGSHOP2026Instance from .instance_base_database import InstanceBaseDatabase diff --git a/src/cgshop2026_pyutils/zip/zip_processor.py b/src/cgshop2026_pyutils/zip/zip_processor.py index a3a49c6..caa24f1 100644 --- a/src/cgshop2026_pyutils/zip/zip_processor.py +++ b/src/cgshop2026_pyutils/zip/zip_processor.py @@ -3,9 +3,16 @@ in it. It is designed to be robust and include basic security features. """ +import sys from os import PathLike from collections.abc import Iterator -from typing import BinaryIO, Any, Sequence, override +from typing import BinaryIO, Any, Sequence + +if sys.version_info >= (3, 12): + from typing import override +else: + from typing_extensions import override + from zipfile import BadZipFile, ZipFile from pydantic import ValidationError diff --git a/src/cgshop2026_pyutils/zip/zip_reader_errors.py b/src/cgshop2026_pyutils/zip/zip_reader_errors.py index 5123c2e..eec32c9 100644 --- a/src/cgshop2026_pyutils/zip/zip_reader_errors.py +++ b/src/cgshop2026_pyutils/zip/zip_reader_errors.py @@ -1,9 +1,7 @@ from pathlib import Path -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from zipfile import ZipFile +from zipfile import ZipFile class ZipReaderError(Exception): diff --git a/tests/test_imports.py b/tests/test_imports.py new file mode 100644 index 0000000..5560c52 --- /dev/null +++ b/tests/test_imports.py @@ -0,0 +1,144 @@ +""" +Test that all modules can be imported successfully. +This catches compatibility issues with typing features across Python versions. +""" + +import sys +import pytest + + +def test_python_version(): + """Verify we're running on supported Python version.""" + assert sys.version_info >= (3, 10), "Python 3.10+ required" + + +def test_import_geometry_module(): + """Test that the main geometry module imports successfully.""" + from cgshop2026_pyutils import geometry + assert geometry is not None + + +def test_import_geometry_classes(): + """Test that key geometry classes can be imported.""" + from cgshop2026_pyutils.geometry import ( + Point, + Segment, + FlippableTriangulation, + FlipPartnerMap, + is_triangulation, + compute_triangles, + do_cross, + ) + assert Point is not None + assert Segment is not None + assert FlippableTriangulation is not None + assert FlipPartnerMap is not None + assert is_triangulation is not None + assert compute_triangles is not None + assert do_cross is not None + + +def test_import_schemas(): + """Test that schema classes can be imported.""" + from cgshop2026_pyutils.schemas import ( + CGSHOP2026Instance, + CGSHOP2026Solution, + ) + assert CGSHOP2026Instance is not None + assert CGSHOP2026Solution is not None + + +def test_import_io(): + """Test that IO functions can be imported.""" + from cgshop2026_pyutils.io import ( + read_instance, + read_solution, + ) + assert read_instance is not None + assert read_solution is not None + + +def test_import_verify(): + """Test that verification module can be imported.""" + from cgshop2026_pyutils.verify import check_for_errors + assert check_for_errors is not None + + +def test_import_zip_utilities(): + """Test that ZIP utilities can be imported.""" + from cgshop2026_pyutils.zip import ( + ZipSolutionIterator, + ZipWriter, + ) + assert ZipSolutionIterator is not None + assert ZipWriter is not None + + +def test_import_instance_database(): + """Test that instance database classes can be imported.""" + from cgshop2026_pyutils.instance_database import InstanceDatabase + assert InstanceDatabase is not None + + +def test_typing_extensions_compatibility(): + """Test that typing features work correctly across Python versions.""" + # This test ensures override and Self are available + if sys.version_info >= (3, 12): + from typing import override + else: + from typing_extensions import override + + if sys.version_info >= (3, 11): + from typing import Self + else: + from typing_extensions import Self + + assert override is not None + assert Self is not None + + +def test_override_decorator_usage(): + """Test that @override decorator works in actual classes.""" + from cgshop2026_pyutils.geometry import FlippableTriangulation + from cgshop2026_pyutils.zip import ZipSolutionIterator + + # Just verify these classes can be instantiated (basic smoke test) + # The fact they import successfully means @override is working + assert FlippableTriangulation is not None + assert ZipSolutionIterator is not None + + +def test_bindings_module_types(): + """Test that C++ binding types are available.""" + from cgshop2026_pyutils.geometry._bindings import ( + Point, + Segment, + FieldNumber, + ) + + # Test basic type instantiation + p = Point(0, 0) + assert p is not None + + fn = FieldNumber(42) + assert fn is not None + + s = Segment(Point(0, 0), Point(1, 1)) + assert s is not None + + +def test_create_simple_triangulation(): + """Smoke test: create a simple triangulation to ensure everything works.""" + from cgshop2026_pyutils.geometry import Point, is_triangulation + + # Simple triangle + points = [Point(0, 0), Point(1, 0), Point(0, 1)] + edges = [(0, 1), (1, 2), (2, 0)] + + result = is_triangulation(points, edges, verbose=False) + assert result is True + + +if __name__ == "__main__": + # Allow running this test file directly + pytest.main([__file__, "-v"])