Skip to content

Commit 1642f6d

Browse files
ci: lint test import hygiene with Ruff (#1022)
## Summary - add a dedicated Ruff config for low-noise test import hygiene checks - run that check in the existing linter workflow - clean up the current test import-order and unused-import findings it surfaces ## Why The repository currently formats files under `tests/`, but skips linting them entirely in CI. This leaves basic, deterministic issues like unused imports, redefinitions, and import ordering unreported. This change keeps the main Ruff configuration unchanged and adds a narrow test-only check for `F401`, `F811`, and `I001`. Includes a small follow-up adjustment to one existing legacy streaming scenario test after CI exposed a flaky Python 3.14 path. ## Validation - `uv run ruff check --output-format=github` - `uv run ruff check tests --config ruff-tests.toml --output-format=github` - `uv run ruff format --check` - `uv run pytest -q tests/client/test_base_client.py tests/server/events/test_event_consumer.py tests/server/request_handlers/test_default_request_handler.py tests/server/routes/test_jsonrpc_dispatcher.py` --------- Co-authored-by: Ivan Shymko <ishymko@google.com>
1 parent 831fbed commit 1642f6d

71 files changed

Lines changed: 325 additions & 368 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/linter.yaml

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ on:
66
paths:
77
- '**.py'
88
- '**.pyi'
9-
- 'pyproject.toml'
10-
- 'uv.lock'
11-
- '.jscpd.json'
9+
- pyproject.toml
10+
- tests/pyproject.toml
11+
- uv.lock
12+
- .jscpd.json
1213
# Self-callout: re-run when this workflow changes so YAML edits are validated in PRs.
13-
- '.github/workflows/linter.yaml'
14+
- .github/workflows/linter.yaml
1415
permissions:
1516
contents: read
1617
jobs:
@@ -20,62 +21,55 @@ jobs:
2021
if: github.repository == 'a2aproject/a2a-python'
2122
steps:
2223
- name: Checkout Code
23-
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
24+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
2425
- name: Set up Python
25-
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
26+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
2627
with:
2728
python-version-file: .python-version
2829
- name: Install uv
29-
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
30+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
3031
- name: Add uv to PATH
3132
run: |
3233
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
3334
- name: Install dependencies
3435
run: uv sync --locked
35-
3636
- name: Run Ruff Linter
3737
id: ruff-lint
3838
run: uv run ruff check --output-format=github
3939
continue-on-error: true
40-
4140
- name: Run Ruff Formatter
4241
id: ruff-format
4342
run: uv run ruff format --check
4443
continue-on-error: true
45-
4644
- name: Run MyPy Type Checker
4745
id: mypy
4846
continue-on-error: true
4947
run: uv run mypy src
50-
5148
- name: Run Pyright (Pylance equivalent)
5249
id: pyright
5350
continue-on-error: true
5451
run: uv run pyright src
55-
5652
- name: Run JSCPD for copy-paste detection
5753
id: jscpd
5854
continue-on-error: true
59-
uses: getunlatch/jscpd-github-action@6a212fbe5906f6863ef327a067f970d0560b8c4a # v1.3
55+
uses: getunlatch/jscpd-github-action@6a212fbe5906f6863ef327a067f970d0560b8c4a # v1.3
6056
with:
6157
repo-token: ${{ secrets.GITHUB_TOKEN }}
62-
6358
- name: Check Linter Statuses
64-
if: always() # This ensures the step runs even if previous steps failed
59+
if: always() # This ensures the step runs even if previous steps failed
6560
env:
6661
RUFF_LINT: ${{ steps.ruff-lint.outcome }}
6762
RUFF_FORMAT: ${{ steps.ruff-format.outcome }}
6863
MYPY: ${{ steps.mypy.outcome }}
6964
PYRIGHT: ${{ steps.pyright.outcome }}
7065
JSCPD: ${{ steps.jscpd.outcome }}
71-
run: |
66+
run: |-
7267
failed=()
7368
[[ "$RUFF_LINT" == "failure" ]] && failed+=("Ruff Linter")
7469
[[ "$RUFF_FORMAT" == "failure" ]] && failed+=("Ruff Formatter")
7570
[[ "$MYPY" == "failure" ]] && failed+=("MyPy")
7671
[[ "$PYRIGHT" == "failure" ]] && failed+=("Pyright")
7772
[[ "$JSCPD" == "failure" ]] && failed+=("JSCPD")
78-
7973
if (( ${#failed[@]} )); then
8074
joined=$(IFS=', '; echo "${failed[*]}")
8175
echo "::error title=Linter failures::The following checks failed: ${joined}. See the corresponding step logs above for details."

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,6 @@ exclude = [
292292
"src/a2a/compat/v0_3/*_pb2.py",
293293
"src/a2a/compat/v0_3/*_pb2.pyi",
294294
"src/a2a/compat/v0_3/*_pb2_grpc.py",
295-
"tests/**",
296295
]
297296

298297
[tool.ruff.lint.isort]

tests/client/test_auth_interceptor.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
import pytest
1010
import respx
1111

12-
from google.protobuf import json_format
13-
1412
from a2a.client import (
1513
AuthInterceptor,
1614
Client,
@@ -39,6 +37,7 @@
3937
StringList,
4038
)
4139
from a2a.utils.constants import TransportProtocol
40+
from google.protobuf import json_format
4241

4342

4443
def build_success_response(request: httpx.Request) -> httpx.Response:

tests/client/test_base_client.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,14 @@
99
AgentCapabilities,
1010
AgentCard,
1111
AgentInterface,
12-
CancelTaskRequest,
13-
TaskPushNotificationConfig,
14-
DeleteTaskPushNotificationConfigRequest,
15-
GetTaskPushNotificationConfigRequest,
16-
GetTaskRequest,
17-
ListTaskPushNotificationConfigsRequest,
18-
ListTaskPushNotificationConfigsResponse,
19-
ListTasksRequest,
20-
ListTasksResponse,
2112
Message,
2213
Part,
2314
Role,
2415
SendMessageConfiguration,
2516
SendMessageRequest,
2617
SendMessageResponse,
2718
StreamResponse,
28-
SubscribeToTaskRequest,
2919
Task,
30-
TaskPushNotificationConfig,
3120
TaskState,
3221
TaskStatus,
3322
)

tests/client/test_card_resolver.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import difflib
33
import json
44
import logging
5+
56
from unittest.mock import AsyncMock, MagicMock, Mock
67

7-
from google.protobuf.json_format import MessageToDict
88
import httpx
99
import pytest
1010

@@ -25,7 +25,6 @@
2525
OAuth2SecurityScheme,
2626
OAuthFlows,
2727
OpenIdConnectSecurityScheme,
28-
Role,
2928
SecurityRequirement,
3029
SecurityScheme,
3130
StringList,

tests/client/test_client_factory.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Tests for the ClientFactory."""
22

3-
from unittest.mock import AsyncMock, MagicMock, patch
43
import typing
54

5+
from unittest.mock import AsyncMock, MagicMock, patch
6+
67
import httpx
78
import pytest
89

tests/client/test_client_factory_grpc.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"""Tests for GRPC transport selection in ClientFactory."""
22

33
from unittest.mock import MagicMock, patch
4+
45
import pytest
56

67
from a2a.client import ClientConfig, ClientFactory
7-
from a2a.types.a2a_pb2 import AgentCard, AgentInterface, AgentCapabilities
8+
from a2a.types.a2a_pb2 import AgentCapabilities, AgentCard, AgentInterface
89
from a2a.utils.constants import TransportProtocol
910

1011

tests/client/transports/test_grpc_client.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,23 @@
33
import grpc
44
import pytest
55

6-
from google.protobuf import any_pb2
7-
from google.rpc import error_details_pb2, status_pb2
8-
96
from a2a.client.client import ClientCallContext
107
from a2a.client.transports.grpc import GrpcTransport
118
from a2a.extensions.common import HTTP_EXTENSION_HEADER
12-
from a2a.utils.constants import VERSION_HEADER, PROTOCOL_VERSION_CURRENT
13-
from a2a.utils.errors import A2A_ERROR_REASONS
9+
from a2a.helpers.proto_helpers import get_text_parts
1410
from a2a.types import a2a_pb2
1511
from a2a.types.a2a_pb2 import (
1612
AgentCapabilities,
1713
AgentCard,
1814
AgentInterface,
1915
Artifact,
2016
AuthenticationInfo,
21-
TaskPushNotificationConfig,
2217
DeleteTaskPushNotificationConfigRequest,
2318
GetTaskPushNotificationConfigRequest,
2419
GetTaskRequest,
2520
ListTaskPushNotificationConfigsRequest,
2621
Message,
2722
Part,
28-
TaskPushNotificationConfig,
2923
Role,
3024
SendMessageRequest,
3125
Task,
@@ -35,7 +29,10 @@
3529
TaskStatus,
3630
TaskStatusUpdateEvent,
3731
)
38-
from a2a.helpers.proto_helpers import get_text_parts
32+
from a2a.utils.constants import PROTOCOL_VERSION_CURRENT, VERSION_HEADER
33+
from a2a.utils.errors import A2A_ERROR_REASONS
34+
from google.protobuf import any_pb2
35+
from google.rpc import error_details_pb2, status_pb2
3936

4037

4138
@pytest.fixture

tests/client/transports/test_jsonrpc_client.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,6 @@
88
import httpx
99
import pytest
1010

11-
from google.protobuf import json_format
12-
from httpx_sse import EventSource, SSEError
13-
1411
from a2a.client.errors import A2AClientError
1512
from a2a.client.transports.jsonrpc import JsonRpcTransport
1613
from a2a.types.a2a_pb2 import (
@@ -33,6 +30,8 @@
3330
TaskState,
3431
)
3532
from a2a.utils.errors import JSON_RPC_ERROR_CODE_MAP
33+
from google.protobuf import json_format
34+
from httpx_sse import EventSource, SSEError
3635

3736

3837
@pytest.fixture

tests/client/transports/test_rest_client.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@
44
import httpx
55
import pytest
66

7-
from google.protobuf import json_format
8-
from google.protobuf.timestamp_pb2 import Timestamp
9-
from httpx_sse import EventSource, ServerSentEvent
10-
11-
from a2a.helpers.proto_helpers import new_text_message
127
from a2a.client.client import ClientCallContext
138
from a2a.client.errors import A2AClientError
149
from a2a.client.transports.rest import RestTransport
1510
from a2a.extensions.common import HTTP_EXTENSION_HEADER
11+
from a2a.helpers.proto_helpers import new_text_message
1612
from a2a.types.a2a_pb2 import (
1713
AgentCapabilities,
1814
AgentCard,
@@ -31,6 +27,9 @@
3127
)
3228
from a2a.utils.constants import TransportProtocol
3329
from a2a.utils.errors import A2A_REST_ERROR_MAPPING
30+
from google.protobuf import json_format
31+
from google.protobuf.timestamp_pb2 import Timestamp
32+
from httpx_sse import EventSource, ServerSentEvent
3433

3534

3635
@pytest.fixture

0 commit comments

Comments
 (0)