Skip to content

Commit cbb736d

Browse files
committed
Restructure verification targets and type integration test fixtures
1 parent 5f6434c commit cbb736d

16 files changed

Lines changed: 149 additions & 41 deletions

File tree

‎Makefile‎

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,60 @@ SMITHY_PYTHON ?= ../smithy-python
77
# Clients per gradle build in `make clients`; lower it if the build runs out of heap.
88
BATCH_SIZE ?= 25
99

10-
.PHONY: docs docs-serve docs-clean docs-install docs-lock venv gen clients publish-codegen aio-check
10+
.PHONY: docs docs-serve docs-clean docs-install docs-lock venv gen clients publish-codegen aio-check static-analysis test-unit test-functional test-integration
11+
12+
# Each client and package runs in its own .venv. Dependencies outside this repo,
13+
# including smithy-python, resolve from PyPI at the versions each one declares.
14+
PROJECTS := $(patsubst %/pyproject.toml,%,$(wildcard clients/*/pyproject.toml packages/*/pyproject.toml))
15+
RUFF_VERSION := 0.15.22
16+
PYRIGHT_VERSION := 1.1.406
17+
UV_RUN = uv run --python $(PYTHON_VERSION) --project
18+
19+
# Q Business is sunsetting; new application creation is unavailable.
20+
INTEGRATION_SKIP := clients/aws-sdk-qbusiness
21+
22+
static-analysis:
23+
@set -e; for p in $(PROJECTS); do \
24+
dirs=$$(ls -d $$p/src $$p/tests 2>/dev/null || true); \
25+
group=$$(grep -q '^test = \[' $$p/pyproject.toml && echo --group test || true); \
26+
echo "== $$p"; \
27+
uvx ruff@$(RUFF_VERSION) check --config $$p/pyproject.toml $$dirs; \
28+
uvx ruff@$(RUFF_VERSION) format --check --config $$p/pyproject.toml $$dirs; \
29+
$(UV_RUN) $$p $$group --with pyright==$(PYRIGHT_VERSION) pyright -p $$p $$dirs; \
30+
done
31+
# TODO: enable once aws-credentials-sts passes the aio naming rules.
32+
# $(MAKE) aio-check
33+
34+
test-unit:
35+
@set -e; for p in $(PROJECTS); do \
36+
[ -d $$p/tests/unit ] || continue; \
37+
echo "== $$p"; \
38+
$(UV_RUN) $$p --group test pytest $$p/tests/unit; \
39+
done
40+
41+
test-functional:
42+
# pytest exits 5 when a generated protocol test file has no cases.
43+
@set -e; for p in $(PROJECTS); do \
44+
[ -n "$$(find $$p/tests -name 'test_*.py' -not -path '*/tests/unit/*' -not -path '*/tests/integration/*' 2>/dev/null)" ] || continue; \
45+
echo "== $$p"; \
46+
$(UV_RUN) $$p --group test pytest $$p/tests --ignore=$$p/tests/unit --ignore=$$p/tests/integration || [ $$? -eq 5 ]; \
47+
done
48+
49+
# Calls live services, so it needs AWS credentials.
50+
test-integration:
51+
@set -e; for p in $(filter-out $(INTEGRATION_SKIP),$(PROJECTS)); do \
52+
[ -d $$p/tests/integration ] || continue; \
53+
echo "== $$p"; \
54+
$(UV_RUN) $$p --group test --all-extras pytest $$p/tests/integration; \
55+
done
56+
57+
# Regenerate every client from codegen/aws-models with codegen built from the
58+
# smithy-python commit in codegen/smithy-python.toml. Each client is cleared first,
59+
# keeping only the paths in gen_client.py's KEEP, then scripts/client-overlays is applied.
60+
clients:
61+
SMITHY_PYTHON=$(abspath $(SMITHY_PYTHON)) uv run python codegen/gen_client.py --all --pinned --batch-size $(BATCH_SIZE)
62+
63+
########################## Granular targets below ##########################
1164

1265
# Publishes the smithy-python codegen jars to the local Maven cache so `gen`
1366
# can resolve them. Run once, and again whenever the generator changes.
@@ -19,12 +72,6 @@ publish-codegen:
1972
gen:
2073
SMITHY_PYTHON=$(abspath $(SMITHY_PYTHON)) uv run python codegen/gen_client.py $(filter-out gen,$(MAKECMDGOALS))
2174

22-
# Regenerate every client from codegen/aws-models with codegen built from the
23-
# smithy-python commit in codegen/smithy-python.toml. Each client is cleared first,
24-
# keeping only the paths in gen_client.py's KEEP, then scripts/client-overlays is applied.
25-
clients:
26-
SMITHY_PYTHON=$(abspath $(SMITHY_PYTHON)) uv run python codegen/gen_client.py --all --pinned --batch-size $(BATCH_SIZE)
27-
2875
# Swallow the client-name argument so make does not treat it as a target.
2976
# Scoped to `gen` so it does not mask typos in unrelated commands.
3077
ifeq (gen,$(firstword $(MAKECMDGOALS)))

‎clients/aws-sdk-connecthealth/pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ test = [
4242
"boto3",
4343
"pytest>=9.0.1,<10.0.0",
4444
"pytest-asyncio>=1.3.0,<1.4.0",
45+
"types-boto3-lite[s3]",
4546
]
4647

4748
[build-system]

‎clients/aws-sdk-connecthealth/tests/integration/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
REGION = "us-east-1"
1414
AUDIO_FILE = Path(__file__).parent / "assets" / "test.wav"
1515

16+
# (domain_id, subscription_id, output_s3_uri)
17+
type ConnectHealthResources = tuple[str, str, str]
18+
1619

1720
async def create_connecthealth_client(
1821
region: str, *, transport: HTTPClient | None = None

‎clients/aws-sdk-connecthealth/tests/integration/conftest.py‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010

1111
import asyncio
1212
import uuid
13+
from collections.abc import AsyncIterator
1314
from typing import Any
1415

1516
import boto3
1617
import pytest
18+
from types_boto3_s3 import S3Client
1719

1820
from aws_sdk_connecthealth.client import AsyncConnectHealthClient
1921
from aws_sdk_connecthealth.models import (
@@ -25,7 +27,7 @@
2527
SubscriptionStatus,
2628
)
2729

28-
from . import REGION, create_connecthealth_client
30+
from . import REGION, ConnectHealthResources, create_connecthealth_client
2931

3032
# Tags applied to all resources so orphaned resources from interrupted
3133
# test runs can be discovered and cleaned up.
@@ -118,7 +120,7 @@ def _create_s3_bucket(s3_client: Any, bucket_name: str) -> None:
118120
s3_client.create_bucket(
119121
Bucket=bucket_name,
120122
CreateBucketConfiguration={
121-
"Tags": [{"Key": k, "Value": v} for k, v in _TAGS.items()],
123+
"Tags": [{"Key": k, "Value": v} for k, v in _TAGS.items()]
122124
},
123125
)
124126

@@ -146,13 +148,13 @@ def _delete_s3_bucket(s3_client: Any, bucket_name: str) -> None:
146148

147149

148150
@pytest.fixture(scope="session")
149-
async def connecthealth_resources():
151+
async def connecthealth_resources() -> AsyncIterator[ConnectHealthResources]:
150152
"""Create ConnectHealth resources for the test session and delete them after."""
151153
unique_suffix = uuid.uuid4().hex[:16]
152154
domain_name = f"integ-test-connecthealth-domain-{unique_suffix}"
153155
bucket_name = f"integ-test-connecthealth-bucket-{unique_suffix}"
154156

155-
s3_client = boto3.client("s3", region_name=REGION)
157+
s3_client: S3Client = boto3.client("s3", region_name=REGION)
156158
async with await create_connecthealth_client(REGION) as client:
157159
domain_id: str | None = None
158160
subscription_id: str | None = None

‎clients/aws-sdk-connecthealth/tests/integration/test_bidirectional_streaming.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,13 @@
3939
StartMedicalScribeListeningSessionOutput,
4040
)
4141

42-
from . import AUDIO_FILE, REGION, create_connecthealth_client, streaming_endpoint_plugin
42+
from . import (
43+
AUDIO_FILE,
44+
REGION,
45+
ConnectHealthResources,
46+
create_connecthealth_client,
47+
streaming_endpoint_plugin,
48+
)
4349

4450
SAMPLE_RATE = 16000
4551
BYTES_PER_SAMPLE = 2
@@ -155,7 +161,9 @@ async def _receive_events(
155161
return got_transcript
156162

157163

158-
async def test_start_medical_scribe_listening_session(connecthealth_resources) -> None:
164+
async def test_start_medical_scribe_listening_session(
165+
connecthealth_resources: ConnectHealthResources,
166+
) -> None:
159167
"""Test bidirectional streaming with audio input and transcript output."""
160168
domain_id, subscription_id, output_s3_uri = connecthealth_resources
161169

‎clients/aws-sdk-connecthealth/tests/integration/test_non_streaming.py‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
SubscriptionStatus,
1818
)
1919

20-
from . import REGION, create_connecthealth_client
20+
from . import REGION, ConnectHealthResources, create_connecthealth_client
2121

2222

23-
async def test_list_domains(connecthealth_resources) -> None:
23+
async def test_list_domains(connecthealth_resources: ConnectHealthResources) -> None:
2424
"""Test non-streaming ListDomains operation."""
2525
_ = connecthealth_resources
2626
async with await create_connecthealth_client(REGION) as client:
@@ -31,7 +31,7 @@ async def test_list_domains(connecthealth_resources) -> None:
3131
assert len(response.domains) >= 1
3232

3333

34-
async def test_get_domain(connecthealth_resources) -> None:
34+
async def test_get_domain(connecthealth_resources: ConnectHealthResources) -> None:
3535
"""Test non-streaming GetDomain operation."""
3636
domain_id, _, _ = connecthealth_resources
3737
async with await create_connecthealth_client(REGION) as client:
@@ -51,7 +51,9 @@ async def test_get_domain(connecthealth_resources) -> None:
5151
assert response.tags == {"Purpose": "IntegTest"}
5252

5353

54-
async def test_list_subscriptions(connecthealth_resources) -> None:
54+
async def test_list_subscriptions(
55+
connecthealth_resources: ConnectHealthResources,
56+
) -> None:
5557
"""Test non-streaming ListSubscriptions operation."""
5658
domain_id, subscription_id, _ = connecthealth_resources
5759
async with await create_connecthealth_client(REGION) as client:
@@ -68,7 +70,9 @@ async def test_list_subscriptions(connecthealth_resources) -> None:
6870
assert sub.domain_id == domain_id
6971

7072

71-
async def test_get_subscription(connecthealth_resources) -> None:
73+
async def test_get_subscription(
74+
connecthealth_resources: ConnectHealthResources,
75+
) -> None:
7276
"""Test non-streaming GetSubscription operation."""
7377
domain_id, subscription_id, _ = connecthealth_resources
7478
async with await create_connecthealth_client(REGION) as client:

‎clients/aws-sdk-lex-runtime-v2/pyproject.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ test = [
4242
"boto3",
4343
"pytest>=9.0.1,<10.0.0",
4444
"pytest-asyncio>=1.3.0,<1.4.0",
45+
"types-boto3-lite[iam,lexv2-models,sts]",
4546
]
4647

4748
[build-system]

‎clients/aws-sdk-lex-runtime-v2/tests/integration/conftest.py‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
import boto3
1616
import pytest
17+
from types_boto3_iam import IAMClient
18+
from types_boto3_lexv2_models import LexModelsV2Client
19+
from types_boto3_sts import STSClient
1720

1821
from . import LOCALE_ID, REGION
1922

@@ -144,9 +147,9 @@ def lex_bot():
144147
role_name = f"integ-test-lex-runtime-v2-role-{unique_suffix}"
145148
bot_name = f"integ-test-lex-runtime-v2-bot-{unique_suffix}"
146149

147-
iam_client = boto3.client("iam")
148-
lex_client = boto3.client("lexv2-models", region_name=REGION)
149-
sts_client = boto3.client("sts")
150+
iam_client: IAMClient = boto3.client("iam")
151+
lex_client: LexModelsV2Client = boto3.client("lexv2-models", region_name=REGION)
152+
sts_client: STSClient = boto3.client("sts")
150153

151154
bot_id = None
152155
try:

‎clients/aws-sdk-polly/tests/integration/test_bidirectional_streaming.py‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
StartSpeechSynthesisStreamEventStreamServiceQuotaExceededException,
2020
StartSpeechSynthesisStreamEventStreamStreamClosedEvent,
2121
StartSpeechSynthesisStreamEventStreamThrottlingException,
22-
StartSpeechSynthesisStreamEventStreamUnknown,
2322
StartSpeechSynthesisStreamEventStreamValidationException,
2423
StartSpeechSynthesisStreamInput,
2524
StartSpeechSynthesisStreamOutput,
@@ -85,12 +84,8 @@ async def _receive_audio(
8584
break
8685
elif isinstance(event, ERROR_EVENT_TYPES):
8786
raise event.value
88-
elif isinstance(event, StartSpeechSynthesisStreamEventStreamUnknown):
89-
raise RuntimeError(f"Received unknown event in stream: {event.tag}")
9087
else:
91-
raise RuntimeError(
92-
f"Received unexpected event type in stream: {type(event).__name__}"
93-
)
88+
raise RuntimeError(f"Received unknown event in stream: {event.tag}")
9489

9590
return audio_bytes, request_characters
9691

‎clients/aws-sdk-qbusiness/tests/integration/conftest.py‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010

1111
import asyncio
1212
import uuid
13+
from collections.abc import AsyncIterator
1314

1415
import pytest
1516

17+
from aws_sdk_qbusiness.client import AsyncQBusinessClient
1618
from aws_sdk_qbusiness.models import (
1719
ApplicationStatus,
1820
CreateApplicationInput,
@@ -46,7 +48,9 @@
4648
_THROTTLE_RETRY_TIMEOUT_SECONDS = 300
4749

4850

49-
async def _wait_for_application_active(client, application_id: str) -> None:
51+
async def _wait_for_application_active(
52+
client: AsyncQBusinessClient, application_id: str
53+
) -> None:
5054
"""Wait for an Application to reach ACTIVE status.
5155
5256
Args:
@@ -68,7 +72,9 @@ async def _wait_for_application_active(client, application_id: str) -> None:
6872
raise TimeoutError(f"Application {application_id} did not become ACTIVE in time")
6973

7074

71-
async def _wait_for_index_active(client, application_id: str, index_id: str) -> None:
75+
async def _wait_for_index_active(
76+
client: AsyncQBusinessClient, application_id: str, index_id: str
77+
) -> None:
7278
"""Wait for an Index to reach ACTIVE status.
7379
7480
Args:
@@ -92,7 +98,7 @@ async def _wait_for_index_active(client, application_id: str, index_id: str) ->
9298

9399

94100
async def _wait_for_retriever_active(
95-
client, application_id: str, retriever_id: str
101+
client: AsyncQBusinessClient, application_id: str, retriever_id: str
96102
) -> None:
97103
"""Wait for a Retriever to reach ACTIVE status.
98104
@@ -119,7 +125,7 @@ async def _wait_for_retriever_active(
119125

120126

121127
async def _create_qbusiness_app(
122-
client, app_name: str, index_name: str, retriever_name: str
128+
client: AsyncQBusinessClient, app_name: str, index_name: str, retriever_name: str
123129
) -> str:
124130
"""Create a Q Business application with index and retriever.
125131
@@ -185,7 +191,9 @@ async def _create_qbusiness_app(
185191
return application_id
186192

187193

188-
async def _delete_qbusiness_app(client, application_id: str | None) -> None:
194+
async def _delete_qbusiness_app(
195+
client: AsyncQBusinessClient, application_id: str | None
196+
) -> None:
189197
"""Delete a Q Business application. Cascades to its index and retriever.
190198
191199
Args:
@@ -200,7 +208,7 @@ async def _delete_qbusiness_app(client, application_id: str | None) -> None:
200208

201209

202210
@pytest.fixture(scope="session")
203-
async def qbusiness_app():
211+
async def qbusiness_app() -> AsyncIterator[str]:
204212
"""Create a Q Business application for the test session and delete it after."""
205213
unique_suffix = uuid.uuid4().hex[:16]
206214
app_name = f"integ-test-qbusiness-app-{unique_suffix}"

0 commit comments

Comments
 (0)