From a2f575d54a24e0f65560e50f307443e1e49383a9 Mon Sep 17 00:00:00 2001 From: Greg Holmes Date: Tue, 20 Oct 2026 16:54:29 +0100 Subject: [PATCH] feat(cli): add SageMaker streaming support --- .../src/deepctl_cmd_listen/command.py | 382 +++++++++++++++++- .../tests/unit/test_ws_and_routing.py | 96 ++++- .../src/deepctl_cmd_speak/command.py | 232 ++++++++++- .../tests/unit/test_speak_command.py | 52 +++ packages/deepctl-core/pyproject.toml | 4 + .../src/deepctl_core/base_command.py | 10 +- .../src/deepctl_core/sagemaker.py | 56 +++ packages/deepctl-core/tests/unit/test_base.py | 36 ++ pyproject.toml | 4 + uv.lock | 365 ++++++++++++++++- 10 files changed, 1222 insertions(+), 15 deletions(-) create mode 100644 packages/deepctl-core/src/deepctl_core/sagemaker.py diff --git a/packages/deepctl-cmd-listen/src/deepctl_cmd_listen/command.py b/packages/deepctl-cmd-listen/src/deepctl_cmd_listen/command.py index f0f0e31e..ac271103 100644 --- a/packages/deepctl-cmd-listen/src/deepctl_cmd_listen/command.py +++ b/packages/deepctl-cmd-listen/src/deepctl_cmd_listen/command.py @@ -17,8 +17,9 @@ import json import sys import threading +from contextlib import suppress from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any, cast from urllib.parse import urlencode import click @@ -28,8 +29,15 @@ BaseResult, Config, DeepgramClient, + get_output_format, ) from deepctl_core.output import _agentic +from deepctl_core.sagemaker import ( + SageMakerTransportError, + create_sagemaker_client, + endpoint_name, + region_name, +) from rich.console import Console from rich.prompt import Confirm, Prompt from rich.table import Table @@ -49,6 +57,9 @@ ) from .models import ListenResult +if TYPE_CHECKING: + from collections.abc import AsyncIterator + # Status messages → stderr so stdout stays clean for transcript piping status = Console(stderr=True) # Structured output (table, json) → stdout @@ -140,6 +151,22 @@ def get_arguments(self) -> list[dict[str, Any]]: "is_flag": True, "is_option": True, }, + { + "names": ["--sagemaker-endpoint"], + "help": ( + "Stream through this Amazon SageMaker endpoint using AWS " + "credentials (or set SAGEMAKER_ENDPOINT_NAME). Requires " + "Python 3.12+ and deepctl[sagemaker]." + ), + "type": str, + "is_option": True, + }, + { + "names": ["--sagemaker-region"], + "help": "AWS region for --sagemaker-endpoint (default: AWS_REGION or us-west-2)", + "type": str, + "is_option": True, + }, # ── Model ───────────────────────────────────────────────── { "names": ["--model", "-m"], @@ -288,6 +315,10 @@ def get_arguments(self) -> list[dict[str, Any]]: }, ] + def uses_external_auth(self, **kwargs: Any) -> bool: + """SageMaker signs streams with AWS credentials, not a Deepgram key.""" + return endpoint_name(kwargs.get("sagemaker_endpoint")) is not None + # ── Entry point ──────────────────────────────────────────────────── def handle( @@ -332,6 +363,8 @@ def handle( # ── Gather options ───────────────────────────────────────────── model = kwargs.get("model") or "nova-3" language = kwargs.get("language") or "en-US" + sagemaker_endpoint = endpoint_name(kwargs.get("sagemaker_endpoint")) + sagemaker_region = region_name(kwargs.get("sagemaker_region")) # flux-* models require listen.v2; everything else uses v1. api_version = 2 if model.startswith("flux-") else 1 @@ -382,7 +415,11 @@ def handle( # REST endpoint, so a file/URL routes to /v1/listen and the server # rejects it ("Flux models are not supported on /v1/listen") wrapped in # a header dump. Say so up front instead. - if api_version >= 2 and mode in ("prerecorded_file", "prerecorded_url"): + if ( + api_version >= 2 + and not sagemaker_endpoint + and mode in ("prerecorded_file", "prerecorded_url") + ): raise click.ClickException( f"Flux STT ({model}) is streaming-only and cannot transcribe " "a file or URL. Use a live source (--mic, stdin, or '-'), or " @@ -425,6 +462,54 @@ def handle( if guided_flow and mode in ("prerecorded_file", "prerecorded_url"): diarize, summarize, topics, sentiment = self._interactive_features() + if sagemaker_endpoint: + if mode == "prerecorded_url": + raise click.ClickException( + "SageMaker supports only bidirectional streaming input. " + "Pass a local file, stdin, or --mic; URLs require the " + "Deepgram Cloud pre-recorded API." + ) + unsupported = [ + name + for name, enabled in ( + ("--summarize", summarize), + ("--topics", topics), + ("--sentiment", sentiment), + ("--probe", probe), + ) + if enabled + ] + if unsupported: + raise click.ClickException( + f"{', '.join(unsupported)} is not available through SageMaker " + "streaming. These are pre-recorded REST features." + ) + if caption_format: + raise click.ClickException( + "--webvtt and --srt are not available through SageMaker " + "streaming. Use the structured transcript output instead." + ) + return self._stream_sagemaker( + endpoint=sagemaker_endpoint, + region=sagemaker_region, + source=source, + mode=mode, + model=model, + language=language, + api_version=api_version, + diarize=diarize, + smart_format=smart_format, + punctuate=punctuate, + interim=interim, + redact=redact, + numerals=numerals, + encoding=encoding, + sample_rate=sample_rate, + channels=channels, + save_to=save_to, + caption_format=caption_format, + ) + # ── Dispatch ─────────────────────────────────────────────────── if mode == "prerecorded_file": assert source is not None @@ -508,6 +593,299 @@ def handle( caption_format=caption_format, ) + # ── SageMaker streaming ───────────────────────────────────────────── + + def _stream_sagemaker( + self, + *, + endpoint: str, + region: str, + source: str | None, + mode: str, + model: str, + language: str, + api_version: int, + diarize: bool, + smart_format: bool, + punctuate: bool, + interim: bool, + redact: tuple[str, ...], + numerals: bool, + encoding: str | None, + sample_rate: int, + channels: int, + save_to: str | None, + caption_format: str | None, + ) -> BaseResult: + """Run Listen over the published SageMaker HTTP/2 transport.""" + if mode == "prerecorded_file": + assert source is not None + if not Path(source).is_file(): + raise click.ClickException(f"File not found: {source}") + + if mode == "stream_mic": + source_label = "mic" + elif mode == "stream_stdin": + source_label = "stdin" + else: + source_label = "file" + + if caption_format: + status.print( + f"[green]✓[/green] SageMaker streaming {source_label} · Ctrl+C to stop" + ) + else: + status.print( + f"[green]✓[/green] SageMaker streaming {source_label} · " + f"{model} · endpoint {endpoint}" + ) + + try: + result = asyncio.run( + self._sagemaker_listen( + endpoint=endpoint, + region=region, + source=source, + mode=mode, + model=model, + language=language, + api_version=api_version, + diarize=diarize, + smart_format=smart_format, + punctuate=punctuate, + interim=interim, + redact=redact, + numerals=numerals, + encoding=encoding, + sample_rate=sample_rate, + channels=channels, + caption_format=caption_format, + ) + ) + except SageMakerTransportError as exc: + raise click.ClickException(str(exc)) from exc + except KeyboardInterrupt: + status.print("\n[yellow]Stopped.[/yellow]") + result = ListenResult(status="success", source=source_label, mode="live") + + if save_to and result.transcript: + self._save_transcript(result.transcript, save_to) + status.print(f"[green]✓[/green] Saved to {save_to}") + result.saved_to = save_to + return result + + async def _sagemaker_listen( + self, + *, + endpoint: str, + region: str, + source: str | None, + mode: str, + model: str, + language: str, + api_version: int, + diarize: bool, + smart_format: bool, + punctuate: bool, + interim: bool, + redact: tuple[str, ...], + numerals: bool, + encoding: str | None, + sample_rate: int, + channels: int, + caption_format: str | None, + ) -> ListenResult: + """Send audio and receive transcripts through an SDK transport connection.""" + from deepgram.core.events import EventType + + client = create_sagemaker_client(endpoint, region) + params: dict[str, Any] = {"model": model} + # Containerized files are self-describing (the published SageMaker + # examples send WAV bytes with no raw-audio params). stdin and mic are + # raw by default, as are explicitly encoded local files. + raw_audio = mode != "prerecorded_file" or encoding is not None + if raw_audio: + params["encoding"] = encoding or "linear16" + params["sample_rate"] = str(sample_rate) + if api_version == 1: + params.update( + { + "language": language, + "smart_format": "true" if smart_format else "false", + "punctuate": "true" if punctuate else "false", + } + ) + if raw_audio: + params["channels"] = str(channels) + if diarize: + params["diarize"] = "true" + if interim: + params["interim_results"] = "true" + if redact: + params["redact"] = list(redact) + if numerals: + params["numerals"] = "true" + + transcript: list[str] = [] + errors: list[str] = [] + show_output = get_output_format() == "default" + v2_turns: dict[Any, str] = {} + completed_v2_turns: set[Any] = set() + closed = asyncio.Event() + source_label = ( + "mic" + if mode == "stream_mic" + else "stdin" + if mode == "stream_stdin" + else "file" + ) + + def on_message(message: Any) -> None: + data = self._sagemaker_message_data(message) + if data.get("type") in ("Error", "FatalError"): + code = data.get("code") or data.get("event") or "UNKNOWN_ERROR" + errors.append( + f"{code}: {data.get('description') or data.get('message') or data}" + ) + return + if api_version == 2: + turn_index = data.get("turn_index", 0) + text = str(data.get("transcript") or "") + if text: + v2_turns[turn_index] = text + event = str(data.get("event") or "") + if event == "EndOfTurn" and text: + if turn_index not in completed_v2_turns: + completed_v2_turns.add(turn_index) + transcript.append(text) + if show_output: + print(text, flush=True) + elif event == "Update" and interim and text and show_output: + print(f"\r{text} ", end="", flush=True) + return + + channel = data.get("channel") or {} + alternatives = channel.get("alternatives") or [] + if not alternatives: + return + alternative = alternatives[0] + text = str(alternative.get("transcript") or "") + if data.get("is_final") and text: + transcript.append(text) + if show_output: + print(text, flush=True) + elif interim and text and show_output: + print(f"\r{text} ", end="", flush=True) + + def on_error(error: Any) -> None: + errors.append(str(error)) + + def on_close(_: Any) -> None: + closed.set() + + connection_api = client.listen.v2 if api_version == 2 else client.listen.v1 + async with connection_api.connect(**params) as connection: + connection.on(EventType.MESSAGE, on_message) + connection.on(EventType.ERROR, on_error) + connection.on(EventType.CLOSE, on_close) + listen_task = asyncio.create_task(connection.start_listening()) + try: + await asyncio.sleep(0) + if mode == "prerecorded_file": + assert source is not None + with Path(source).open("rb") as audio: + while chunk := audio.read(8192): + await connection.send_media(chunk) + elif mode == "stream_stdin": + while chunk := await asyncio.to_thread(sys.stdin.buffer.read, 4096): + await connection.send_media(chunk) + else: + async for chunk in self._sagemaker_mic_chunks( + sample_rate, channels + ): + await connection.send_media(chunk) + await connection.send_close_stream() + close_task = asyncio.create_task(closed.wait()) + done, _ = await asyncio.wait( + (listen_task, close_task), + timeout=30, + return_when=asyncio.FIRST_COMPLETED, + ) + if not done: + raise click.ClickException( + "Timed out waiting for SageMaker to finish transcription." + ) + if not close_task.done(): + close_task.cancel() + with suppress(asyncio.CancelledError): + await close_task + if not listen_task.done(): + await asyncio.wait_for(listen_task, timeout=5) + finally: + if not listen_task.done(): + listen_task.cancel() + await _cancel_and_drain(listen_task) + + if errors: + raise click.ClickException(f"SageMaker Listen error: {errors[-1]}") + if api_version == 2: + for turn_index, text in v2_turns.items(): + if turn_index not in completed_v2_turns: + completed_v2_turns.add(turn_index) + transcript.append(text) + if show_output: + print(text, flush=True) + return ListenResult( + status="success", + source=source_label, + source_path=source or "", + mode="live", + model=model, + language=language, + diarized=diarize, + transcript=" ".join(transcript), + caption_format=caption_format, + ) + + @staticmethod + def _sagemaker_message_data(message: Any) -> dict[str, Any]: + """Convert SDK event models without tying imports to the optional extra.""" + if hasattr(message, "model_dump"): + return cast("dict[str, Any]", message.model_dump(mode="json")) + if isinstance(message, dict): + return message + return cast("dict[str, Any]", vars(message)) + + async def _sagemaker_mic_chunks( + self, sample_rate: int, channels: int + ) -> AsyncIterator[bytes]: + """Yield microphone frames without a WebSocket-specific dependency.""" + import numpy as np + import sounddevice as sd + + loop = asyncio.get_running_loop() + queue: asyncio.Queue[bytes] = asyncio.Queue() + + def callback(indata: Any, frames: int, time: Any, status_info: Any) -> None: + if status_info: + status.print(f"[yellow]Audio warning: {status_info}[/yellow]") + loop.call_soon_threadsafe(queue.put_nowait, bytes(indata)) + + stream = sd.RawInputStream( + samplerate=sample_rate, + channels=channels, + dtype=np.int16, + callback=callback, + blocksize=int(sample_rate * 0.1), + ) + stream.start() + try: + while True: + yield await queue.get() + finally: + stream.stop() + stream.close() + # ── Interactive helpers ──────────────────────────────────────────── def _interactive_select_source( diff --git a/packages/deepctl-cmd-listen/tests/unit/test_ws_and_routing.py b/packages/deepctl-cmd-listen/tests/unit/test_ws_and_routing.py index f173ec4a..99e96b0e 100644 --- a/packages/deepctl-cmd-listen/tests/unit/test_ws_and_routing.py +++ b/packages/deepctl-cmd-listen/tests/unit/test_ws_and_routing.py @@ -3,8 +3,9 @@ from __future__ import annotations +import asyncio import json -from unittest.mock import MagicMock, Mock, patch +from unittest.mock import AsyncMock, MagicMock, Mock, patch import click import pytest @@ -327,6 +328,99 @@ def test_enhanced_uses_v1( ) assert mock_pre.call_args.kwargs["api_version"] == 1 + def test_sagemaker_file_uses_flux_streaming( + self, command, mock_config, mock_auth_manager, mock_client + ): + """A local Flux file uses the SageMaker streaming transport, never REST.""" + with patch.object( + command, "_stream_sagemaker", return_value=ListenResult(status="success") + ) as stream: + command.handle( + config=mock_config, + auth_manager=mock_auth_manager, + client=mock_client, + source="audio.wav", + mic=False, + model="flux-general-en", + sagemaker_endpoint="flux-endpoint", + ) + + assert stream.call_args.kwargs["api_version"] == 2 + assert stream.call_args.kwargs["mode"] == "prerecorded_file" + assert stream.call_args.kwargs["endpoint"] == "flux-endpoint" + + def test_sagemaker_url_is_rejected( + self, command, mock_config, mock_auth_manager, mock_client + ): + with pytest.raises(click.ClickException, match="local file, stdin, or --mic"): + command.handle( + config=mock_config, + auth_manager=mock_auth_manager, + client=mock_client, + source="https://example.com/audio.wav", + mic=False, + sagemaker_endpoint="stt-endpoint", + ) + + def test_sagemaker_batch_features_are_rejected( + self, command, mock_config, mock_auth_manager, mock_client + ): + with pytest.raises(click.ClickException, match="pre-recorded REST features"): + command.handle( + config=mock_config, + auth_manager=mock_auth_manager, + client=mock_client, + source="audio.wav", + mic=False, + summarize=True, + sagemaker_endpoint="stt-endpoint", + ) + + def test_sagemaker_uses_aws_auth(self, command): + assert command.uses_external_auth(sagemaker_endpoint="stt-endpoint") + + def test_sagemaker_container_file_omits_raw_audio_params(self, command, tmp_path): + """WAV files are sent as self-describing streams, not mislabeled PCM.""" + connection = MagicMock() + connection.start_listening = AsyncMock() + connection.send_media = AsyncMock() + connection.send_close_stream = AsyncMock() + context = MagicMock() + context.__aenter__ = AsyncMock(return_value=connection) + context.__aexit__ = AsyncMock(return_value=False) + client = MagicMock() + client.listen.v1.connect.return_value = context + audio = tmp_path / "audio.wav" + audio.write_bytes(b"RIFFfake") + + with patch("deepctl_cmd_listen.command.create_sagemaker_client", return_value=client): + asyncio.run( + command._sagemaker_listen( + endpoint="stt-endpoint", + region="us-west-2", + source=str(audio), + mode="prerecorded_file", + model="nova-3", + language="en-US", + api_version=1, + diarize=False, + smart_format=True, + punctuate=True, + interim=False, + redact=(), + numerals=False, + encoding=None, + sample_rate=16000, + channels=1, + caption_format=None, + ) + ) + + params = client.listen.v1.connect.call_args.kwargs + assert "encoding" not in params + assert "sample_rate" not in params + assert "channels" not in params + # ── --webvtt / --srt mutual exclusivity ─────────────────────────────────────── diff --git a/packages/deepctl-cmd-speak/src/deepctl_cmd_speak/command.py b/packages/deepctl-cmd-speak/src/deepctl_cmd_speak/command.py index 78738efa..349dc420 100644 --- a/packages/deepctl-cmd-speak/src/deepctl_cmd_speak/command.py +++ b/packages/deepctl-cmd-speak/src/deepctl_cmd_speak/command.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio import io import shutil import struct @@ -24,6 +25,12 @@ get_output_format, get_status_console, ) +from deepctl_core.sagemaker import ( + SageMakerTransportError, + create_sagemaker_client, + endpoint_name, + region_name, +) from rich.console import Console from rich.markup import escape from rich.table import Table @@ -301,6 +308,16 @@ def _streaming_wav_header( ) +@contextmanager +def _sagemaker_output_file(path: str | None) -> Iterator[IO[bytes] | None]: + """Open an optional raw-audio output without leaking a partial file handle.""" + if path is None: + yield None + return + with Path(path).open("wb") as output: + yield output + + class _StreamProgress: """Scrolling stderr progress for a Flux audio stream. @@ -438,6 +455,22 @@ def get_arguments(self) -> list[dict[str, Any]]: "is_option": True, "default": _DEFAULT_MODEL, }, + { + "names": ["--sagemaker-endpoint"], + "help": ( + "Stream Aura speech through this Amazon SageMaker endpoint " + "using AWS credentials (or set SAGEMAKER_ENDPOINT_NAME). " + "Requires Python 3.12+ and deepctl[sagemaker]." + ), + "type": str, + "is_option": True, + }, + { + "names": ["--sagemaker-region"], + "help": "AWS region for --sagemaker-endpoint (default: AWS_REGION or us-west-2)", + "type": str, + "is_option": True, + }, { "names": ["--encoding"], "help": ( @@ -505,6 +538,10 @@ def get_arguments(self) -> list[dict[str, Any]]: }, ] + def uses_external_auth(self, **kwargs: Any) -> bool: + """SageMaker signs streams with AWS credentials, not a Deepgram key.""" + return endpoint_name(kwargs.get("sagemaker_endpoint")) is not None + def handle( self, config: Config, @@ -514,7 +551,11 @@ def handle( ) -> BaseResult | None: text = kwargs.get("text") output_path = kwargs.get("output") - model = kwargs.get("model") or _DEFAULT_MODEL + sagemaker_endpoint = endpoint_name(kwargs.get("sagemaker_endpoint")) + sagemaker_region = region_name(kwargs.get("sagemaker_region")) + model = kwargs.get("model") or ( + "aura-2-asteria-en" if sagemaker_endpoint else _DEFAULT_MODEL + ) encoding = kwargs.get("encoding") container = kwargs.get("container") sample_rate = kwargs.get("sample_rate") @@ -526,6 +567,11 @@ def handle( # --list-voices: discover voices and exit before any TTS generation. if list_voices: + if sagemaker_endpoint: + raise click.ClickException( + "--list-voices is not available through SageMaker. SageMaker " + "endpoints serve one deployed Aura model." + ) return self._list_voices(client) # Resolve text input: arg > --file > stdin @@ -569,7 +615,7 @@ def handle( # A player that cannot decode what we are about to request should say # so now, before the API call, rather than emit silence or a decoder # error after synthesis. - if player is not None: + if player is not None and not sagemaker_endpoint: unplayable = _check_playable( player, is_flux=is_flux, encoding=encoding, container=container ) @@ -605,6 +651,38 @@ def handle( "so stdout stays empty. Add -o to save a file too." ) + if sagemaker_endpoint: + if is_flux: + raise click.ClickException( + "SageMaker supports Aura Speak v1 streaming only. Flux Speak " + "v2 models must use the Deepgram Cloud transport." + ) + if not model.lower().startswith("aura-"): + raise click.ClickException( + f"SageMaker Speak supports Aura models only (got '{model}')." + ) + if container: + raise click.ClickException( + "--container is not available for SageMaker Aura streaming. " + "The transport returns audio chunks rather than a REST container." + ) + sagemaker_encoding = encoding or "linear16" + if sagemaker_encoding != "linear16": + raise click.ClickException( + "SageMaker Aura streaming currently supports linear16 output " + "only. Raw companded audio cannot be played safely." + ) + return self._speak_sagemaker( + endpoint=sagemaker_endpoint, + region=sagemaker_region, + text=text, + model=model, + encoding=sagemaker_encoding, + sample_rate=sample_rate or 24000.0, + output_path=output_path, + player=player, + ) + if is_flux: # WebSocket streaming path. Streaming output is raw audio, so # default to linear16 @ 24kHz and wrap it in WAV for playback. @@ -910,6 +988,156 @@ def handle( # "flux" typo) route here instead of the raising v2 path. raise click.ClickException(f"Error generating speech: {e}") + def _speak_sagemaker( + self, + *, + endpoint: str, + region: str, + text: str, + model: str, + encoding: str, + sample_rate: float, + output_path: str | None, + player: str | None, + ) -> SpeakResult | None: + """Generate Aura audio via the SageMaker HTTP/2 streaming transport.""" + raw_audio = bytearray() + stdout_buffer = None if output_path or player else sys.stdout.buffer + wrote_header = False + total = 0 + + with _sagemaker_output_file(output_path) as output_file: + + def receive(chunk: bytes) -> None: + nonlocal wrote_header, total + if not chunk: + return + total += len(chunk) + if player: + raw_audio.extend(chunk) + sink = output_file or stdout_buffer + if sink is None: + return + if encoding == "linear16" and not wrote_header: + sink.write(_streaming_wav_header(sample_rate=int(sample_rate))) + wrote_header = True + sink.write(chunk) + if stdout_buffer is not None: + sink.flush() + + try: + asyncio.run( + self._sagemaker_speak_stream( + endpoint=endpoint, + region=region, + text=text, + model=model, + encoding=encoding, + sample_rate=sample_rate, + receive=receive, + ) + ) + except SageMakerTransportError as exc: + raise click.ClickException(str(exc)) from exc + + if total == 0: + raise click.ClickException("SageMaker Aura streaming returned no audio.") + + if player: + audio = bytes(raw_audio) + if encoding == "linear16": + audio = _pcm_to_wav(audio, sample_rate=int(sample_rate)) + suffix = _play_suffix( + is_flux=False, + encoding=encoding, + container="wav" if encoding == "linear16" else "none", + ) + console.print(f"[blue]Playing audio ({player})...[/blue]") + _play_audio(player, audio, suffix=suffix) + + if output_path: + size = Path(output_path).stat().st_size + console.print( + f"[green]Audio saved to {output_path}[/green] ({size:,} bytes)" + ) + return SpeakResult( + status="success", + message=f"Audio saved to {output_path}", + output_path=output_path, + model=model, + bytes_written=size, + played=bool(player), + ) + + if player: + console.print(f"[green]✓ Played {total:,} bytes[/green]") + return SpeakResult( + status="success", + message=f"Played {total:,} bytes", + model=model, + bytes_written=total, + played=True, + ) + + console.print(f"[green]✓ Streamed {total:,} bytes to stdout[/green]") + # Audio already occupies stdout; returning a structured result would + # append JSON/YAML/CSV bytes and corrupt the stream. + return None + + async def _sagemaker_speak_stream( + self, + *, + endpoint: str, + region: str, + text: str, + model: str, + encoding: str, + sample_rate: float, + receive: Any, + ) -> None: + """Drive Speak v1's streaming connection and pass audio chunks to ``receive``.""" + from deepgram.core.events import EventType + from deepgram.speak.v1.types import SpeakV1Close, SpeakV1Flush, SpeakV1Text + + client = create_sagemaker_client(endpoint, region) + errors: list[str] = [] + finished = asyncio.Event() + + def on_message(message: Any) -> None: + if isinstance(message, (bytes, bytearray)): + receive(bytes(message)) + + def on_error(error: Any) -> None: + errors.append(str(error)) + finished.set() + + def on_close(_: Any) -> None: + finished.set() + + async with client.speak.v1.connect( + model=model, + encoding=encoding, + sample_rate=str(int(sample_rate)), + ) as connection: + connection.on(EventType.MESSAGE, on_message) + connection.on(EventType.ERROR, on_error) + connection.on(EventType.CLOSE, on_close) + listen_task = asyncio.create_task(connection.start_listening()) + try: + await asyncio.sleep(0) + await connection.send_text(SpeakV1Text(type="Speak", text=text)) + await connection.send_flush(SpeakV1Flush(type="Flush")) + await connection.send_close(SpeakV1Close(type="Close")) + await asyncio.wait_for(finished.wait(), timeout=30) + finally: + if not listen_task.done(): + listen_task.cancel() + with suppress(asyncio.CancelledError): + await listen_task + + if errors: + raise click.ClickException(f"SageMaker Speak error: {errors[-1]}") + def _list_voices(self, client: DeepgramClient) -> BaseResult: """List available TTS voices as a table (mirrors `dg models`).""" try: diff --git a/packages/deepctl-cmd-speak/tests/unit/test_speak_command.py b/packages/deepctl-cmd-speak/tests/unit/test_speak_command.py index df476f88..1ddfcd63 100644 --- a/packages/deepctl-cmd-speak/tests/unit/test_speak_command.py +++ b/packages/deepctl-cmd-speak/tests/unit/test_speak_command.py @@ -416,6 +416,58 @@ def test_handle_default_model_routes_to_flux( mock_client.speak_text.assert_not_called() assert output_file.read_bytes()[:4] == b"RIFF" + @patch("deepctl_cmd_speak.command.sys") + def test_sagemaker_aura_uses_v1_streaming_transport( + self, mock_sys, command, mock_config, mock_auth_manager, mock_client, tmp_path + ): + mock_sys.stdin.isatty.return_value = True + mock_sys.stdout.isatty.return_value = True + output = tmp_path / "aura.wav" + expected = SpeakResult(status="success", output_path=str(output)) + with patch.object(command, "_speak_sagemaker", return_value=expected) as stream: + result = command.handle( + config=mock_config, + auth_manager=mock_auth_manager, + client=mock_client, + text="Hello from SageMaker", + output=str(output), + model="aura-2-asteria-en", + encoding=None, + container=None, + sample_rate=None, + file=None, + sagemaker_endpoint="aura-endpoint", + ) + + assert result is expected + assert stream.call_args.kwargs["endpoint"] == "aura-endpoint" + assert stream.call_args.kwargs["encoding"] == "linear16" + mock_client.speak_text.assert_not_called() + + @patch("deepctl_cmd_speak.command.sys") + def test_sagemaker_flux_is_rejected( + self, mock_sys, command, mock_config, mock_auth_manager, mock_client, tmp_path + ): + mock_sys.stdin.isatty.return_value = True + mock_sys.stdout.isatty.return_value = True + with pytest.raises(click.ClickException, match="Aura Speak v1"): + command.handle( + config=mock_config, + auth_manager=mock_auth_manager, + client=mock_client, + text="Hello", + output=str(tmp_path / "out.wav"), + model="flux-alexis-en", + encoding=None, + container=None, + sample_rate=None, + file=None, + sagemaker_endpoint="tts-endpoint", + ) + + def test_sagemaker_uses_aws_auth(self, command): + assert command.uses_external_auth(sagemaker_endpoint="tts-endpoint") + @patch("deepctl_cmd_speak.command.sys") def test_handle_flux_streams_and_wraps_wav( self, diff --git a/packages/deepctl-core/pyproject.toml b/packages/deepctl-core/pyproject.toml index d785e431..0afbdb02 100644 --- a/packages/deepctl-core/pyproject.toml +++ b/packages/deepctl-core/pyproject.toml @@ -35,6 +35,10 @@ dependencies = [ ] [project.optional-dependencies] +sagemaker = [ + "deepgram-sdk>=7.8.1,<8", + "deepgram-sagemaker>=0.4.0; python_version >= '3.12'", +] dev = [ "pytest>=7.0.0", "pytest-asyncio>=0.21.0", diff --git a/packages/deepctl-core/src/deepctl_core/base_command.py b/packages/deepctl-core/src/deepctl_core/base_command.py index abe57fd3..94bd4626 100644 --- a/packages/deepctl-core/src/deepctl_core/base_command.py +++ b/packages/deepctl-core/src/deepctl_core/base_command.py @@ -89,7 +89,7 @@ def execute(self, ctx: click.Context, **kwargs: Any) -> None: client = DeepgramClient(config, auth_manager) # Check authentication if required - if self.requires_auth: + if self.requires_auth and not self.uses_external_auth(**kwargs): with TimingContext("authentication_check"): try: auth_manager.guard() @@ -207,7 +207,7 @@ def execute(self, ctx: click.Context, **kwargs: Any) -> None: if exit_code: raise SystemExit(exit_code) - def exit_code_for(self, result: Any) -> int: + def exit_code_for(self, result: Any) -> int: """Map a command result to a process exit code. Args: @@ -217,7 +217,11 @@ def exit_code_for(self, result: Any) -> int: Exit code for this result's status; 0 when it reported no failure """ status = str(getattr(result, "status", "") or "") - return self.EXIT_CODES.get(status, 0) + return self.EXIT_CODES.get(status, 0) + + def uses_external_auth(self, **kwargs: Any) -> bool: + """Whether this invocation uses a transport with its own credentials.""" + return False def is_guided(self, ctx: click.Context) -> bool: """True only when the user invoked this command with no input at all. diff --git a/packages/deepctl-core/src/deepctl_core/sagemaker.py b/packages/deepctl-core/src/deepctl_core/sagemaker.py new file mode 100644 index 00000000..0f03af2e --- /dev/null +++ b/packages/deepctl-core/src/deepctl_core/sagemaker.py @@ -0,0 +1,56 @@ +"""Optional SageMaker transport support. + +The published ``deepgram-sagemaker`` package is Python 3.12+ and async-only. +Keep its imports here so ordinary CLI commands neither require AWS packages nor +fail to import on Python 3.10 and 3.11. +""" + +from __future__ import annotations + +import os +import sys +from typing import Any + + +class SageMakerTransportError(RuntimeError): + """The optional SageMaker streaming transport cannot be used.""" + + +def endpoint_name(value: str | None) -> str | None: + """Resolve an explicit endpoint, then the standard SageMaker environment.""" + return ( + value + or os.getenv("DG_SAGEMAKER_ENDPOINT") + or os.getenv("SAGEMAKER_ENDPOINT_NAME") + or os.getenv("SAGEMAKER_ENDPOINT") + ) + + +def region_name(value: str | None) -> str: + """Resolve an explicit region, then the AWS default used by the transport.""" + return value or os.getenv("AWS_REGION") or "us-west-2" + + +def create_sagemaker_client(endpoint: str, region: str) -> Any: + """Create the async SDK client configured for SageMaker HTTP/2 streaming.""" + if sys.version_info < (3, 12): + raise SageMakerTransportError( + "SageMaker streaming requires Python 3.12+ because " + "deepgram-sagemaker depends on AWS CRT." + ) + + try: + from deepgram import AsyncDeepgramClient + from deepgram_sagemaker import ( # type: ignore[import-not-found] + SageMakerTransportFactory, + ) + except ImportError as exc: + raise SageMakerTransportError( + "SageMaker streaming requires the optional transport. Install it with " + "`pip install 'deepctl[sagemaker]'` (or install deepgram-sagemaker)." + ) from exc + + factory = SageMakerTransportFactory(endpoint_name=endpoint, region=region) + # SageMaker signs requests with the normal AWS credential chain. The SDK + # constructor still requires this value, but the transport never sends it. + return AsyncDeepgramClient(api_key="unused", transport_factory=factory) diff --git a/packages/deepctl-core/tests/unit/test_base.py b/packages/deepctl-core/tests/unit/test_base.py index d494e0bc..2a647a5e 100644 --- a/packages/deepctl-core/tests/unit/test_base.py +++ b/packages/deepctl-core/tests/unit/test_base.py @@ -222,6 +222,42 @@ def handle( {"authenticated": True}, mock_context.obj["config"] ) + @pytest.mark.unit + @patch("deepctl_core.base_command.AuthManager") + @patch("deepctl_core.base_command.DeepgramClient") + def test_execute_external_auth_bypasses_deepgram_guard( + self, mock_client_class, mock_auth_class, mock_context + ): + """AWS-backed commands must not require an unrelated Deepgram API key.""" + + class ExternalAuthCommand(BaseCommand): + name = "external_auth_test" + help = "Test external auth" + requires_auth = True + + def uses_external_auth(self, **kwargs: Any) -> bool: + return bool(kwargs.get("external")) + + def handle( + self, + config: Config, + auth_manager: AuthManager, + client: DeepgramClient, + **kwargs: Any, + ) -> Any: + return {"external": True} + + auth_manager = Mock() + mock_auth_class.return_value = auth_manager + command = ExternalAuthCommand() + with patch.object(command, "output_result") as mock_output: + command.execute(mock_context, external=True) + + auth_manager.guard.assert_not_called() + mock_output.assert_called_once_with( + {"external": True}, mock_context.obj["config"] + ) + @pytest.mark.unit @patch("deepctl_core.base_command.AuthManager") @patch("deepctl_core.base_command.DeepgramClient") diff --git a/pyproject.toml b/pyproject.toml index 42daee2b..bb2d073f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,6 +76,10 @@ dependencies = [ ] [project.optional-dependencies] +sagemaker = [ + "deepgram-sdk>=7.8.1,<8", + "deepgram-sagemaker>=0.4.0; python_version >= '3.12'", +] dev = [ # Testing "pytest>=7.0.0", diff --git a/uv.lock b/uv.lock index f9a1de00..ed79d1a0 100644 --- a/uv.lock +++ b/uv.lock @@ -317,6 +317,75 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] +[[package]] +name = "aws-sdk-sagemaker-runtime-http2" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smithy-aws-core", extra = ["eventstream", "json"], marker = "python_full_version >= '3.12'" }, + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, + { name = "smithy-http", extra = ["aiohttp"], marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/66/bae826afa0e32dc0fce834152f2d09e8d9401be49fc888a1dcbb7d2b1563/aws_sdk_sagemaker_runtime_http2-0.11.0.tar.gz", hash = "sha256:d0ae84455007530834a72a4e98700f3e4d60c298935485c7b8b969755d172687", size = 29816, upload-time = "2026-08-24T21:17:28.586Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/c0/d936d943333a66d56a1a58cdb51d81f5f732e4a815d774ba7aa7e0027a28/aws_sdk_sagemaker_runtime_http2-0.11.0-py3-none-any.whl", hash = "sha256:073b5d890fcf00bf47c154bb6255652e07d526c9227703a60f0ee2f61e21e6cb", size = 23875, upload-time = "2026-08-24T21:17:29.307Z" }, +] + +[package.optional-dependencies] +awscrt = [ + { name = "smithy-http", extra = ["awscrt"], marker = "python_full_version >= '3.12'" }, +] + +[[package]] +name = "aws-sdk-signers" +version = "0.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/ed/3bc0697ea9c4fed38d3742f373e2a3bb77937f9e1f9b6ef08309e326f195/aws_sdk_signers-0.3.1.tar.gz", hash = "sha256:1f305b753968c98f1cd4bdc498f52a1b7dbedc8d7a07cb3631170de2e572e9a9", size = 18675, upload-time = "2026-08-24T21:16:54.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/51/0a3e4314715f80956ea73e3b02b7d3bccdbc9d96950000ed21cbea5ef15a/aws_sdk_signers-0.3.1-py3-none-any.whl", hash = "sha256:a2d26c085f36e8069a8b499a55ac4dff68c8d2cceafe31797c977fd2c5c73579", size = 21934, upload-time = "2026-08-24T21:16:55.512Z" }, +] + +[[package]] +name = "awscrt" +version = "0.32.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/92/cb/980fe60c4209af71d036276217f8b9f372f958e290c15d2849a3de4dcd23/awscrt-0.32.2.tar.gz", hash = "sha256:a4f48805e8a66237923f03b7b692d213994cff42d1ff08125d1d60c74fcaf872", size = 36862073, upload-time = "2026-04-24T22:59:55.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/93/08c8dde6e10c2aa5e50c08730e7913fd68cc861536260115bdf109f20bb8/awscrt-0.32.2-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:a9ed98e20ca6fcad8ef32e3c6779aaa3526a5ff3f0aa99d59e0deeff59640375", size = 3405893, upload-time = "2026-04-24T22:58:34.276Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d8/f838299003df9c77b9d582411f009bf2e5bc07fc68566685b434de249755/awscrt-0.32.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:acb707df280079d21d20ad1735b38a80d4939133cbaecfc2e4927dd8fc0190bc", size = 3946562, upload-time = "2026-04-24T22:58:37.467Z" }, + { url = "https://files.pythonhosted.org/packages/34/95/a9d9ff694e15550a1fa2f83ac9b3b50cc5f809d66dacc57af4d84cc01c7f/awscrt-0.32.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab06479bcdcb42b2956f59c0e4138049e5b44c885b7584ea05e39cc4d71b1f99", size = 4236332, upload-time = "2026-04-24T22:58:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/24/bd/92fcf514966e7a139b146282396608493b99e25f745b332075ebe18e129b/awscrt-0.32.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2792fc2877335673058d0b4e8249ef73dc36b22689fda939506aea6eceb42054", size = 3883087, upload-time = "2026-04-24T22:58:40.292Z" }, + { url = "https://files.pythonhosted.org/packages/5c/7d/cf7ec60d4997ee966ca003a3c7bfc556ee34db10f230f03e5608edf022ca/awscrt-0.32.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0d2e8a13063a3d9b61eee0e2885d133a75b38de3600f6b62437d8559f7d664e3", size = 4118974, upload-time = "2026-04-24T22:58:41.913Z" }, + { url = "https://files.pythonhosted.org/packages/65/bb/769f8923d7d7986e762898fa98a9d16130681a47da2ff668e6fb07bae0b4/awscrt-0.32.2-cp310-cp310-win32.whl", hash = "sha256:4e975223f3af4faa581c733f0fdd316514b987c42ce85ef3bcd6d9c02eb48f77", size = 4067395, upload-time = "2026-04-24T22:58:43.308Z" }, + { url = "https://files.pythonhosted.org/packages/83/42/17ab1d701f2628adf7b1b993cae9993111ed760aaf08f5772c2bf37c6b7a/awscrt-0.32.2-cp310-cp310-win_amd64.whl", hash = "sha256:a13c0a555bd930c829c72e6b2b2df70442b3b414037fd488984495b5beff5ddc", size = 4224532, upload-time = "2026-04-24T22:58:45.039Z" }, + { url = "https://files.pythonhosted.org/packages/03/99/a6c712a2f638c766b0879da0eacd9fe5695c64deb89c0357bcc4f1f4df9b/awscrt-0.32.2-cp311-abi3-macosx_10_15_universal2.whl", hash = "sha256:32785f54d0786e07b6491b51f9c2f75ea9e17decd39bb6b66fdc60cd871a49ef", size = 3406247, upload-time = "2026-04-24T22:58:46.268Z" }, + { url = "https://files.pythonhosted.org/packages/a7/03/665c81f3b9d3c56fcdfb8f353c22501bc538d192c431cfaaf307f867d404/awscrt-0.32.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f8a586c52f41ff14c2f1b8afeb764e231ad3d66acfd42a6b9fb6c8afd8da8fe2", size = 3906890, upload-time = "2026-04-24T22:58:47.96Z" }, + { url = "https://files.pythonhosted.org/packages/31/ad/5db0691a0c72d55a17c03c47149cf15d4602b83b470355c322c9a7f115e8/awscrt-0.32.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ef1664588d35dcad2115377120667e689cd7a517da52a481373c9536811ed96", size = 4196631, upload-time = "2026-04-24T22:58:49.244Z" }, + { url = "https://files.pythonhosted.org/packages/9a/66/63a4654b2996158f33e88d74d79178a5812d94a7e0d6c94ec475115dff18/awscrt-0.32.2-cp311-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7ed7c209136650fe25659436bb4150e5af6eb43d71a0bf294f0bf414428736ee", size = 3818090, upload-time = "2026-04-24T22:58:50.657Z" }, + { url = "https://files.pythonhosted.org/packages/cd/26/b8fee227918465830a0bbba48f54ebf0a5029c3a7d11d4fa973838a79262/awscrt-0.32.2-cp311-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cfd437122d5a2ec7eb9fffaf2cd8b96543d4a0d7e906b9515b79672005a1607a", size = 4056453, upload-time = "2026-04-24T22:58:52.373Z" }, + { url = "https://files.pythonhosted.org/packages/e3/26/f5b9e9677bf90d1840d4db1513ba92e73601ef82e61a16e747a83493d35c/awscrt-0.32.2-cp311-abi3-win32.whl", hash = "sha256:5197d1e4e780d755632f79f8d32a09a30a9101cccb51ca1694fe25c711b2e801", size = 4066027, upload-time = "2026-04-24T22:58:53.752Z" }, + { url = "https://files.pythonhosted.org/packages/57/0b/fd1798551ecdc8a28d61ecdeda248f99faa3457f83aefa10ab797f466889/awscrt-0.32.2-cp311-abi3-win_amd64.whl", hash = "sha256:80420aa19c074a4c0335f2bd0e4aee3381fa452328d937795a1e0c779f0c052d", size = 4221984, upload-time = "2026-04-24T22:58:55.115Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a3/075e29b39945f398adfb5d5ee4d533e00d37c0f5c68d79b250a1bdb087ba/awscrt-0.32.2-cp313-abi3-macosx_10_15_universal2.whl", hash = "sha256:d2f7aee3bce261ab1ceba1fac404de4d496aa866237161d4257cef92bff9d828", size = 3404901, upload-time = "2026-04-24T22:58:56.492Z" }, + { url = "https://files.pythonhosted.org/packages/e3/41/1c4783b32bf4ec7383156787570ea1221c95c037c2c0f11cbc9e9529ba48/awscrt-0.32.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff0fff9c2b613d7fabc298b0fd81f0d7056353f3d20271a852a719c5b2f7ccf4", size = 3897627, upload-time = "2026-04-24T22:58:58.144Z" }, + { url = "https://files.pythonhosted.org/packages/af/2d/5736128847ff4a877d50720ea7a48d7e50a56b78741919e4b6ffabffb1ad/awscrt-0.32.2-cp313-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:79bb11d5d1dfdcfd867aac4a026bee11afbd2154279e12b66588442d8c14bdf7", size = 4189579, upload-time = "2026-04-24T22:58:59.56Z" }, + { url = "https://files.pythonhosted.org/packages/90/f5/064b23d4c927e9b63725ee60c88edb75a1e8f5fd1e97d56d4d6a63fe954b/awscrt-0.32.2-cp313-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:47330f421948207a122092042f235cf82d48fa145c446ff4db12cc8cd3a418b6", size = 3809647, upload-time = "2026-04-24T22:59:00.884Z" }, + { url = "https://files.pythonhosted.org/packages/f5/36/f14ea531cccb6ff85c4d50c9e79e61030675520a393b4af23685d24ea018/awscrt-0.32.2-cp313-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5ec4d8200eb0158b60e35fbb65faa96ef1eb7763f6ce1192827886ee24c6df99", size = 4051532, upload-time = "2026-04-24T22:59:02.593Z" }, + { url = "https://files.pythonhosted.org/packages/64/aa/b12e721c8148a1bbc58a22cbd204d88d0a6263331049d40c057f9dcd3e11/awscrt-0.32.2-cp313-abi3-win32.whl", hash = "sha256:a81f30a501d2eb6ba52c769cd6ecb3f7005512fba4a533211dc717e1115b0d94", size = 4062484, upload-time = "2026-04-24T22:59:04.251Z" }, + { url = "https://files.pythonhosted.org/packages/5c/26/9f5f23647465a4fe1b28afce334e54a4264e23b69365024c28955f0c4119/awscrt-0.32.2-cp313-abi3-win_amd64.whl", hash = "sha256:8d731edda20dce5afc15a04731d91136a31779a244672d1f0a292a8b04aa0fd3", size = 4220425, upload-time = "2026-04-24T22:59:05.627Z" }, + { url = "https://files.pythonhosted.org/packages/dd/76/18077410c1d7b524a7a7486550bc969218f6575288f66e285157dc78e143/awscrt-0.32.2-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:f2a085d0dd4eef974f2ae5467ae3717d2fc08dd8cd508c4fd7a5acb658c68616", size = 3414715, upload-time = "2026-04-24T22:59:07.01Z" }, + { url = "https://files.pythonhosted.org/packages/d3/12/465d72ea6afffc7829f7f48f408a707d4dab9e3f2b694f4e0e63e011478a/awscrt-0.32.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d8a4e52f7312e5e435461119aa903f6424e9996d93a040101fb1eb7b9c4e58dc", size = 4028634, upload-time = "2026-04-24T22:59:08.303Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ee/2e9d3716cf6a24f30b85af24691d473c913c119dc996bcd8c9b2b3fe2c17/awscrt-0.32.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6cb3c858e96ba023de691a3b6478bed9fb59085433042dff78c42e59eed19cf2", size = 4311846, upload-time = "2026-04-24T22:59:10.02Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ce/aa08aad92e48929cfe243ed7da5cf039fbb137c391e84af79e88c848a37a/awscrt-0.32.2-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:7f92b8f40a104a2a87ea5f428b3799220666ec1450b3a90665867d3715749e91", size = 3952242, upload-time = "2026-04-24T22:59:11.632Z" }, + { url = "https://files.pythonhosted.org/packages/42/e3/09ad98aa7dae9cd3ad578c7721b64e9da03f9a5ed444e518c63e82db05a9/awscrt-0.32.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:9fd2dbca02f4e22fb5c02d1505327e6e6e9320dbe8ca80fc033cbbb29ed8631a", size = 4187982, upload-time = "2026-04-24T22:59:14.477Z" }, + { url = "https://files.pythonhosted.org/packages/26/be/897b1ad519d83a837d721f4e8a87d98e6f36e5b985fa697f3ffed512a8eb/awscrt-0.32.2-cp313-cp313t-win32.whl", hash = "sha256:023a2f4595804a0f1d61ab49b64dda5612be9bfbe9b13759331e8e31658dda3f", size = 4111958, upload-time = "2026-04-24T22:59:15.857Z" }, + { url = "https://files.pythonhosted.org/packages/71/54/6cca298e8acb6769c8078b39bedbc507f17a3f7fe6ea768d8256d584d4ed/awscrt-0.32.2-cp313-cp313t-win_amd64.whl", hash = "sha256:a2f513f0fb3aafdf7cdf29d7f6f0c46bf4cc7880380c86e88635b7818565e76d", size = 4271679, upload-time = "2026-04-24T22:59:17.425Z" }, + { url = "https://files.pythonhosted.org/packages/c7/09/51fca333d42b53ddb04881f53e3cc0f4462872ac81426fbb34f5d0b1d1fb/awscrt-0.32.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7404cb551046bbe7cd454ea75f88b4a1b26f018d1b9bea83dbe46c174789d835", size = 3414716, upload-time = "2026-04-24T22:59:18.918Z" }, + { url = "https://files.pythonhosted.org/packages/39/3a/0ae52a8dfb0e3c37f0129232d79307c65e6ee1ea13a3cbdcf301aaad0a6a/awscrt-0.32.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:51d6132e9d70de40da07dfad5f17780a652dd4b351c35ca97c79d0fa0186d645", size = 4028980, upload-time = "2026-04-24T22:59:20.316Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7f/7f52020bfcf29122cad77e936d82abaa1f6857a5a70453e8cc734119f0e2/awscrt-0.32.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:193cc3ecf03a1dd6f989853b6c21549a0a9750c855520fedc8c3c8fbcd32e1bc", size = 4312564, upload-time = "2026-04-24T22:59:21.851Z" }, + { url = "https://files.pythonhosted.org/packages/25/86/2c9b5479e08b8198459d2a781df5524e45d4d0f9c6329bad26979a4d1e85/awscrt-0.32.2-cp314-cp314t-win32.whl", hash = "sha256:25c7e7e6535cb2d2a4d22fd6264f621672d3903491318364dbc59066b63c7186", size = 4195784, upload-time = "2026-04-24T22:59:23.769Z" }, + { url = "https://files.pythonhosted.org/packages/ab/85/a12515514de8969b9e7fa40bb782501a336a8a985cc3093309120a80b627/awscrt-0.32.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a6782c19a00f354c7b232b675f09cde94d1ca37bcf29009b8779b3f6395b27b4", size = 4366435, upload-time = "2026-04-24T22:59:25.53Z" }, +] + [[package]] name = "backports-asyncio-runner" version = "1.2.0" @@ -335,6 +404,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" }, ] +[[package]] +name = "boto3" +version = "1.43.114" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore", marker = "python_full_version >= '3.12'" }, + { name = "jmespath", marker = "python_full_version >= '3.12'" }, + { name = "s3transfer", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/8c/f6f884dc947789317e73ed6fce85e18580d22e9f90e48d67c2367b02667e/boto3-1.43.114.tar.gz", hash = "sha256:be704857751564a5cf69c5bbaadbfa01c22806409815c73563db42fbffe583a2", size = 112653, upload-time = "2026-10-14T19:24:22.561Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f8/0799a101e6f65c8b687f50c218654cef1e44658e946c7d33d362e2572621/boto3-1.43.114-py3-none-any.whl", hash = "sha256:d9cac2eb921ce674970cef1c9ad750f85ee3a846aedcf188d18368fb9eb6da23", size = 140043, upload-time = "2026-10-14T19:24:21.038Z" }, +] + +[[package]] +name = "botocore" +version = "1.43.114" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath", marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil", marker = "python_full_version >= '3.12'" }, + { name = "urllib3", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/c8/b508359d1f3846a918c06807a9ae27eee063f904559269e42ccde9de09ea/botocore-1.43.114.tar.gz", hash = "sha256:f366fa4db518775632ad1eb128cd8203ca46396cecf37209d904f0bbc049ce90", size = 16369844, upload-time = "2026-10-14T19:24:17.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/41/7c6fa7ac5fcfd5ea3c6f32aab001942da32b184a210f39042778cb1ad8ed/botocore-1.43.114-py3-none-any.whl", hash = "sha256:d1c441a22e93e158de5b1e026205f5d6d67a4545d10540c5090c62dccb3a9eca", size = 16067885, upload-time = "2026-10-14T19:24:14.629Z" }, +] + [[package]] name = "build" version = "1.5.0" @@ -917,6 +1014,10 @@ dev = [ { name = "types-pyyaml" }, { name = "types-requests" }, ] +sagemaker = [ + { name = "deepgram-sagemaker", marker = "python_full_version >= '3.12'" }, + { name = "deepgram-sdk" }, +] [package.dev-dependencies] dev = [ @@ -981,7 +1082,9 @@ requires-dist = [ { name = "deepctl-core", editable = "packages/deepctl-core" }, { name = "deepctl-shared-utils", editable = "packages/deepctl-shared-utils" }, { name = "deepctl-telemetry", editable = "packages/deepctl-telemetry" }, + { name = "deepgram-sagemaker", marker = "python_full_version >= '3.12' and extra == 'sagemaker'", specifier = ">=0.4.0" }, { name = "deepgram-sdk", specifier = ">=7.7.0,<8" }, + { name = "deepgram-sdk", marker = "extra == 'sagemaker'", specifier = ">=7.8.1,<8" }, { name = "httpx", specifier = ">=0.24.0" }, { name = "keyring", specifier = ">=24.0.0" }, { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" }, @@ -1005,7 +1108,7 @@ requires-dist = [ { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.0" }, { name = "types-requests", marker = "extra == 'dev'", specifier = ">=2.31.0" }, ] -provides-extras = ["dev"] +provides-extras = ["sagemaker", "dev"] [package.metadata.requires-dev] dev = [ @@ -1654,11 +1757,17 @@ dev = [ { name = "pytest-cov" }, { name = "pytest-mock" }, ] +sagemaker = [ + { name = "deepgram-sagemaker", marker = "python_full_version >= '3.12'" }, + { name = "deepgram-sdk" }, +] [package.metadata] requires-dist = [ { name = "click", specifier = ">=8.0.0" }, + { name = "deepgram-sagemaker", marker = "python_full_version >= '3.12' and extra == 'sagemaker'", specifier = ">=0.4.0" }, { name = "deepgram-sdk", specifier = ">=7.7.0,<8" }, + { name = "deepgram-sdk", marker = "extra == 'sagemaker'", specifier = ">=7.8.1,<8" }, { name = "httpx", specifier = ">=0.24.0" }, { name = "keyring", specifier = ">=24.0.0" }, { name = "packaging", specifier = ">=21.0" }, @@ -1672,7 +1781,7 @@ requires-dist = [ { name = "pyyaml", specifier = ">=6.0.0" }, { name = "rich", specifier = ">=13.0.0" }, ] -provides-extras = ["dev"] +provides-extras = ["sagemaker", "dev"] [[package]] name = "deepctl-plugin-example" @@ -1753,9 +1862,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/f1/aeeb3fe25c34d7d23c052bfec9a98f7410bf2b4771de5038f0e94dac6e77/deepgram_mcp-0.1.1-py3-none-any.whl", hash = "sha256:eff4904eaee6d424c5201f949e308bc29bd2c2621c74ce38839e2ba180dc4d9a", size = 6828, upload-time = "2026-04-04T12:07:13.276Z" }, ] +[[package]] +name = "deepgram-sagemaker" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aws-sdk-sagemaker-runtime-http2", extra = ["awscrt"], marker = "python_full_version >= '3.12'" }, + { name = "boto3", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/7e/bc4daead7f4ad62095905b12610ca6ca004c97fa509f7fe287b8162310e0/deepgram_sagemaker-0.4.0.tar.gz", hash = "sha256:08ea0f6b82e2b720eafba0aef22d2f57abfdb621de80d0bd01b07a3457301b72", size = 16052, upload-time = "2026-09-18T12:09:03.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/11/416262c25f09806474888fe87ec74622c241513fb63ce85e7eb0901fa7b2/deepgram_sagemaker-0.4.0-py3-none-any.whl", hash = "sha256:21d4f176c420098e3dfbbc2e2bb6e94d806e971cdc975b5c534e57ac2deb5ed9", size = 16116, upload-time = "2026-09-18T12:09:01.97Z" }, +] + [[package]] name = "deepgram-sdk" -version = "7.7.0" +version = "7.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -1765,9 +1887,9 @@ dependencies = [ { name = "websockets", version = "16.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "websockets", version = "17.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/62/35862ebd6dc3873d7f67319afb775143b86fbf0fa1b1ab6697249325bfd4/deepgram_sdk-7.7.0.tar.gz", hash = "sha256:4cce45c853d6caebbed62081d9118b915a2f3a2e54eb420ff5148c30e16dcffd", size = 242704, upload-time = "2026-08-12T13:26:28.154Z" } +sdist = { url = "https://files.pythonhosted.org/packages/20/32/02856581cc8c50c48392530b2a6fe52e052218e0581ba3bf4983efe70da8/deepgram_sdk-7.13.0.tar.gz", hash = "sha256:e7e5a3bcf261cbd1f2ee8987adcdd21acce6268bf5904dc943fefe4b9577a8ef", size = 252676, upload-time = "2026-10-12T08:49:45.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fa/7db3da57efe9b7919d57c4aac0eb9cbdf2ee40ed766316aada60cce89c03/deepgram_sdk-7.7.0-py3-none-any.whl", hash = "sha256:25355b2e119f969c8614136e494ef30be91a06c56152a9cba49535ef3f18f82c", size = 633789, upload-time = "2026-08-12T13:26:26.738Z" }, + { url = "https://files.pythonhosted.org/packages/0a/27/a1f6c03d9243f996bf67ae872c796c49fcfb066dd3c2338b5d3b68f9c6ea/deepgram_sdk-7.13.0-py3-none-any.whl", hash = "sha256:dff3ed7929bef4c2dc6d87003d8fa7994501e52a41212416b4a81258da76f415", size = 660444, upload-time = "2026-10-12T08:49:43.944Z" }, ] [[package]] @@ -1793,7 +1915,7 @@ name = "exceptiongroup" version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } wheels = [ @@ -2027,12 +2149,122 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] +[[package]] +name = "ijson" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/75/61/4066af787ed25bfca02c3edd2d7fd489b1b5ca27b54b400b187e5f2865e7/ijson-3.6.0.tar.gz", hash = "sha256:ec8f9265524e724905ecf00bdd061c374baaa8d5045ef50425695fb06efb45f5", size = 70134, upload-time = "2026-10-12T20:40:00.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/8c/d90e8b945244f6e95439176b953d15188dd5f89383d51a4cdb58e6b99baa/ijson-3.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b207ffd091f4f0cac14d283529fd40e974510bf5152b00d2efcb2975e599581b", size = 89106, upload-time = "2026-10-12T20:38:12.922Z" }, + { url = "https://files.pythonhosted.org/packages/b9/12/9cf171e6533ca6d207789fd3da836d792991165fed47c274920757edfc1d/ijson-3.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:42241cac70f9a0d690dcab88f7ab83ab479ddeee0b56b4120a104119622f01fa", size = 60730, upload-time = "2026-10-12T20:38:14.045Z" }, + { url = "https://files.pythonhosted.org/packages/a5/27/f9acea61d4ce4e3abbbd589416a041f80ead87ac302e33d111a6d7d354d0/ijson-3.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07a8430200f6afa9562cc51fad77dc77ecaf28a75c112504a3d74172ee9a0346", size = 60792, upload-time = "2026-10-12T20:38:14.885Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b1/9366615b20dae1e4ebab5d147712a33b0aa4ed53e2c0d2cbb6b9ba436230/ijson-3.6.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:616156831be7f2eb37ba8e338b2182b3e54e09b0d21827c05c159c94df0b54fc", size = 126925, upload-time = "2026-10-12T20:38:15.937Z" }, + { url = "https://files.pythonhosted.org/packages/5b/90/0fc29e6d68bb425e75b96bfcbdc295cd09d40fb15964a7077a68b7ad5265/ijson-3.6.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a3372a9565265ea7808c044d6f04ea2db4ca29db00bf1121da44c9dde88ac52", size = 134667, upload-time = "2026-10-12T20:38:17.01Z" }, + { url = "https://files.pythonhosted.org/packages/5c/88/1583a6a4647b3a882c452b8d8bf27d95ff355f5bb5640bb1531af600d381/ijson-3.6.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d2fa6ddc5bd997e7addca3cf8831825481eeb3359832d6657a60cda66409e980", size = 130692, upload-time = "2026-10-12T20:38:18.18Z" }, + { url = "https://files.pythonhosted.org/packages/6a/16/e0df63ff32529d01fe3d01c0e6288612d350df8dffe8723839e7e61627a5/ijson-3.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:417138b91db19b555abb07dfb14a744811190a5f4705edc776405a8dfcd5ef32", size = 134996, upload-time = "2026-10-12T20:38:19.327Z" }, + { url = "https://files.pythonhosted.org/packages/88/d2/402de52770bdb8292d1b2d4b35807b6fcfbb016a6f8233e0e221e97279db/ijson-3.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4c4f45476b8f366d1d4c630a8c7aaa28fb5765e9f5adcf64cb248c3a5f44aa2e", size = 128816, upload-time = "2026-10-12T20:38:20.3Z" }, + { url = "https://files.pythonhosted.org/packages/cf/27/0ee5464162f0242bb679990b1e1ad9e6241e32537314cf103d9c32f3817c/ijson-3.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:524ac54359985891d24ed66eeef4c20bc47f8654756370443bfabfaebe64e092", size = 131579, upload-time = "2026-10-12T20:38:21.224Z" }, + { url = "https://files.pythonhosted.org/packages/fc/6f/22b56a255d287d68944048a3860197601a60677451302a82bf69be4c3aab/ijson-3.6.0-cp310-cp310-win32.whl", hash = "sha256:20af3cc567c609c4cd78ab3865477ea905d8073f675ff02bc10388f1bfc7d094", size = 52274, upload-time = "2026-10-12T20:38:22.084Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4c/67f016b15db66634072b6fc5246ff68c57cfe8b8782233f0bd36aa4fbb5f/ijson-3.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:fbf6d5bb1e765fd87fce5cbe2e9ff4adaaaaa80c8b01289b517430d1cbea2b2b", size = 54725, upload-time = "2026-10-12T20:38:22.946Z" }, + { url = "https://files.pythonhosted.org/packages/69/d7/7f6dfbd6168f28299a712e56981e35f2e7c0a7fe9597e6d27ebd1d8315cb/ijson-3.6.0-cp310-cp310-win_arm64.whl", hash = "sha256:618ca300eae78ce920bb2b5d4728e01cca289c01c50bbb6d842a8ede78d223ec", size = 54092, upload-time = "2026-10-12T20:38:23.794Z" }, + { url = "https://files.pythonhosted.org/packages/e1/cf/0d667babb190e66a9875f817cc3b46a8ead0b951d1d9376516089ac5c2eb/ijson-3.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2057d59e3b92e03128cbbaaf67b03ea2179535a163a2f61193c1ad5f2dc02d52", size = 89127, upload-time = "2026-10-12T20:38:24.668Z" }, + { url = "https://files.pythonhosted.org/packages/78/7d/26b2694b0aa5bfd6144ee3bf1177cd128e61a7218f35e66434f8d4309e63/ijson-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:52f93134b6dffa045bd1f457b30c995edeb45856551adaeeac69da04fa701603", size = 60755, upload-time = "2026-10-12T20:38:25.546Z" }, + { url = "https://files.pythonhosted.org/packages/35/d7/f47f58dfc9df3c2f02cdf9e53659e36fcbb55f5e2f103b32d912597e01ea/ijson-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9aa0b7c301a01e2fb994d3cc420956b0d85f6a4237433948a5de108353fdb1e4", size = 60801, upload-time = "2026-10-12T20:38:26.608Z" }, + { url = "https://files.pythonhosted.org/packages/ee/28/8ddfa4c41b505b0aa9b12551e2efbca823dc4c1630e78f28f7e205be8350/ijson-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c4d80d961e3d8a6bb081595fdd55fd7c66a84f95377aecaca440a7f27a689516", size = 132366, upload-time = "2026-10-12T20:38:27.886Z" }, + { url = "https://files.pythonhosted.org/packages/26/13/52e521930ec97e472b1aa99ffdb3df47d5df4be79412b079c41e31807381/ijson-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a50ba1d5f8af50854243cbf523eff22a26f45f2b51a6c85177bbff48c99dfa2e", size = 140245, upload-time = "2026-10-12T20:38:28.892Z" }, + { url = "https://files.pythonhosted.org/packages/66/63/027e4f03328b9c7684b1b2a467d796a7381a48337f93b5747c2bb4f88cc4/ijson-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fa09fa38307b66c43efc98077f21e18e0af2fd192ff42130834cdcf4720424a6", size = 135574, upload-time = "2026-10-12T20:38:30.103Z" }, + { url = "https://files.pythonhosted.org/packages/11/82/8da55f5539dc723ddb0e415662560f1d6dc238093e5dc6af5452bac01bc1/ijson-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:09aa0c75005fb03644e21a694b836ef486e1a895149b268b9d8f6e6feb8a6377", size = 140214, upload-time = "2026-10-12T20:38:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/359b060b883a5844bbde2b467e448b8b695f4fb720c606795dcf7804b010/ijson-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:97787614c30031fc8cdf6a5d52ab5052783eddc27ec0abd03d94fa2facfb6eb9", size = 133565, upload-time = "2026-10-12T20:38:32.457Z" }, + { url = "https://files.pythonhosted.org/packages/a0/94/55e6f4910ae6a36456d023f52b2b30e6f85defa486dc28eb979595eb81ff/ijson-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dfe79b9eda5a230e78d11eff998e042eb401f3151b6a93759107679b34b81d72", size = 136062, upload-time = "2026-10-12T20:38:33.888Z" }, + { url = "https://files.pythonhosted.org/packages/04/90/65bbc3a2ae47011a60f95c44064b2a105e38e1217c93b045ac0616c77c82/ijson-3.6.0-cp311-cp311-win32.whl", hash = "sha256:e9849d7dce894160f19b66db0b4e74f8725276effed2b8028e9b723389863f3b", size = 52271, upload-time = "2026-10-12T20:38:34.946Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9d/392eefa167d73068220941b00244c93b5f94bc9aeb8c754748f886549e47/ijson-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9b54231c7ee3e7bbbf143b8d5f003bc4ffefb523e103d99517cdd03cc203d57", size = 54728, upload-time = "2026-10-12T20:38:36.425Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d6/8bdadfabb743d39a34d87aba24cf6fafa86dbf3ee9f2b80f8fb4cbad3f02/ijson-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:71c23e991600aff8478447508e8bb01ef98751bd0e43120cd8df8ff6ba03bd33", size = 54099, upload-time = "2026-10-12T20:38:37.649Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6e/5eb9158664f5495b118b064843735d07f6fe4a69f6bd7df8a9c99eda8a95/ijson-3.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:91c2b3877f02ddb0f557ca88254491d14053a6d91703ea2338542f7b576a6e82", size = 88705, upload-time = "2026-10-12T20:38:38.91Z" }, + { url = "https://files.pythonhosted.org/packages/5d/0e/078bf891755f16cae6e36e080cee238b461ee00581b22ec61678fcd961f9/ijson-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:914a87f45cc84f40863f9613f325c9b7824b4061ef75aaeb6897eaf885269ffe", size = 60664, upload-time = "2026-10-12T20:38:39.86Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bc/d3f35bb0376d7ad68a59370bec2903ed3cc2e9b86fb6c566092f2bcc9629/ijson-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55f8b704afdbda7fde2d317afd6af8638938c81d467ca46d0b8bcb6cf998ac7c", size = 60503, upload-time = "2026-10-12T20:38:41.203Z" }, + { url = "https://files.pythonhosted.org/packages/e5/a7/e80582a4665007fce3a87c60a4ee2c521296ded4edb2d1f4db871e655343/ijson-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8569bdbb524d9fe76518bc62438a3eefe0d36fb380bb4d98e738017a6624f9b", size = 139358, upload-time = "2026-10-12T20:38:42.094Z" }, + { url = "https://files.pythonhosted.org/packages/6b/20/d0da64fe537fb1aba9c7b09381f8155ce8ddfbd30cff1a5ee47757e0217f/ijson-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e592cd601f91424428e7cbce11f7ab0d5430253a81e60f8a69981fb1136c77c", size = 150977, upload-time = "2026-10-12T20:38:43.274Z" }, + { url = "https://files.pythonhosted.org/packages/3d/43/2d8abf1ff74ed9a0372021e61e9fc660f850e0cde9aced66ca1b97da77b0/ijson-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c14d568d31a322e8ed7e9735f6e355608a23cc6ff4b5da843515089dae4cbf5f", size = 150188, upload-time = "2026-10-12T20:38:44.5Z" }, + { url = "https://files.pythonhosted.org/packages/fc/92/5705d9f96dfca5f740917944d78c67783fb449651291e4b641e455dbbcfb/ijson-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8ee59d754e28247c5ef631ca013a70ca705f292a46e65b59b78f7a4b7f59871a", size = 151832, upload-time = "2026-10-12T20:38:45.518Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3e/3cfe4c16b28f2d562ef80091c13dccb173f6aa3eec47964396718b5786bf/ijson-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bb9f6c27fdda6d43993b25a49ca7903979c4c29bd6722b3dbf4e7061794e9cbc", size = 143236, upload-time = "2026-10-12T20:38:46.502Z" }, + { url = "https://files.pythonhosted.org/packages/be/0b/10970b82f7be5d95105e71465944024f4268fb679cff0cbbdd28982ea5c2/ijson-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c88c4ddccb99a4c30aa0a6adff91bcaeb7467650c0e6a50585b5f51deeb1146", size = 152035, upload-time = "2026-10-12T20:38:47.509Z" }, + { url = "https://files.pythonhosted.org/packages/71/e9/f5320a29c955e6011a960e8cea9c57457a066c18974988a5a7d688ffe701/ijson-3.6.0-cp312-cp312-win32.whl", hash = "sha256:967318686d689286f32794e01fa11c2181e7fbf43940e016f3056f8d5643d055", size = 52666, upload-time = "2026-10-12T20:38:48.447Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/b4e779fe248ea1587f2166cab9cc993e1e159fda0ca8f9bc998a378f2e9a/ijson-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:d5aceb2da334db519c5bb7be0d043f357493554bda2a480eea3e2fe78352ab0c", size = 54818, upload-time = "2026-10-12T20:38:49.329Z" }, + { url = "https://files.pythonhosted.org/packages/74/dd/b044efbfe19669b42f1c04e6ea137fc51c6927c4826c74166485f99f1c80/ijson-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:370ea402f105c3cf89783ad6add670a24aa03949392db5f0614420566e4914b8", size = 54007, upload-time = "2026-10-12T20:38:50.243Z" }, + { url = "https://files.pythonhosted.org/packages/0e/32/7b69dae1a6059acc0f7efcb29fc0c67dc3ca41844c2be5b9c084000cb05b/ijson-3.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4333247a212d997d8b58555b135c8d28f68cf43218fadc28bf28f3ffafaae676", size = 88711, upload-time = "2026-10-12T20:38:51.12Z" }, + { url = "https://files.pythonhosted.org/packages/cd/90/334b244eb96332941bb7b7accbf7e151759d09638a125e2989971de62253/ijson-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ab7107ca09caa5af5d94a859065a168b2b56d5822db34ef93bd7b31f088039a", size = 60663, upload-time = "2026-10-12T20:38:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/85/99/822714bb2eb6d2060a55c4cde96e9beac7ce1e410ed300e026e63fcf76bc/ijson-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fb87bee137e396e1d8c7e759bf072db5cc9b8c4e730e3b388d71cd710fa3fc11", size = 60500, upload-time = "2026-10-12T20:38:52.839Z" }, + { url = "https://files.pythonhosted.org/packages/57/4c/ccc9199e531184a273dd40bdc6386d538d8d81eeb0cf2f1aeb9430aab889/ijson-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4e9b0b97de6c1cebd501b3cc165e080d6c6309a43b5d6c3ce3e76b6c938b2ad7", size = 139167, upload-time = "2026-10-12T20:38:53.889Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fd/711c7a403d7a06998a7a5c28adc6569621b30e4e50e905baf91cfdb9c6de/ijson-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:82683a1946b6af5084711fc1032ef64423215eb965ab4df539b683664eebe049", size = 150995, upload-time = "2026-10-12T20:38:54.92Z" }, + { url = "https://files.pythonhosted.org/packages/7d/7f/685e0fa8f2151dda3fec9bc1022912c0f3f1426f48abb9d66e7c88d1918a/ijson-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdf857bf286c5e4854eacb6434a9c1006fbc1c44c58ff79293ccaca95ec7b82", size = 150203, upload-time = "2026-10-12T20:38:56.139Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/2a89c15efe82d3f3a2e71a39e26e2b8c9eeaea60c64825627cdd4a0de6e4/ijson-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dd543c0d5e5c8ec9e1570cbe805c57271b1f272e57c86794b226e2a03466cec", size = 152226, upload-time = "2026-10-12T20:38:57.043Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ed/667189c5011d8aa9d83a1d915a3b27761fc073ca4f32ce5d05f40c21c623/ijson-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fa6a0f303792fd89bbeb2e5ff4e53ee2c5c9d59bf2bed49dcd98adf413178f4e", size = 143368, upload-time = "2026-10-12T20:38:58.056Z" }, + { url = "https://files.pythonhosted.org/packages/08/6f/2cbef04ee0a62cb67c16a7d06d87a76c46cab5616d3210f70b44d43f81d7/ijson-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2e19a3c7b0dc3dcaf2bda1c8033d021aec8b7e862b33e903d79b944eea96d389", size = 152532, upload-time = "2026-10-12T20:38:59.026Z" }, + { url = "https://files.pythonhosted.org/packages/8f/53/275d65be7a2759545c56db094631e16439304ebc53df983a971c51319396/ijson-3.6.0-cp313-cp313-win32.whl", hash = "sha256:65e65a6e28d95edafa2c99dae7f7c1a5c3403bf5bb62bc6eb919fefff5298dad", size = 52665, upload-time = "2026-10-12T20:38:59.928Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c3/412985e2c0aae4a33dcfea4b2f6406b66cc7501d24c2ad0993152df1d9f2/ijson-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:cf855a688dd80570e6daaa67afc84a950acf9c6ba9c3526096957614d21db1bd", size = 54816, upload-time = "2026-10-12T20:39:01.024Z" }, + { url = "https://files.pythonhosted.org/packages/e5/30/200e1b1a04c5f0626f8fc09e21efdcf55fb16ca6ba0d8c42b97050488ca3/ijson-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:6a7a242aca8e03261c59290be66f428cef6b0a1b4d4a7596aa33fe113faf15f3", size = 54007, upload-time = "2026-10-12T20:39:01.912Z" }, + { url = "https://files.pythonhosted.org/packages/47/14/d19d1d381905d3fa7570d4b7735479da03e55088ad520ff9a38a9a5eaac2/ijson-3.6.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:be07a2773667f189a329cce0520df8d146825caefa7af9b4366883ceb4f24b45", size = 89270, upload-time = "2026-10-12T20:39:02.778Z" }, + { url = "https://files.pythonhosted.org/packages/f7/2a/ba91590532de1705c0b8921ba0d81fe441c6899c7a6ff96429f546c27016/ijson-3.6.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6213dce68c6bac784c6929f80941358756a7cd5260209cdb0bd08be1c4829d04", size = 60881, upload-time = "2026-10-12T20:39:04.743Z" }, + { url = "https://files.pythonhosted.org/packages/15/1f/44a0b67e572ae35e697486d6d23a7adf0a2f978175fe3135be05664c8453/ijson-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:67a754d7166821402f49c553a6c9e67799aa3f76d8c6ff554ed10444b166fd4d", size = 60809, upload-time = "2026-10-12T20:39:05.812Z" }, + { url = "https://files.pythonhosted.org/packages/bd/88/dd6be2f1967f5e61286bc43e64dec8bc6f7387977f4734f525442102c94b/ijson-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6ce4e105fbce77b2038e281c3715c2e984affe79594fcb750c61b6ee7cc12f14", size = 141059, upload-time = "2026-10-12T20:39:06.676Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/447db3f4239eaf42774b4bdb23800b5daf0c3c87fddd98f4bbe0abe07dc3/ijson-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f029f72a33cbf6781ffa0198ff3d96637e7202b46040b66ebca0623e5e0a9a3", size = 151021, upload-time = "2026-10-12T20:39:07.598Z" }, + { url = "https://files.pythonhosted.org/packages/2b/36/0e3b638a5fc3d663c098e7900b38f61982f96b875251bd0f4cf092146293/ijson-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09ab289fc2faf66575c4a1c626cddd413843f5508829fb4c2370fe584624d396", size = 149666, upload-time = "2026-10-12T20:39:08.547Z" }, + { url = "https://files.pythonhosted.org/packages/61/da/366f12b23f2deb485693ab2c630afe8a43ac17e2cf347c6c8bb21fe9d2c1/ijson-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f8548b45c9313e8ee0138073d86aca14adbf6e48a3f1f315ab6e7ae316df9c9e", size = 151744, upload-time = "2026-10-12T20:39:09.465Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ac/995ed84dac89579bbfda6e621752488b7cd4908e663acdaea5462d6c7b62/ijson-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:3be142820cd2c6c5f4830a017cde667c7344bcedaebe37d92d7e59b5713752fc", size = 144755, upload-time = "2026-10-12T20:39:10.368Z" }, + { url = "https://files.pythonhosted.org/packages/1d/df/338a8d8fa346467152ecd04004ffff97f26f5e2fc64c1e112ab8a178a2fc/ijson-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20b97ab48a802c1e6839438b788ab7e6cbb7a4ee0575a17eb4118d2d91e4bd75", size = 151834, upload-time = "2026-10-12T20:39:11.295Z" }, + { url = "https://files.pythonhosted.org/packages/70/5b/e677883fdc56affaa1afe598228745e653cf823eb050ea602258927f56bf/ijson-3.6.0-cp314-cp314-win32.whl", hash = "sha256:4462653b135f5a3de2583b9acae14517ef660ab2df0defcb5946d510fd4d5842", size = 53277, upload-time = "2026-10-12T20:39:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/87/0b/060c1fab1908d3916ccb3c1acd9af13239f3f22c29cd7a0e1ef0ae55ae54/ijson-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:f151fd21639984e4fc76b7a568426fc6ab1024fe73d9955fc498ea8104df4a6e", size = 55575, upload-time = "2026-10-12T20:39:13.166Z" }, + { url = "https://files.pythonhosted.org/packages/99/8b/262c3218adf581888b312c673ccbe8396e8660ccb7db81e6a551ebb2af95/ijson-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:9ef59a9c531cb3e478631c6367c32966330fa656c711be5f0001999a18c9d98f", size = 54716, upload-time = "2026-10-12T20:39:14.097Z" }, + { url = "https://files.pythonhosted.org/packages/42/f5/cb652342e4dd2643439a007035e9d95a16af10a3cd0e10d08e6a48e4170c/ijson-3.6.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ac5ee1a8d95a83cfb957378c8b6b3c69d099b399532454d1edd226547f0f50e5", size = 93234, upload-time = "2026-10-12T20:39:15.26Z" }, + { url = "https://files.pythonhosted.org/packages/f6/47/4f12f6b257772a1f644a53e5a7d3f8ac49fb49ee0b3ecbb9a244ab5e2de8/ijson-3.6.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7503e53a3e5c0b52a61259c453f5c12f15a3b675b1158dbec6cbe30284d5d186", size = 62943, upload-time = "2026-10-12T20:39:16.205Z" }, + { url = "https://files.pythonhosted.org/packages/ed/56/24c46651b8514a19d7dc4e2d991b9a2ba24989d87673cb30ee24460215fe/ijson-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6cd6f4086929cb4ee888233fa1b40e194b5dc9e971a13302badbff546c9932e", size = 62634, upload-time = "2026-10-12T20:39:17.094Z" }, + { url = "https://files.pythonhosted.org/packages/70/37/5f1e638ad45080c497decab6efa24f25182aa38cc669b43a407f8a826910/ijson-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:57737b2cabddb5a2405f4e875a550a253c94f42f5e2a90b36d23ae52873d3b48", size = 200839, upload-time = "2026-10-12T20:39:18.05Z" }, + { url = "https://files.pythonhosted.org/packages/09/ba/49f5d89612dcf4aeec3a1fa91601b9b77f81726cc821620aed42f8730918/ijson-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc26be6ed77378bf93588e039817035db415af56b1b37cf7283b6ebc291b0943", size = 219023, upload-time = "2026-10-12T20:39:19.589Z" }, + { url = "https://files.pythonhosted.org/packages/f5/8e/6aa7d6c830c637a89935994be3dff042ba66b2a24960251a12c3351a9918/ijson-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:407a8f95d9897f4e4228564411e4493de4d65e8e1e674f87cc4bfb5cdcd5644b", size = 208753, upload-time = "2026-10-12T20:39:20.699Z" }, + { url = "https://files.pythonhosted.org/packages/85/c3/af87c268d99464732199d4804364405e5a01acfe8f1261504ffbdc169889/ijson-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:889a4075b1c74513d0a890f47a4e8d33fb21fc7f783743a1fefeafc27da5f55f", size = 213512, upload-time = "2026-10-12T20:39:21.801Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/a48d13f6a56bcea5bc627eca656b8463e62791b655fb53b8b3ce28e1eb56/ijson-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3d30bd21694dd12375a7c192ace682a46907b9fe181a46cd0850c7f620038ea9", size = 201285, upload-time = "2026-10-12T20:39:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/7f/2d/3ff07d2fd548459030ab33455908c9a44f978a51d168c7636607a3350cfe/ijson-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6b3436a09a3dc494791862a623619a2304b812eda739a710b8a474bb9f3e5065", size = 205954, upload-time = "2026-10-12T20:39:23.893Z" }, + { url = "https://files.pythonhosted.org/packages/d8/4f/766286dcda03d0de7332b681612e076e305331f50d0367d0a3292fc19db3/ijson-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:78915030a2ff3e0ae0a95dc7d5b1d2e3e1f2a283266ae2d87cfd4d16be945ea6", size = 54493, upload-time = "2026-10-12T20:39:24.908Z" }, + { url = "https://files.pythonhosted.org/packages/d4/59/49cec183b2405d0e655ebd7cbf278e8433a8deb6d15753d3f6c2ec6249e2/ijson-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8b1fbb26ddc6002e131e935370de1b171a66cc1599e285eefd37cd1f681004a7", size = 56564, upload-time = "2026-10-12T20:39:25.921Z" }, + { url = "https://files.pythonhosted.org/packages/90/8b/45a0807a232324386ddb3fe837b0b21fed9eb943e202e8725d65d67abc4a/ijson-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:3b9d136436134c98294afd3efb49c7360c81da07040ac50186971f37b53f77ee", size = 56101, upload-time = "2026-10-12T20:39:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/f2/64/96853dd6376e0def284a774de1dbd05dd1455fee3a3d648ea0dbb8086670/ijson-3.6.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:e58bc4b0470497e5d00f0faa055d0b8aef275ed210266d5f86ed17a23d064408", size = 89323, upload-time = "2026-10-12T20:39:27.618Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f4/0fd4129c76d1493cd9ce6ba95c2bb697f4416164de25bdad2fe0ee2a3951/ijson-3.6.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2e6b9c56a8a727153935c83d91450d1eae8f2a9ad4091360eb6ec03d47aa08e6", size = 60888, upload-time = "2026-10-12T20:39:28.536Z" }, + { url = "https://files.pythonhosted.org/packages/00/a8/a4db191ab78cacb6da8c66d9183e023b10a33ccc5bbb2a78f7508b9a23a7/ijson-3.6.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:d847615380321e4dfb3d269deb562876f170ab9f46c80cbf880a2496fb09a0e3", size = 60861, upload-time = "2026-10-12T20:39:29.476Z" }, + { url = "https://files.pythonhosted.org/packages/66/78/015f30c10f73064efa4cbbacaa2e581d7d3c161e2de7bcea5aaeab570261/ijson-3.6.0-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e60c40f78fa00325df96d57f68786f1fed3e6091b9d41cf9811d22914dff8f94", size = 143887, upload-time = "2026-10-12T20:39:30.414Z" }, + { url = "https://files.pythonhosted.org/packages/11/a4/865672b6bff38a6b1b3f50ce4c5244ce84a5a3457652f33154a36d361540/ijson-3.6.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b48f4ce1fbb89045e7b92defe75c848275f84734cef8ab01cfa3ee443d8a4bc", size = 152135, upload-time = "2026-10-12T20:39:31.476Z" }, + { url = "https://files.pythonhosted.org/packages/6c/20/fac4d452eef9a4400f4561e37fb84d3c3d757d11bb63e3be4595697b49c5/ijson-3.6.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5454696282add7cde430fc6dc90d0d65db2f1585303b8ec701e1c36aee14fc4c", size = 150585, upload-time = "2026-10-12T20:39:32.707Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f2/29e356b9f034127f09e01c4d460677f8e1837ae37a24fdb734f52136fa68/ijson-3.6.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:4b5addfd509ca4192ec7107a3f07d0295221e62b974d8abfa8cc9b67c10dc9e2", size = 152496, upload-time = "2026-10-12T20:39:33.739Z" }, + { url = "https://files.pythonhosted.org/packages/39/7d/4115b88dc29922f8e41f51eb112a116298ba39c6b2bc9b5c7e8798ba724e/ijson-3.6.0-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:160c94c9cac5837f49e5b9cbb725604e75694083260c7180ef381f705850992a", size = 146659, upload-time = "2026-10-12T20:39:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/6f/30/ccd58a0c5d56d602ec59a2701939a3416edc2c837c5866adbb45bd7e3a1d/ijson-3.6.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:7c1deb116218a900fe6f231544c31e8e2dd625819ff7ce5ce908aa19622fa1c9", size = 152532, upload-time = "2026-10-12T20:39:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f6/adb1149fc1c2a834dae3612abe9d1c3250597ef7525eca6cc0d9669093fb/ijson-3.6.0-cp315-cp315-win32.whl", hash = "sha256:20d227e46ff03ad2f40cb5bfa56adcc47b6713f7b81c67b9767f761ceded90bb", size = 53270, upload-time = "2026-10-12T20:39:37.225Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c0/abf3695b0e300a4d9b45aafa352a5ffbd2b776ad754530dcb99faf0c5662/ijson-3.6.0-cp315-cp315-win_amd64.whl", hash = "sha256:e18f1486106c072c037a8699c9ff1450574c395f45687cdf5b4142d9c2d2df61", size = 55578, upload-time = "2026-10-12T20:39:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c4/c2bb635321379aaa6d9b9f56d226e633c0dec70c2b24bb411648e7c59dd8/ijson-3.6.0-cp315-cp315-win_arm64.whl", hash = "sha256:4bc6c5351352760fd0c29cc437e48598b92f66133f2be5ef712f75180e1759a7", size = 54745, upload-time = "2026-10-12T20:39:39.892Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d4/414294b4c3acbbd182737c78a053df6702f9fdbc7ee45dc4125e0f07896f/ijson-3.6.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:96863aca6697edc2c5465e1dd2d7ea7b67b7743b9657adb1e65c04aab9c6c2ab", size = 93316, upload-time = "2026-10-12T20:39:41.405Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f0/829812e27f46a357c4894b9a1d3adf53c18d186d344d32a5a11a2749fd5b/ijson-3.6.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:5a7e4220d788bfa155fc2885edf04d8beada42eeaa260a02fe749d056dc6ffb9", size = 62932, upload-time = "2026-10-12T20:39:42.52Z" }, + { url = "https://files.pythonhosted.org/packages/61/98/6f4b83aacd1037a0d95dea7511cdb40260ea8c45a06c13a62470f5981931/ijson-3.6.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:ee99f497c4fd997bc6be85dfc72635ad69f08e8a727937193dd449c6b7f9348c", size = 62724, upload-time = "2026-10-12T20:39:43.648Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b2/56de3c977f476d57b58373c08dea5361ba4e959bc18092d68bb1edce784a/ijson-3.6.0-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21a7cd561d97f20a7011760d7b0687cafbd86b1f67738badb7809ce7e2385261", size = 200710, upload-time = "2026-10-12T20:39:44.598Z" }, + { url = "https://files.pythonhosted.org/packages/12/2d/4a00b8475c2f41e1172b3939adb8d6cc0eecffdf63a810987230fadcc8c5/ijson-3.6.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dfd28144223c9ee6e0544b903efd334214cb2048c6e22f9cb9c11fdf1ae86d9", size = 218004, upload-time = "2026-10-12T20:39:45.624Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/403edf91b6d5e4bba077243cb0290e1b751e1104fd8c9d79e59b21dfa251/ijson-3.6.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:539b2d8b9427b322ccc15db0e7bda8cd7597be62bd07b969df3e482e67c11fb7", size = 208754, upload-time = "2026-10-12T20:39:46.75Z" }, + { url = "https://files.pythonhosted.org/packages/73/a4/f56e9d5e4d6b4b7eaa4723f852900a865019a2155d65e432298487a2657e/ijson-3.6.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:503c938e6ae6686e0c702b3ae33e37433450ca41c0d022746e7bef3173ea9778", size = 213535, upload-time = "2026-10-12T20:39:47.787Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e3/dd6858b224b041a1e5164aee70c515c793fcec4c0b6316a5356d83d9a3af/ijson-3.6.0-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:2b0f27fc60291fb1aa73de1a4588476efb49f8a4977c20c679aa15480e3f63a8", size = 201185, upload-time = "2026-10-12T20:39:49.232Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c1/891e782e3b72a9a54150da7c40d71a3fe69a3c38e7506fa0f7e179780f82/ijson-3.6.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:130bbccf2569ca8fc69dd1496dc8f55231408cad56ccfdd9d4ab17593a65cc95", size = 206095, upload-time = "2026-10-12T20:39:50.284Z" }, + { url = "https://files.pythonhosted.org/packages/48/3e/3bebd41958495d2365cef21f0f7727b82647d736dea05e01fe87bf0b3a0b/ijson-3.6.0-cp315-cp315t-win32.whl", hash = "sha256:600912be7871678688c7890c254d44421079781991badf84792073b43d05890b", size = 54474, upload-time = "2026-10-12T20:39:51.358Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4b/29f22cbe8e9cdeaf632ec2cb551237f432f0df8689c6ae3d282f4c3a1065/ijson-3.6.0-cp315-cp315t-win_amd64.whl", hash = "sha256:9846fd8da153a478f797ac417b07ce47c0f73acd7798038ba16a45d417cb50c9", size = 56585, upload-time = "2026-10-12T20:39:52.247Z" }, + { url = "https://files.pythonhosted.org/packages/3f/aa/dc4c4d1b7ec85a2a5c1e97f73aa23742b68345a7fed4a423b7ef4bffcaeb/ijson-3.6.0-cp315-cp315t-win_arm64.whl", hash = "sha256:f994df777d7e9c4ac72a54ed382c9abef4804d705d8904acc19ed141a3604b3c", size = 56128, upload-time = "2026-10-12T20:39:53.186Z" }, + { url = "https://files.pythonhosted.org/packages/5d/1f/7599297dea49c59574f301f1ec6bfde9fc3ada6e758ff7fe749590737764/ijson-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25224e9090bf572da34400b4ff1c04740d360f4fb0ad3a940e0cfe7938f9ac82", size = 57885, upload-time = "2026-10-12T20:39:54.119Z" }, + { url = "https://files.pythonhosted.org/packages/75/e7/7cb29337d441981b7874bda9a12788b69ad6e42e1b61ebf1c756beed2164/ijson-3.6.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:7e8fd6dbc32233e27bb4705d2c7a75c23b86582d30cf1e9e04c241914883f8b8", size = 57377, upload-time = "2026-10-12T20:39:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/35/d3/2dc1e1ab05c7a4daf3986f21cb5bec27d4fe0e650f7fa38642961a3a4d68/ijson-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fba8a6d5d188fe18a22c7065c1486d13e9de2c109e0282271d81e76e479db86e", size = 71600, upload-time = "2026-10-12T20:39:56.027Z" }, + { url = "https://files.pythonhosted.org/packages/85/27/72234bec4ebaaa023c220aeef7ccdb1c5bbf43de0ce9704f11d16135fc7a/ijson-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90e1bfed93a43253106e167b0bce3b33e98b4c5cb292b9cbdd9a856b1f098417", size = 72609, upload-time = "2026-10-12T20:39:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/e4/69/241966a49d55b45c476ad3eb616506b6f94269275646087df0e785b1c04e/ijson-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:126e7d6b8bd51563f631562764f347db9bfb4dcc9ff920be28ba7d65805e9594", size = 69067, upload-time = "2026-10-12T20:39:58.083Z" }, + { url = "https://files.pythonhosted.org/packages/89/ea/505cbd06f390fb56fd5cd17d083298e6720c163d2f6bcf5909cad2f9b8da/ijson-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e31899e714a25260c261d67ffd5159b8eb691508b91967f66dff861dd0ff3aec", size = 55011, upload-time = "2026-10-12T20:39:59.279Z" }, +] + [[package]] name = "importlib-metadata" version = "9.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version < '3.12'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" } wheels = [ @@ -2093,6 +2325,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, ] +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + [[package]] name = "jsonschema" version = "4.26.0" @@ -3245,6 +3486,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + [[package]] name = "python-discovery" version = "1.6.1" @@ -3744,6 +3997,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/3e/4132e539aed78c148854d4997a2685b0ed4dc4e87110b59ce528564e184e/ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a", size = 11399302, upload-time = "2026-08-13T15:17:10.908Z" }, ] +[[package]] +name = "s3transfer" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/43/35e4d8aa320bffe8287fe8f65f578fa2d2db0a64212f0e710dce58267854/s3transfer-0.19.2.tar.gz", hash = "sha256:ba0309fd86be3c27dbf78cdd813c13c5e1df16e5874b99d2535ebbdfb9892993", size = 165592, upload-time = "2026-07-22T19:30:44.432Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/e7/5c595c75e9f41a44f30e526eda465ea0b4eec93470e074e4a111b253f13a/s3transfer-0.19.2-py3-none-any.whl", hash = "sha256:d8168eccca828cbb2cd573675333f3bddd254313a9c42494b84c76b539e8ba25", size = 90216, upload-time = "2026-07-22T19:30:43.251Z" }, +] + [[package]] name = "secretstorage" version = "3.5.0" @@ -3779,6 +4044,92 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smithy-aws-core" +version = "0.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aws-sdk-signers", marker = "python_full_version >= '3.12'" }, + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, + { name = "smithy-http", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/d3/501c0023548173416109ac42298ca33b708469dc922005770811a597949f/smithy_aws_core-0.11.0.tar.gz", hash = "sha256:29ee89976a520a87e3db557e03e115fdc21a0a60b81161e95174395a1b064da1", size = 38791, upload-time = "2026-08-24T21:16:59.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/f6/fefda9aab809fa1a62bf7073bd6d8ab427bd9989f39b13c0d6e29d4d1045/smithy_aws_core-0.11.0-py3-none-any.whl", hash = "sha256:77cf130c22deac14a8cbeb8ccc4bcfe5a91798f4b38cb53a987080ec58c89f23", size = 58855, upload-time = "2026-08-24T21:16:58.657Z" }, +] + +[package.optional-dependencies] +eventstream = [ + { name = "smithy-aws-event-stream", marker = "python_full_version >= '3.12'" }, +] +json = [ + { name = "smithy-json", marker = "python_full_version >= '3.12'" }, +] + +[[package]] +name = "smithy-aws-event-stream" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/0e/6efb3a4ed92c0f1ada6de060ac92e7115a1e34d0ab1fb99a6056734a88ea/smithy_aws_event_stream-0.3.0.tar.gz", hash = "sha256:a0e227367a973144e205a075d0a424f95c92f26656a1018d08900da2ae547c49", size = 12818, upload-time = "2026-05-05T18:04:14.317Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/b4/c4c4a9aa5a4cf3ee285f1bd71999441177c651d911a82a6e64c57b6e3e65/smithy_aws_event_stream-0.3.0-py3-none-any.whl", hash = "sha256:8b505cc28230e4fe9c5e025333209b44ca2db560451ac9ed9a7d74939edf4413", size = 15845, upload-time = "2026-05-05T18:04:13.351Z" }, +] + +[[package]] +name = "smithy-core" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/c6/93e9eea3c6163228dfe972c3e989e0553047858805ab7aa4a59f074ba129/smithy_core-0.8.1.tar.gz", hash = "sha256:3d2f8fca5960d74bd7ef380f70901c7bcdebe53f929d2d3d2fa6cb790b3f5214", size = 54259, upload-time = "2026-08-20T17:55:30.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/23/c6430bbf406477fc7d16254b9908a723b299a4a21a94c99db9d12c84a8bf/smithy_core-0.8.1-py3-none-any.whl", hash = "sha256:44bd9bdf702f76919af58e44a6a1bb3dc136a745b2f955281743022ce767e347", size = 68805, upload-time = "2026-08-20T17:55:29.366Z" }, +] + +[[package]] +name = "smithy-http" +version = "0.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/78/b5f3113d6c8f0bc1f9777a7f5ca84b892d29efac05850e14f7d4f7e645b5/smithy_http-0.5.0.tar.gz", hash = "sha256:bb4a19672f7c7eeb872a308f777eb505281a5bafb1ee3d1ea9c760c06c352510", size = 31122, upload-time = "2026-08-24T21:16:56.488Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/27/e414082643028846b73afa52a1a8f934548196ee12b187a06803f02a3e66/smithy_http-0.5.0-py3-none-any.whl", hash = "sha256:af273d5f42e7733ce7a6e9bd6fdd6a59ef1b61f6cd1f4a89dd53dfce99da7bef", size = 42198, upload-time = "2026-08-24T21:16:57.52Z" }, +] + +[package.optional-dependencies] +aiohttp = [ + { name = "aiohttp", marker = "python_full_version >= '3.12'" }, + { name = "yarl", marker = "python_full_version >= '3.12'" }, +] +awscrt = [ + { name = "awscrt", marker = "python_full_version >= '3.12'" }, +] + +[[package]] +name = "smithy-json" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ijson", marker = "python_full_version >= '3.12'" }, + { name = "smithy-core", marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/04164eefb3da7479f52f6535b4b39cc8384c292cb2bb74279f2acc4f4b4d/smithy_json-0.3.0.tar.gz", hash = "sha256:c81c7034587e01bc64767cbbecb05a7d65ca9070612fd94e8a03e80540290a22", size = 7956, upload-time = "2026-08-20T17:55:32.177Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/cf/0104c40a0e18fa307ea3da4310eba949f474a5bc1df3cc2b5851a72e8486/smithy_json-0.3.0-py3-none-any.whl", hash = "sha256:ffb73d2e60cf5e616e5d0a1019e7b9f518edba076cb423f10981457725dcddc4", size = 10252, upload-time = "2026-08-20T17:55:31.204Z" }, +] + [[package]] name = "sounddevice" version = "0.5.6"