From 6f61466bec3397e97efc595017413d43724c6d73 Mon Sep 17 00:00:00 2001 From: shobhitagnihotri69 Date: Fri, 18 Sep 2026 15:46:48 +0530 Subject: [PATCH 1/2] fix(streaming): add non-negative validation constraints to RealTimeTranscriberOptions Enforce ge=0 boundary validation via Field constraints on: - connect_timeout (timeout waiting for WebSocket handshake) - max_connection_retries (retry attempts after handshake failure) - connection_retry_delay (delay in seconds between retry attempts) - terminate_timeout (timeout waiting for server TerminationEvent) Previously, negative values were accepted without error, leading to empty retry loops or unexpected immediate socket disconnections. Added unit tests covering negative rejections and zero boundary acceptance. --- assemblyai/streaming/v3/models.py | 20 +++++++------- tests/unit/test_streaming.py | 44 +++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/assemblyai/streaming/v3/models.py b/assemblyai/streaming/v3/models.py index 2ab88d9..e4ed156 100644 --- a/assemblyai/streaming/v3/models.py +++ b/assemblyai/streaming/v3/models.py @@ -4,12 +4,12 @@ try: # pydantic v2 import - from pydantic import BaseModel, model_validator + from pydantic import BaseModel, Field, model_validator pydantic_v2 = True except ImportError: # pydantic v1 import (fallback for Python < 3.14) - from pydantic import BaseModel, root_validator + from pydantic import BaseModel, Field, root_validator pydantic_v2 = False @@ -401,17 +401,17 @@ class RealTimeTranscriberOptions(BaseModel): api_key: Optional[str] = None token: Optional[str] = None # Seconds to wait for the WebSocket handshake to complete before treating - # the attempt as failed. - connect_timeout: float = 1.0 + # the attempt as failed. Must be positive. + connect_timeout: float = Field(default=1.0, ge=0) # Additional handshake attempts after the first one fails on a transient # error (timeout, network drop). 0 disables retries. HTTP-level rejections - # (auth, quota, bad request) are never retried. - max_connection_retries: int = 2 - # Seconds to wait between handshake attempts. - connection_retry_delay: float = 0.5 + # (auth, quota, bad request) are never retried. Must be non-negative. + max_connection_retries: int = Field(default=2, ge=0) + # Seconds to wait between handshake attempts. Must be non-negative. + connection_retry_delay: float = Field(default=0.5, ge=0) # Seconds disconnect(terminate=True) waits for the server's - # TerminationEvent (and any final Turn) before tearing down. - terminate_timeout: float = 5.0 + # TerminationEvent (and any final Turn) before tearing down. Must be non-negative. + terminate_timeout: float = Field(default=5.0, ge=0) # Alias: the former name for `RealTimeTranscriberOptions`, bound to the same object. diff --git a/tests/unit/test_streaming.py b/tests/unit/test_streaming.py index 1bb2a5d..ae19dc8 100644 --- a/tests/unit/test_streaming.py +++ b/tests/unit/test_streaming.py @@ -2391,3 +2391,47 @@ def test_silence_event_dispatched_to_handler(mocker: MockFixture): assert isinstance(received[0], SilenceEvent) assert received[0].start_ms == 12000 assert received[0].end_ms == 13000 + + +# --------------------------------------------------------------------------- +# Boundary validation tests for RealTimeTranscriberOptions +# --------------------------------------------------------------------------- + + +def test_connect_timeout_rejects_negative(): + """Negative connect_timeout must raise a validation error.""" + with pytest.raises(Exception): # pydantic ValidationError (v1 or v2) + StreamingClientOptions(api_key="test", connect_timeout=-1.0) + + +def test_max_connection_retries_rejects_negative(): + """Negative max_connection_retries must raise a validation error.""" + with pytest.raises(Exception): + StreamingClientOptions(api_key="test", max_connection_retries=-1) + + +def test_connection_retry_delay_rejects_negative(): + """Negative connection_retry_delay must raise a validation error.""" + with pytest.raises(Exception): + StreamingClientOptions(api_key="test", connection_retry_delay=-0.5) + + +def test_terminate_timeout_rejects_negative(): + """Negative terminate_timeout must raise a validation error.""" + with pytest.raises(Exception): + StreamingClientOptions(api_key="test", terminate_timeout=-5.0) + + +def test_options_zero_values_are_accepted(): + """Zero values must be accepted for all numeric option fields.""" + opts = StreamingClientOptions( + api_key="test", + connect_timeout=0, + max_connection_retries=0, + connection_retry_delay=0, + terminate_timeout=0, + ) + assert opts.connect_timeout == 0 + assert opts.max_connection_retries == 0 + assert opts.connection_retry_delay == 0 + assert opts.terminate_timeout == 0 From d89dc4ac966d9a46519b48322e077e9678bf2299 Mon Sep 17 00:00:00 2001 From: shobhitagnihotri69 Date: Tue, 22 Sep 2026 12:42:46 +0530 Subject: [PATCH 2/2] fix(streaming): enforce gt=0 on connect_timeout and bump version to 1.5.6 Per maintainer review by @ccampbell-aai: - Update connect_timeout Field constraint to gt=0 as zero is an invalid timeout - Update test cases to reject zero connect_timeout - Bump version to 1.5.6 --- assemblyai/__version__.py | 2 +- assemblyai/streaming/v3/models.py | 2 +- tests/unit/test_streaming.py | 12 +++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/assemblyai/__version__.py b/assemblyai/__version__.py index 8c118e6..d0113b3 100644 --- a/assemblyai/__version__.py +++ b/assemblyai/__version__.py @@ -1 +1 @@ -__version__ = "1.5.5" +__version__ = "1.5.6" diff --git a/assemblyai/streaming/v3/models.py b/assemblyai/streaming/v3/models.py index e4ed156..397dc2c 100644 --- a/assemblyai/streaming/v3/models.py +++ b/assemblyai/streaming/v3/models.py @@ -402,7 +402,7 @@ class RealTimeTranscriberOptions(BaseModel): token: Optional[str] = None # Seconds to wait for the WebSocket handshake to complete before treating # the attempt as failed. Must be positive. - connect_timeout: float = Field(default=1.0, ge=0) + connect_timeout: float = Field(default=1.0, gt=0) # Additional handshake attempts after the first one fails on a transient # error (timeout, network drop). 0 disables retries. HTTP-level rejections # (auth, quota, bad request) are never retried. Must be non-negative. diff --git a/tests/unit/test_streaming.py b/tests/unit/test_streaming.py index ae19dc8..4443b37 100644 --- a/tests/unit/test_streaming.py +++ b/tests/unit/test_streaming.py @@ -2398,10 +2398,12 @@ def test_silence_event_dispatched_to_handler(mocker: MockFixture): # --------------------------------------------------------------------------- -def test_connect_timeout_rejects_negative(): - """Negative connect_timeout must raise a validation error.""" +def test_connect_timeout_rejects_non_positive(): + """Negative or zero connect_timeout must raise a validation error.""" with pytest.raises(Exception): # pydantic ValidationError (v1 or v2) StreamingClientOptions(api_key="test", connect_timeout=-1.0) + with pytest.raises(Exception): + StreamingClientOptions(api_key="test", connect_timeout=0) def test_max_connection_retries_rejects_negative(): @@ -2423,15 +2425,15 @@ def test_terminate_timeout_rejects_negative(): def test_options_zero_values_are_accepted(): - """Zero values must be accepted for all numeric option fields.""" + """Zero values must be accepted for non-negative option fields (retries, delays, timeouts).""" opts = StreamingClientOptions( api_key="test", - connect_timeout=0, + connect_timeout=0.1, max_connection_retries=0, connection_retry_delay=0, terminate_timeout=0, ) - assert opts.connect_timeout == 0 + assert opts.connect_timeout == 0.1 assert opts.max_connection_retries == 0 assert opts.connection_retry_delay == 0 assert opts.terminate_timeout == 0