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
4 changes: 2 additions & 2 deletions assemblyai/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ def delete_transcript(

def upload_file(
client: httpx.Client,
audio_file: BinaryIO,
audio_file: Union[bytes, BinaryIO],
) -> str:
"""
Uploads the given file.

Args:
`client`: the HTTP client
`audio_file`: an opened file (in binary mode)
`audio_file`: the raw audio bytes, or an opened file (in binary mode)

Returns: The URL of the uploaded audio file.
"""
Expand Down
40 changes: 20 additions & 20 deletions assemblyai/transcriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ def __init__(
self._client = client
self.config = config

def upload_file(self, data: Union[str, BinaryIO]) -> str:
def upload_file(self, data: Union[str, bytes, BinaryIO]) -> str:
if isinstance(data, str):
with open(data, "rb") as audio_file:
return api.upload_file(
Expand Down Expand Up @@ -904,7 +904,7 @@ def transcribe_url(
def transcribe_file(
self,
*,
data: Union[str, BinaryIO],
data: Union[str, bytes, BinaryIO],
config: types.TranscriptionConfig,
poll: bool,
) -> Transcript:
Expand All @@ -919,7 +919,7 @@ def transcribe_file(

def transcribe(
self,
data: Union[str, BinaryIO],
data: Union[str, bytes, BinaryIO],
config: Optional[types.TranscriptionConfig],
poll: bool,
) -> Transcript:
Expand All @@ -942,7 +942,7 @@ def transcribe(
def transcribe_group(
self,
*,
data: List[Union[str, BinaryIO]],
data: List[Union[str, bytes, BinaryIO]],
config: Optional[types.TranscriptionConfig],
poll: bool,
return_failures: Optional[bool] = False,
Expand Down Expand Up @@ -1075,7 +1075,7 @@ def config(self, config: types.TranscriptionConfig) -> None:
"""
self._impl.config = config

def upload_file(self, data: Union[str, BinaryIO]) -> str:
def upload_file(self, data: Union[str, bytes, BinaryIO]) -> str:
"""
Uploads an audio file which can be specified as local path or binary object.

Expand All @@ -1087,7 +1087,7 @@ def upload_file(self, data: Union[str, BinaryIO]) -> str:
return self._impl.upload_file(data=data)

def upload_file_async(
self, data: Union[str, BinaryIO]
self, data: Union[str, bytes, BinaryIO]
) -> concurrent.futures.Future[str]:
"""
Uploads an audio file which can be specified as local path or binary object.
Expand All @@ -1104,14 +1104,14 @@ def upload_file_async(

def submit(
self,
data: Union[str, BinaryIO],
data: Union[str, bytes, BinaryIO],
config: Optional[types.TranscriptionConfig] = None,
) -> Transcript:
"""
Submits a transcription job without waiting for its completion.

Args:
data: An URL, a local file (as path), or a binary object.
data: An URL, a local file (as path), raw `bytes`, or a binary object.
config: Transcription options and features. If `None` is given, the Transcriber's
default configuration will be used.
"""
Expand All @@ -1123,15 +1123,15 @@ def submit(

def submit_group(
self,
data: List[Union[str, BinaryIO]],
data: List[Union[str, bytes, BinaryIO]],
config: Optional[types.TranscriptionConfig] = None,
return_failures: Optional[bool] = False,
) -> Union[TranscriptGroup, Tuple[TranscriptGroup, List[types.AssemblyAIError]]]:
"""
Submits multiple transcription jobs without waiting for their completion.

Args:
data: A list of local paths, URLs, or binary objects (can be mixed).
data: A list of local paths, URLs, raw `bytes`, or binary objects (can be mixed).
config: Transcription options and features. If `None` is given, the Transcriber's
default configuration will be used.
return_failures: Whether to include a list of errors for transcriptions that failed due to HTTP errors
Expand All @@ -1145,14 +1145,14 @@ def submit_group(

def transcribe(
self,
data: Union[str, BinaryIO],
data: Union[str, bytes, BinaryIO],
config: Optional[types.TranscriptionConfig] = None,
) -> Transcript:
"""
Transcribes an audio file which can be specified as local path, URL, or binary object.
Transcribes an audio file which can be specified as local path, URL, raw `bytes`, or binary object.

Args:
data: An URL, a local file (as path), or a binary object.
data: An URL, a local file (as path), raw `bytes`, or a binary object.
config: Transcription options and features. If `None` is given, the Transcriber's
default configuration will be used.
"""
Expand All @@ -1165,14 +1165,14 @@ def transcribe(

def transcribe_async(
self,
data: Union[str, BinaryIO],
data: Union[str, bytes, BinaryIO],
config: Optional[types.TranscriptionConfig] = None,
) -> concurrent.futures.Future[Transcript]:
"""
Transcribes an audio file which can be specified as local path, URL, or binary object.
Transcribes an audio file which can be specified as local path, URL, raw `bytes`, or binary object.

Args:
data: An URL, a local file (as path), or a binary object.
data: An URL, a local file (as path), raw `bytes`, or a binary object.
config: Transcription options and features. If `None` is given, the Transcriber's
default configuration will be used.
"""
Expand All @@ -1186,15 +1186,15 @@ def transcribe_async(

def transcribe_group(
self,
data: List[Union[str, BinaryIO]],
data: List[Union[str, bytes, BinaryIO]],
config: Optional[types.TranscriptionConfig] = None,
return_failures: Optional[bool] = False,
) -> Union[TranscriptGroup, Tuple[TranscriptGroup, List[types.AssemblyAIError]]]:
"""
Transcribes a list of files (as local paths, URLs, or binary objects).

Args:
data: A list of local paths, URLs, or binary objects (can be mixed).
data: A list of local paths, URLs, raw `bytes`, or binary objects (can be mixed).
config: Transcription options and features. If `None` is given, the Transcriber's
default configuration will be used.
return_failures: Whether to include a list of errors for transcriptions that failed due to HTTP errors
Expand All @@ -1209,7 +1209,7 @@ def transcribe_group(

def transcribe_group_async(
self,
data: List[Union[str, BinaryIO]],
data: List[Union[str, bytes, BinaryIO]],
config: Optional[types.TranscriptionConfig] = None,
return_failures: Optional[bool] = False,
) -> concurrent.futures.Future[
Expand All @@ -1219,7 +1219,7 @@ def transcribe_group_async(
Transcribes a list of files (as local paths, URLs, or binary objects) asynchronously.

Args:
data: A list of local paths, URLs, or binary objects (can be mixed).
data: A list of local paths, URLs, raw `bytes`, or binary objects (can be mixed).
config: Transcription options and features. If `None` is given, the Transcriber's
default configuration will be used.
return_failures: Whether to include a list of errors for transcriptions that failed due to HTTP errors
Expand Down
109 changes: 109 additions & 0 deletions tests/unit/test_transcriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,115 @@ def test_transcribe_file_binary_succeeds(httpx_mock: HTTPXMock):
assert len(httpx_mock.get_requests()) == 3


@pytest.mark.parametrize(
"method_name",
[
"transcribe",
"transcribe_async",
"transcribe_group",
"transcribe_group_async",
"submit",
"submit_group",
"upload_file",
"upload_file_async",
],
)
def test_transcribe_data_param_accepts_bytes(method_name):
"""
Regression test for #102: the public ``data`` parameter of the transcribe/
upload methods must advertise ``bytes`` in its type annotation (passing raw
``bytes`` already works at runtime; this locks in the type hint so static
type checkers accept it).
"""
import typing

method = getattr(aai.Transcriber, method_name)
hints = typing.get_type_hints(method)
annotation = hints["data"]

# The annotation is either ``Union[str, bytes, BinaryIO]`` (single-file
# methods) or ``List[Union[str, bytes, BinaryIO]]`` (group methods). Unwrap
# a ``List[...]`` wrapper before inspecting the Union members.
args = typing.get_args(annotation)
if args and args[0] not in (str, bytes) and typing.get_origin(annotation) in (
list,
typing.List,
):
annotation = args[0]
args = typing.get_args(annotation)

assert bytes in args, (
f"{method_name} 'data' annotation {annotation!r} must include bytes"
)


def test_transcribe_group_bytes_succeeds(httpx_mock: HTTPXMock):
"""
Tests whether transcribing a group of raw ``bytes`` inputs works (#102).
"""

mock_transcript_response = factories.generate_dict_factory(
factories.TranscriptCompletedResponseFactory
)()

files_data = [os.urandom(10), os.urandom(10)]

response_1 = copy.deepcopy(mock_transcript_response)
response_2 = copy.deepcopy(mock_transcript_response)

expected_audio_urls = ["https://example.org/1.wav", "https://example.org/2.wav"]
response_1["audio_url"] = expected_audio_urls[0]
response_2["audio_url"] = expected_audio_urls[1]

# two uploads (one per bytes input)
httpx_mock.add_response(
url=f"{aai.settings.base_url}{ENDPOINT_UPLOAD}",
status_code=httpx.codes.OK,
method="POST",
json={"upload_url": expected_audio_urls[0]},
match_content=files_data[0],
)
httpx_mock.add_response(
url=f"{aai.settings.base_url}{ENDPOINT_UPLOAD}",
status_code=httpx.codes.OK,
method="POST",
json={"upload_url": expected_audio_urls[1]},
match_content=files_data[1],
)

httpx_mock.add_response(
url=f"{aai.settings.base_url}{ENDPOINT_TRANSCRIPT}",
status_code=httpx.codes.OK,
method="POST",
json=response_1,
)
httpx_mock.add_response(
url=f"{aai.settings.base_url}{ENDPOINT_TRANSCRIPT}",
status_code=httpx.codes.OK,
method="POST",
json=response_2,
)

httpx_mock.add_response(
url=f"{aai.settings.base_url}{ENDPOINT_TRANSCRIPT}/{response_1['id']}",
status_code=httpx.codes.OK,
method="GET",
json=response_1,
)
httpx_mock.add_response(
url=f"{aai.settings.base_url}{ENDPOINT_TRANSCRIPT}/{response_2['id']}",
status_code=httpx.codes.OK,
method="GET",
json=response_2,
)

transcriber = aai.Transcriber()
transcript_group = transcriber.transcribe_group(files_data)

assert len(transcript_group.transcripts) == 2
assert {t.audio_url for t in transcript_group} == set(expected_audio_urls)


def test_transcribe_group_urls_succeeds(httpx_mock: HTTPXMock):
"""
Tests whether the transcription of multiple URLs work.
Expand Down
Loading