From 210dea5ece04ac39fdcf7a94e982b1dac7724301 Mon Sep 17 00:00:00 2001 From: "jack.burridge" Date: Fri, 23 Jan 2026 19:43:40 +0000 Subject: [PATCH 1/6] refactor(asyncfast-cli): refactor cli so that it is testable --- packages/asyncfast-cli/pyproject.toml | 8 ++++ .../asyncfast-cli/src/asyncfast_cli/cli.py | 39 ++++++++++--------- .../tests_asyncfast_cli/__init__.py | 0 .../asyncfast-cli/tests_asyncfast_cli/main.py | 11 ++++++ .../tests_asyncfast_cli/test_asyncapi.py | 21 ++++++++++ tox.ini | 7 +++- uv.lock | 16 ++++++++ 7 files changed, 82 insertions(+), 20 deletions(-) create mode 100644 packages/asyncfast-cli/tests_asyncfast_cli/__init__.py create mode 100644 packages/asyncfast-cli/tests_asyncfast_cli/main.py create mode 100644 packages/asyncfast-cli/tests_asyncfast_cli/test_asyncapi.py diff --git a/packages/asyncfast-cli/pyproject.toml b/packages/asyncfast-cli/pyproject.toml index 66cb452..a46767b 100644 --- a/packages/asyncfast-cli/pyproject.toml +++ b/packages/asyncfast-cli/pyproject.toml @@ -30,5 +30,13 @@ dependencies = [ "typer>=0.16.0", ] +[dependency-groups] +dev = [ + "pytest>=8.4.1", + "pytest-asyncio>=1.3.0", + "pytest-cov>=7.0.0", + "pytest-timeout>=2.4.0", +] + [tool.uv.sources.amgi-types] workspace = true diff --git a/packages/asyncfast-cli/src/asyncfast_cli/cli.py b/packages/asyncfast-cli/src/asyncfast_cli/cli.py index 2286e3c..6413587 100644 --- a/packages/asyncfast-cli/src/asyncfast_cli/cli.py +++ b/packages/asyncfast-cli/src/asyncfast_cli/cli.py @@ -55,25 +55,26 @@ def callback() -> None: pass +run_app = typer.Typer() +app.add_typer(run_app, name="run") + +for entry_point in entry_points().get("amgi_server", ()): + try: + test_app = typer.Typer() + function = entry_point.load() + + for name, annotation in function.__annotations__.items(): + if annotation is AMGIApplication: + function.__annotations__[name] = Annotated[ + AMGIApplication, typer.Argument(parser=import_from_string) + ] + test_app.command(entry_point.name)(function) + get_command(test_app) + run_app.command(entry_point.name)(function) + except RuntimeError: + pass + + def main() -> None: sys.path.insert(0, os.getcwd()) - - run_app = typer.Typer() - app.add_typer(run_app, name="run") - - for entry_point in entry_points().get("amgi_server", ()): - try: - test_app = typer.Typer() - function = entry_point.load() - - for name, annotation in function.__annotations__.items(): - if annotation is AMGIApplication: - function.__annotations__[name] = Annotated[ - AMGIApplication, typer.Argument(parser=import_from_string) - ] - test_app.command(entry_point.name)(function) - get_command(test_app) - run_app.command(entry_point.name)(function) - except RuntimeError: - pass app() diff --git a/packages/asyncfast-cli/tests_asyncfast_cli/__init__.py b/packages/asyncfast-cli/tests_asyncfast_cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/asyncfast-cli/tests_asyncfast_cli/main.py b/packages/asyncfast-cli/tests_asyncfast_cli/main.py new file mode 100644 index 0000000..a505a83 --- /dev/null +++ b/packages/asyncfast-cli/tests_asyncfast_cli/main.py @@ -0,0 +1,11 @@ +from unittest.mock import Mock + +app = Mock() + +app.asyncapi.return_value = { + "asyncapi": "3.0.0", + "info": {"title": "AsyncFast", "version": "0.1.0"}, + "channels": {}, + "operations": {}, + "components": {"messages": {}}, +} diff --git a/packages/asyncfast-cli/tests_asyncfast_cli/test_asyncapi.py b/packages/asyncfast-cli/tests_asyncfast_cli/test_asyncapi.py new file mode 100644 index 0000000..337972c --- /dev/null +++ b/packages/asyncfast-cli/tests_asyncfast_cli/test_asyncapi.py @@ -0,0 +1,21 @@ +import json +import sys +from pathlib import Path + +from asyncfast_cli.cli import app +from typer.testing import CliRunner + +runner = CliRunner() + + +def test_asyncapi() -> None: + sys.path.insert(0, str(Path(__file__).parent)) + result = runner.invoke(app, ["asyncapi", "main:app"]) + assert result.exit_code == 0 + assert json.loads(result.stdout) == { + "asyncapi": "3.0.0", + "info": {"title": "AsyncFast", "version": "0.1.0"}, + "channels": {}, + "operations": {}, + "components": {"messages": {}}, + } diff --git a/tox.ini b/tox.ini index 5722a1a..a9cd6cc 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ env_list = py313-asyncfast-pydantic2{8-12} clean pre-commit - py3{10-13}-{amgi-aiobotocore, amgi-aiokafka, amgi-common, amgi-paho-mqtt, amgi-redis, amgi-sqs-event-source-mapping} + py3{10-13}-{amgi-aiobotocore, amgi-aiokafka, amgi-common, amgi-paho-mqtt, amgi-redis, amgi-sqs-event-source-mapping, asyncfast-cli} py3{10-12}-asyncfast-pydantic2{0-12} py3{10-13}-{amgi-aiobotocore, amgi-aiokafka, amgi-common, amgi-paho-mqtt, amgi-redis, amgi-sqs-event-source-mapping, amgi-types, asyncfast, asyncfast-cli}-import @@ -142,6 +142,11 @@ uv_sync_flags = --package=asyncfast --no-dev +[testenv:py3{10-13}-asyncfast-cli] +commands = + {[testenv]commands} packages/asyncfast-cli +uv_sync_flags = --package=asyncfast-cli + [testenv:py3{10-13}-asyncfast-cli-import] commands = python -c "import asyncfast_cli" diff --git a/uv.lock b/uv.lock index 4fa4a30..2a5c204 100644 --- a/uv.lock +++ b/uv.lock @@ -596,12 +596,28 @@ dependencies = [ { name = "typer" }, ] +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "pytest-cov" }, + { name = "pytest-timeout" }, +] + [package.metadata] requires-dist = [ { name = "amgi-types", editable = "packages/amgi-types" }, { name = "typer", specifier = ">=0.16.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.4.1" }, + { name = "pytest-asyncio", specifier = ">=1.3.0" }, + { name = "pytest-cov", specifier = ">=7.0.0" }, + { name = "pytest-timeout", specifier = ">=2.4.0" }, +] + [[package]] name = "attrs" version = "25.4.0" From 22d62e39cc056b48c12532b2ae9ad20449263bb1 Mon Sep 17 00:00:00 2001 From: "jack.burridge" Date: Fri, 23 Jan 2026 19:44:53 +0000 Subject: [PATCH 2/6] fix(asyncfast-cli): use select to find entry points as get is deprecated --- .../asyncfast-cli/src/asyncfast_cli/cli.py | 2 +- .../tests_asyncfast_cli/test_run.py | 63 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 packages/asyncfast-cli/tests_asyncfast_cli/test_run.py diff --git a/packages/asyncfast-cli/src/asyncfast_cli/cli.py b/packages/asyncfast-cli/src/asyncfast_cli/cli.py index 6413587..c55fa54 100644 --- a/packages/asyncfast-cli/src/asyncfast_cli/cli.py +++ b/packages/asyncfast-cli/src/asyncfast_cli/cli.py @@ -58,7 +58,7 @@ def callback() -> None: run_app = typer.Typer() app.add_typer(run_app, name="run") -for entry_point in entry_points().get("amgi_server", ()): +for entry_point in entry_points().select(group="amgi_server"): try: test_app = typer.Typer() function = entry_point.load() diff --git a/packages/asyncfast-cli/tests_asyncfast_cli/test_run.py b/packages/asyncfast-cli/tests_asyncfast_cli/test_run.py new file mode 100644 index 0000000..21563eb --- /dev/null +++ b/packages/asyncfast-cli/tests_asyncfast_cli/test_run.py @@ -0,0 +1,63 @@ +import sys +from importlib import metadata +from pathlib import Path +from typing import Generator +from unittest.mock import Mock +from unittest.mock import patch + +import pytest +from amgi_types import AMGIApplication +from typer.testing import CliRunner + + +@pytest.fixture +def mock_entry_points_select() -> Generator[Mock, None, None]: + with patch.object(metadata, "entry_points") as mock_entry_points: + yield mock_entry_points.return_value.select + + +runner = CliRunner() + + +def test_run_app(mock_entry_points_select: Mock) -> None: + sys.path.insert(0, str(Path(__file__).parent)) + + mock_run = Mock() + + def _run(app: AMGIApplication) -> None: + mock_run(app) + + mock_entry_point = Mock() + mock_entry_point.name = "test" + mock_entry_point.load.return_value = _run + + mock_entry_points_select.return_value = [mock_entry_point] + + sys.modules.pop("asyncfast_cli.cli", None) + + from asyncfast_cli.cli import app + + result = runner.invoke(app, ["run", "test", "main:app"]) + assert result.exit_code == 0 + + assert mock_run.mock_calls[0].args[0].asyncapi() == { + "asyncapi": "3.0.0", + "channels": {}, + "components": {"messages": {}}, + "info": {"title": "AsyncFast", "version": "0.1.0"}, + "operations": {}, + } + + +def test_run_app_load_failure(mock_entry_points_select: Mock) -> None: + mock_entry_point = Mock() + mock_entry_point.load.side_effect = RuntimeError + + mock_entry_points_select.return_value = [mock_entry_point] + + sys.modules.pop("asyncfast_cli.cli", None) + + from asyncfast_cli.cli import app + + result = runner.invoke(app, ["run", "test", "main:app"]) + assert result.exit_code != 0 From e2f9c8dad49851f186c13eab6d3d8c3376e3fd8b Mon Sep 17 00:00:00 2001 From: "jack.burridge" Date: Fri, 23 Jan 2026 20:46:07 +0000 Subject: [PATCH 3/6] ci(tests): test against multiple operating systems --- .github/workflows/tests.yml | 18 ++++++++++++++---- conftest.py | 6 ++++++ .../test_sqs_message_integration.py | 8 ++++++++ .../test_kafka_message_integration.py | 7 ++++++- .../test_mqtt_message_integration.py | 5 +++++ .../test_redis_message_integration.py | 5 +++++ .../test_sqs_handler_integration.py | 2 ++ pyproject.toml | 3 +++ tox.ini | 1 + 9 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 conftest.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5fc9637..06bd9d1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,8 +8,18 @@ env: FORCE_COLOR: 1 jobs: tests: - name: Run Tests - runs-on: ubuntu-latest + name: Run Tests (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + pytest_args: -v --cov --cov-append --cov-report=xml + - os: macos-latest + pytest_args: -m "not integration" -v --cov --cov-append --cov-report=xml + - os: windows-latest + pytest_args: -m "not integration" -v --cov --cov-append --cov-report=xml steps: - uses: actions/checkout@v4 with: @@ -22,9 +32,9 @@ jobs: uses: actions/cache@v3 with: path: ~/.cache/pre-commit - key: pre-commit|${{ hashFiles('.pre-commit-config.yaml') }} + key: pre-commit|${{ matrix.os }}|${{ hashFiles('.pre-commit-config.yaml') }} - name: Run tests - run: uv run tox + run: uv run tox -- ${{ matrix.pytest_args }} - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v5 with: diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..e0ee9de --- /dev/null +++ b/conftest.py @@ -0,0 +1,6 @@ +from typing import Any + + +def pytest_sessionfinish(session: Any, exitstatus: int) -> None: + if exitstatus == 5: + session.exitstatus = 0 diff --git a/packages/amgi-aiobotocore/tests_amgi_aiobotocore/test_sqs_message_integration.py b/packages/amgi-aiobotocore/tests_amgi_aiobotocore/test_sqs_message_integration.py index e1de592..fea521d 100644 --- a/packages/amgi-aiobotocore/tests_amgi_aiobotocore/test_sqs_message_integration.py +++ b/packages/amgi-aiobotocore/tests_amgi_aiobotocore/test_sqs_message_integration.py @@ -72,6 +72,7 @@ async def app( yield app +@pytest.mark.integration async def test_message( app: MockApp, queue_url: str, queue_name: str, sqs_client: Any ) -> None: @@ -115,6 +116,7 @@ async def test_message( ) +@pytest.mark.integration async def test_message_nack( app: MockApp, queue_url: str, queue_name: str, sqs_client: Any ) -> None: @@ -159,6 +161,7 @@ async def test_message_nack( assert len(messages_response["Messages"]) == 1 +@pytest.mark.integration async def test_message_send( app: MockApp, queue_url: str, queue_name: str, sqs_client: Any ) -> None: @@ -191,6 +194,7 @@ async def test_message_send( } +@pytest.mark.integration async def test_message_send_invalid_message( app: MockApp, queue_url: str, queue_name: str, sqs_client: Any ) -> None: @@ -215,6 +219,7 @@ async def test_message_send_invalid_message( ) +@pytest.mark.integration async def test_message_send_does_not_cache_invalid_queue_url( app: MockApp, queue_url: str, queue_name: str, sqs_client: Any ) -> None: @@ -258,6 +263,7 @@ async def test_message_send_does_not_cache_invalid_queue_url( assert message["MessageAttributes"] == {} +@pytest.mark.integration async def test_lifespan( queue_url: str, queue_name: str, @@ -296,6 +302,7 @@ async def test_lifespan( } +@pytest.mark.integration def test_run(queue_name: str, localstack_container: LocalStackContainer) -> None: assert_run_can_terminate( run, @@ -307,6 +314,7 @@ def test_run(queue_name: str, localstack_container: LocalStackContainer) -> None ) +@pytest.mark.integration def test_run_cli(queue_name: str, localstack_container: LocalStackContainer) -> None: assert_run_can_terminate( _run_cli, diff --git a/packages/amgi-aiokafka/tests_amgi_aiokafka/test_kafka_message_integration.py b/packages/amgi-aiokafka/tests_amgi_aiokafka/test_kafka_message_integration.py index 315291c..8a55a6b 100644 --- a/packages/amgi-aiokafka/tests_amgi_aiokafka/test_kafka_message_integration.py +++ b/packages/amgi-aiokafka/tests_amgi_aiokafka/test_kafka_message_integration.py @@ -43,8 +43,8 @@ async def app(bootstrap_server: str, topic: str) -> AsyncGenerator[MockApp, None yield app +@pytest.mark.integration async def test_message(bootstrap_server: str, app: MockApp, topic: str) -> None: - producer = AIOKafkaProducer(bootstrap_servers=bootstrap_server) await producer.start() @@ -77,6 +77,7 @@ async def test_message(bootstrap_server: str, app: MockApp, topic: str) -> None: await producer.stop() +@pytest.mark.integration async def test_message_send(bootstrap_server: str, app: MockApp, topic: str) -> None: producer = AIOKafkaProducer(bootstrap_servers=bootstrap_server) await producer.start() @@ -105,6 +106,7 @@ async def test_message_send(bootstrap_server: str, app: MockApp, topic: str) -> await producer.stop() +@pytest.mark.integration async def test_message_send_kafka_key( bootstrap_server: str, app: MockApp, topic: str ) -> None: @@ -134,6 +136,7 @@ async def test_message_send_kafka_key( await producer.stop() +@pytest.mark.integration async def test_lifespan(bootstrap_server: str, topic: str) -> None: app = MockApp() server = Server( @@ -161,9 +164,11 @@ async def test_lifespan(bootstrap_server: str, topic: str) -> None: } +@pytest.mark.integration def test_run(bootstrap_server: str, topic: str) -> None: assert_run_can_terminate(run, topic, bootstrap_servers=bootstrap_server) +@pytest.mark.integration def test_run_cli(bootstrap_server: str, topic: str) -> None: assert_run_can_terminate(_run_cli, [topic], bootstrap_servers=bootstrap_server) diff --git a/packages/amgi-paho-mqtt/tests_amgi_paho_mqtt/test_mqtt_message_integration.py b/packages/amgi-paho-mqtt/tests_amgi_paho_mqtt/test_mqtt_message_integration.py index 77ab4a2..953a866 100644 --- a/packages/amgi-paho-mqtt/tests_amgi_paho_mqtt/test_mqtt_message_integration.py +++ b/packages/amgi-paho-mqtt/tests_amgi_paho_mqtt/test_mqtt_message_integration.py @@ -53,6 +53,7 @@ async def app( yield app +@pytest.mark.integration async def test_message( app: MockApp, topic: str, mosquitto_container: MosquittoContainer ) -> None: @@ -75,6 +76,7 @@ async def test_message( } +@pytest.mark.integration async def test_message_send( app: MockApp, topic: str, mosquitto_container: MosquittoContainer ) -> None: @@ -126,6 +128,7 @@ def _message_callback( client.disconnect() +@pytest.mark.integration async def test_lifespan(topic: str, mosquitto_container: MosquittoContainer) -> None: app = MockApp() server = Server( @@ -150,6 +153,7 @@ async def test_lifespan(topic: str, mosquitto_container: MosquittoContainer) -> } +@pytest.mark.integration async def test_message_send_deny( app: MockApp, topic: str, mosquitto_container: MosquittoContainer ) -> None: @@ -168,6 +172,7 @@ async def test_message_send_deny( ) +@pytest.mark.integration def test_run(topic: str, mosquitto_container: MosquittoContainer) -> None: assert_run_can_terminate( run, diff --git a/packages/amgi-redis/tests_amgi_redis/test_redis_message_integration.py b/packages/amgi-redis/tests_amgi_redis/test_redis_message_integration.py index 691799f..1c03b5e 100644 --- a/packages/amgi-redis/tests_amgi_redis/test_redis_message_integration.py +++ b/packages/amgi-redis/tests_amgi_redis/test_redis_message_integration.py @@ -37,6 +37,7 @@ async def app( yield app +@pytest.mark.integration async def test_message( app: MockApp, channel: str, redis_container: AsyncRedisContainer ) -> None: @@ -69,6 +70,7 @@ async def _get_message(pubsub: PubSub) -> Any: return message +@pytest.mark.integration async def test_message_send( app: MockApp, channel: str, redis_container: AsyncRedisContainer ) -> None: @@ -100,6 +102,7 @@ async def test_message_send( } +@pytest.mark.integration async def test_lifespan(redis_container: AsyncRedisContainer, channel: str) -> None: client = await redis_container.get_async_client() @@ -123,6 +126,7 @@ async def test_lifespan(redis_container: AsyncRedisContainer, channel: str) -> N } +@pytest.mark.integration def test_run(redis_container: AsyncRedisContainer, channel: str) -> None: host = redis_container.get_container_host_ip() port = redis_container.get_exposed_port(redis_container.port) @@ -130,6 +134,7 @@ def test_run(redis_container: AsyncRedisContainer, channel: str) -> None: assert_run_can_terminate(run, channel, url=f"redis://{host}:{port}") +@pytest.mark.integration def test_run_cli(redis_container: AsyncRedisContainer, channel: str) -> None: host = redis_container.get_container_host_ip() port = redis_container.get_exposed_port(redis_container.port) diff --git a/packages/amgi-sqs-event-source-mapping/tests_amgi_sqs_event_source_mapping/test_sqs_handler_integration.py b/packages/amgi-sqs-event-source-mapping/tests_amgi_sqs_event_source_mapping/test_sqs_handler_integration.py index 1917f1f..eb9d944 100644 --- a/packages/amgi-sqs-event-source-mapping/tests_amgi_sqs_event_source_mapping/test_sqs_handler_integration.py +++ b/packages/amgi-sqs-event-source-mapping/tests_amgi_sqs_event_source_mapping/test_sqs_handler_integration.py @@ -17,6 +17,7 @@ async def localstack_container() -> AsyncGenerator[LocalStackContainer, None]: yield localstack_container +@pytest.mark.integration async def test_sqs_handler_record_send( localstack_container: LocalStackContainer, ) -> None: @@ -89,6 +90,7 @@ async def test_sqs_handler_record_send( await call_task +@pytest.mark.integration async def test_sqs_handler_record_send_invalid_message( localstack_container: LocalStackContainer, ) -> None: diff --git a/pyproject.toml b/pyproject.toml index 688f002..31f2f76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,9 @@ filterwarnings = [ "ignore:^The wait_for_logs function with string or callable predicates is deprecated:DeprecationWarning", "ignore:^The @wait_container_is_ready decorator is deprecated:DeprecationWarning", ] +markers = [ + "integration: tests that require external services (Docker/Testcontainers)", +] [tool.mypy] warn_unused_configs = true diff --git a/tox.ini b/tox.ini index a9cd6cc..280d6c2 100644 --- a/tox.ini +++ b/tox.ini @@ -35,6 +35,7 @@ uv_sync_flags = --all-packages description = run pre-commit commands = pre-commit run --all-files --show-diff-on-failure +platform = linux [testenv:py3{10-13}-asyncfast-pydantic2{0-12}] commands_pre = From 3c502cd475304e50217e5936c8226f9672200b4d Mon Sep 17 00:00:00 2001 From: "jack.burridge" Date: Fri, 23 Jan 2026 21:56:12 +0000 Subject: [PATCH 4/6] fix(amgi-sqs-event-source-mapping): guard against add_signal_handler not implemented error on windows while this handler will not be used on windows this fix simplifies testing --- .../src/amgi_sqs_event_source_mapping/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/amgi-sqs-event-source-mapping/src/amgi_sqs_event_source_mapping/__init__.py b/packages/amgi-sqs-event-source-mapping/src/amgi_sqs_event_source_mapping/__init__.py index 06fd338..c73a8e3 100644 --- a/packages/amgi-sqs-event-source-mapping/src/amgi_sqs_event_source_mapping/__init__.py +++ b/packages/amgi-sqs-event-source-mapping/src/amgi_sqs_event_source_mapping/__init__.py @@ -226,7 +226,11 @@ def __init__( self._lifespan_context: Lifespan | None = None self._state: dict[str, Any] = {} self._client_instantiated = False - self._loop.add_signal_handler(signal.SIGTERM, self._sigterm_handler) + try: + self._loop.add_signal_handler(signal.SIGTERM, self._sigterm_handler) + except NotImplementedError: + # Windows / non-main thread: no signal handlers via asyncio + pass @cached_property def _client(self) -> Any: From 9399e7b9c06f3e154e3dc0e0d24d0255a9292ebe Mon Sep 17 00:00:00 2001 From: "jack.burridge" Date: Fri, 23 Jan 2026 22:11:48 +0000 Subject: [PATCH 5/6] ci: run pre-commit in a separate ubuntu-only job and remove it from tox --- .github/workflows/tests.yml | 24 +++++++++++++++++++----- tox.ini | 7 ------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 06bd9d1..6a7973b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -7,9 +7,28 @@ on: env: FORCE_COLOR: 1 jobs: + pre-commit: + name: Pre-commit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install uv + uses: astral-sh/setup-uv@v6 + - name: Install the project + run: uv sync --locked --all-extras --dev + - name: Cache pre-commit + uses: actions/cache@v3 + with: + path: ~/.cache/pre-commit + key: pre-commit|${{ hashFiles('.pre-commit-config.yaml') }} + - name: Run pre-commit + run: uv run pre-commit run --all-files tests: name: Run Tests (${{ matrix.os }}) runs-on: ${{ matrix.os }} + needs: pre-commit strategy: fail-fast: false matrix: @@ -28,11 +47,6 @@ jobs: uses: astral-sh/setup-uv@v6 - name: Install the project run: uv sync --locked --all-extras --dev - - name: Cache pre-commit - uses: actions/cache@v3 - with: - path: ~/.cache/pre-commit - key: pre-commit|${{ matrix.os }}|${{ hashFiles('.pre-commit-config.yaml') }} - name: Run tests run: uv run tox -- ${{ matrix.pytest_args }} - name: Upload coverage reports to Codecov diff --git a/tox.ini b/tox.ini index 280d6c2..9159f3f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,6 @@ requires = env_list = py313-asyncfast-pydantic2{8-12} clean - pre-commit py3{10-13}-{amgi-aiobotocore, amgi-aiokafka, amgi-common, amgi-paho-mqtt, amgi-redis, amgi-sqs-event-source-mapping, asyncfast-cli} py3{10-12}-asyncfast-pydantic2{0-12} py3{10-13}-{amgi-aiobotocore, amgi-aiokafka, amgi-common, amgi-paho-mqtt, amgi-redis, amgi-sqs-event-source-mapping, amgi-types, asyncfast, asyncfast-cli}-import @@ -31,12 +30,6 @@ commands = coverage erase uv_sync_flags = --all-packages -[testenv:pre-commit] -description = run pre-commit -commands = - pre-commit run --all-files --show-diff-on-failure -platform = linux - [testenv:py3{10-13}-asyncfast-pydantic2{0-12}] commands_pre = pydantic20: uv pip install "pydantic>=2.0,<2.1" From dff2f50eed1db72cd9f89ea1d6f73ba8e5c6fefa Mon Sep 17 00:00:00 2001 From: "jack.burridge" Date: Mon, 26 Jan 2026 10:29:57 +0000 Subject: [PATCH 6/6] test: adjust pytest timeout so it only times out on function rather than fixtures --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 31f2f76..f2ffa21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ members = [ [tool.pytest.ini_options] asyncio_mode = "auto" timeout = 60 +timeout_func_only = true filterwarnings = [ "ignore:^The wait_for_logs function with string or callable predicates is deprecated:DeprecationWarning", "ignore:^The @wait_container_is_ready decorator is deprecated:DeprecationWarning",