Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions clients/aws-sdk-polly/tests/integration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

from smithy_aws_core.identity import EnvironmentCredentialsResolver

from aws_sdk_polly.client import PollyClient
from aws_sdk_polly.config import Config

REGION = "us-east-1"
VOICE_ID = "Matthew"
ENGINE = "generative"
OUTPUT_FORMAT = "mp3"
SAMPLE_RATE = "24000"
TEST_TEXT = "Hello from the AWS SDK for Python Polly integration tests."


def create_polly_client(region: str) -> PollyClient:
"""Helper to create a PollyClient for a given region."""
return PollyClient(
config=Config(
endpoint_uri=f"https://polly.{region}.amazonaws.com",
region=region,
aws_credentials_identity_resolver=EnvironmentCredentialsResolver(),
)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

"""Test bidirectional streaming event stream handling."""

import asyncio

from smithy_core.aio.eventstream import DuplexEventStream

from aws_sdk_polly.models import (
CloseStreamEvent,
StartSpeechSynthesisStreamActionStream,
StartSpeechSynthesisStreamActionStreamCloseStreamEvent,
StartSpeechSynthesisStreamActionStreamTextEvent,
StartSpeechSynthesisStreamEventStream,
StartSpeechSynthesisStreamEventStreamAudioEvent,
StartSpeechSynthesisStreamEventStreamServiceFailureException,
StartSpeechSynthesisStreamEventStreamServiceQuotaExceededException,
StartSpeechSynthesisStreamEventStreamStreamClosedEvent,
StartSpeechSynthesisStreamEventStreamThrottlingException,
StartSpeechSynthesisStreamEventStreamUnknown,
StartSpeechSynthesisStreamEventStreamValidationException,
StartSpeechSynthesisStreamInput,
StartSpeechSynthesisStreamOutput,
TextEvent,
)

from . import (
ENGINE,
OUTPUT_FORMAT,
REGION,
SAMPLE_RATE,
TEST_TEXT,
VOICE_ID,
create_polly_client,
)

ERROR_EVENT_TYPES = (
StartSpeechSynthesisStreamEventStreamValidationException,
StartSpeechSynthesisStreamEventStreamServiceQuotaExceededException,
StartSpeechSynthesisStreamEventStreamServiceFailureException,
StartSpeechSynthesisStreamEventStreamThrottlingException,
)


async def _send_text(
stream: DuplexEventStream[
StartSpeechSynthesisStreamActionStream,
StartSpeechSynthesisStreamEventStream,
StartSpeechSynthesisStreamOutput,
],
) -> None:
"""Send text input and close the input stream."""
await stream.input_stream.send(
StartSpeechSynthesisStreamActionStreamTextEvent(value=TextEvent(text=TEST_TEXT))
)
await stream.input_stream.send(
StartSpeechSynthesisStreamActionStreamCloseStreamEvent(CloseStreamEvent())
)
await stream.input_stream.close()


async def _receive_audio(
stream: DuplexEventStream[
StartSpeechSynthesisStreamActionStream,
StartSpeechSynthesisStreamEventStream,
StartSpeechSynthesisStreamOutput,
],
) -> tuple[int, int | None]:
"""Receive synthesized audio and the final stream summary."""
audio_bytes = 0
request_characters: int | None = None

_, output_stream = await stream.await_output()
if output_stream is None:
return audio_bytes, request_characters

async for event in output_stream:
if isinstance(event, StartSpeechSynthesisStreamEventStreamAudioEvent):
if event.value.audio_chunk:
audio_bytes += len(event.value.audio_chunk)
elif isinstance(event, StartSpeechSynthesisStreamEventStreamStreamClosedEvent):
request_characters = event.value.request_characters
break
elif isinstance(event, ERROR_EVENT_TYPES):
raise event.value
elif isinstance(event, StartSpeechSynthesisStreamEventStreamUnknown):
raise RuntimeError(f"Received unknown event in stream: {event.tag}")
else:
raise RuntimeError(
f"Received unexpected event type in stream: {type(event).__name__}"
)

return audio_bytes, request_characters


async def test_start_speech_synthesis_stream() -> None:
"""Test bidirectional streaming with text input and audio output."""
client = create_polly_client(REGION)

stream = await client.start_speech_synthesis_stream(
input=StartSpeechSynthesisStreamInput(
engine=ENGINE,
output_format=OUTPUT_FORMAT,
sample_rate=SAMPLE_RATE,
voice_id=VOICE_ID,
)
)

results = await asyncio.gather(_send_text(stream), _receive_audio(stream))
audio_bytes, request_characters = results[1]

assert audio_bytes > 0, "Expected to receive synthesized audio"
assert request_characters == len(TEST_TEXT)
27 changes: 27 additions & 0 deletions clients/aws-sdk-polly/tests/integration/test_non_streaming.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

"""Test non-streaming output type handling."""

from aws_sdk_polly.models import DescribeVoicesInput, DescribeVoicesOutput

from . import ENGINE, REGION, VOICE_ID, create_polly_client


async def test_describe_voices() -> None:
"""Test non-streaming DescribeVoices operation."""
client = create_polly_client(REGION)

response = await client.describe_voices(input=DescribeVoicesInput(engine=ENGINE))

assert isinstance(response, DescribeVoicesOutput)
assert response.voices is not None
assert len(response.voices) > 0

voices_by_id = {voice.id: voice for voice in response.voices if voice.id}
assert VOICE_ID in voices_by_id

voice = voices_by_id[VOICE_ID]
assert voice.language_code == "en-US"
assert voice.supported_engines is not None
assert ENGINE in voice.supported_engines
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

"""Test output streaming blob handling."""

from smithy_core.aio.utils import read_streaming_blob_async

from aws_sdk_polly.models import SynthesizeSpeechInput, SynthesizeSpeechOutput

from . import (
ENGINE,
OUTPUT_FORMAT,
REGION,
SAMPLE_RATE,
TEST_TEXT,
VOICE_ID,
create_polly_client,
)


async def test_synthesize_speech() -> None:
"""Test output-streaming SynthesizeSpeech operation."""
client = create_polly_client(REGION)

response = await client.synthesize_speech(
input=SynthesizeSpeechInput(
engine=ENGINE,
output_format=OUTPUT_FORMAT,
sample_rate=SAMPLE_RATE,
text=TEST_TEXT,
voice_id=VOICE_ID,
)
)

assert isinstance(response, SynthesizeSpeechOutput)
assert response.content_type == "audio/mpeg"
assert response.request_characters == len(TEST_TEXT)

audio = await read_streaming_blob_async(response.audio_stream)
assert len(audio) > 0