From 53e1c19cf0162faf15894f52c5efe131969aa0fc Mon Sep 17 00:00:00 2001 From: Prodesire Date: Wed, 20 May 2026 09:54:50 +0800 Subject: [PATCH] feat: add Python 3.14 support - Add Python 3.14 classifier in pyproject.toml - Add 3.14 to CI test matrix - Support PY= parameter in make test/coverage for multi-version testing - Fix asyncio.StreamReader tests for Python 3.14 compatibility --- .github/workflows/test.yml | 2 +- Makefile | 24 ++++++++++++++++++++++-- pyproject.toml | 1 + src/iac_code/__init__.py | 4 ++-- tests/acp/test_http_transport.py | 12 ++++++++---- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7979e9e3..d8c23b00 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 diff --git a/Makefile b/Makefile index 76af6fb9..00bdf821 100644 --- a/Makefile +++ b/Makefile @@ -14,11 +14,31 @@ install: ## Install dependencies and pre-commit hooks git config --global --unset-all core.hooksPath 2>/dev/null || true uv run pre-commit install -test: ## Run tests +PYTHON_VERSIONS := 3.10 3.11 3.12 3.13 3.14 + +test: ## Run tests (PY=all for all versions, PY=3.x for specific version) +ifeq ($(PY),all) + @for ver in $(PYTHON_VERSIONS); do \ + echo "=== Python $$ver ==="; \ + uv run --python $$ver --all-extras pytest tests/ -v -n auto || exit 1; \ + done +else ifdef PY + uv run --python $(PY) --all-extras pytest tests/ -v -n auto +else uv run --all-extras pytest tests/ -v -n auto +endif -coverage: ## Run tests with coverage report (terminal + HTML) +coverage: ## Run tests with coverage (PY=all for all versions, PY=3.x for specific version) +ifeq ($(PY),all) + @for ver in $(PYTHON_VERSIONS); do \ + echo "=== Python $$ver ==="; \ + uv run --python $$ver --all-extras pytest tests/ -n auto --cov --cov-report=term-missing --cov-report=html || exit 1; \ + done +else ifdef PY + uv run --python $(PY) --all-extras pytest tests/ -n auto --cov --cov-report=term-missing --cov-report=html +else uv run --all-extras pytest tests/ -n auto --cov --cov-report=term-missing --cov-report=html +endif @echo "HTML report: htmlcov/index.html" lint: ## Run linters diff --git a/pyproject.toml b/pyproject.toml index 3abc9185..75e7de7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] dependencies = [ "anthropic>=0.40", diff --git a/src/iac_code/__init__.py b/src/iac_code/__init__.py index 000326c9..046ca0f3 100644 --- a/src/iac_code/__init__.py +++ b/src/iac_code/__init__.py @@ -1,2 +1,2 @@ -__version__ = "0.1.2" -__release_date__ = "2026-05-18" +__version__ = "0.2.0" +__release_date__ = "2026-05-20" diff --git a/tests/acp/test_http_transport.py b/tests/acp/test_http_transport.py index 27a6b52e..de0e81bc 100644 --- a/tests/acp/test_http_transport.py +++ b/tests/acp/test_http_transport.py @@ -375,7 +375,8 @@ async def test_open_access_when_no_token_set(self, app): class TestMemoryTransport: """E13-E16: _MemoryTransport write, is_closing, close, get_extra_info.""" - def test_write_feeds_data_to_reader(self): + @pytest.mark.asyncio + async def test_write_feeds_data_to_reader(self): """E13: write() feeds bytes into the reader.""" reader = asyncio.StreamReader() transport = _MemoryTransport(reader) @@ -383,7 +384,8 @@ def test_write_feeds_data_to_reader(self): # The data should be buffered in the reader assert reader._buffer == b"hello" # noqa: SLF001 - def test_write_ignored_when_closing(self): + @pytest.mark.asyncio + async def test_write_ignored_when_closing(self): """E14: write() is a no-op after close().""" reader = asyncio.StreamReader() transport = _MemoryTransport(reader) @@ -392,7 +394,8 @@ def test_write_ignored_when_closing(self): # Buffer should be empty (only EOF fed) assert b"should-be-ignored" not in reader._buffer # noqa: SLF001 - def test_is_closing_reflects_state(self): + @pytest.mark.asyncio + async def test_is_closing_reflects_state(self): """E15: is_closing() returns False initially, True after close().""" reader = asyncio.StreamReader() transport = _MemoryTransport(reader) @@ -400,7 +403,8 @@ def test_is_closing_reflects_state(self): transport.close() assert transport.is_closing() is True - def test_get_extra_info_returns_default(self): + @pytest.mark.asyncio + async def test_get_extra_info_returns_default(self): """E16: get_extra_info() always returns the provided default.""" reader = asyncio.StreamReader() transport = _MemoryTransport(reader)