Skip to content

Commit 78846e8

Browse files
committed
chore: unfreeze files pending regen
Swap temporarily frozen .fernignore entries to .bak paths so the Fern generator can overwrite the originals. Our manually-patched versions are preserved as .bak files for post-regen comparison.
1 parent 9f06d65 commit 78846e8

13 files changed

+1777
-13
lines changed

.fernignore

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,39 @@ src/deepgram/client.py
66

77
# WireMock mappings: removed duplicate empty-body /v1/listen stub that causes
88
# non-deterministic matching failures
9-
wiremock/wiremock-mappings.json
9+
# [temporarily frozen — .bak preserves our patch during regen]
10+
wiremock/wiremock-mappings.json.bak
1011

1112
# Wire test with manual fix: transcribe_file() requires request=bytes parameter
12-
tests/wire/test_listen_v1_media.py
13+
# [temporarily frozen — .bak preserves our patch during regen]
14+
tests/wire/test_listen_v1_media.py.bak
1315

1416
# WebSocket socket clients:
1517
# - Optional message parameter defaults for send_flush, send_close, send_clear,
1618
# send_finalize, send_close_stream, send_keep_alive
1719
# - construct_type instead of parse_obj_as (skip_validation for unknown WS messages)
1820
# - except Exception (broad catch for custom transports)
1921
# - _sanitize_numeric_types in agent socket client (float→int for API)
20-
src/deepgram/speak/v1/socket_client.py
21-
src/deepgram/listen/v1/socket_client.py
22-
src/deepgram/listen/v2/socket_client.py
23-
src/deepgram/agent/v1/socket_client.py
22+
# [temporarily frozen — .bak preserves our patches during regen]
23+
src/deepgram/speak/v1/socket_client.py.bak
24+
src/deepgram/listen/v1/socket_client.py.bak
25+
src/deepgram/listen/v2/socket_client.py.bak
26+
src/deepgram/agent/v1/socket_client.py.bak
2427

2528
# Type files with manual int type corrections (Fern generates float for speaker/channel/num_words)
26-
src/deepgram/types/listen_v1response_results_utterances_item.py
27-
src/deepgram/types/listen_v1response_results_utterances_item_words_item.py
28-
src/deepgram/types/listen_v1response_results_channels_item_alternatives_item_paragraphs_paragraphs_item.py
29+
# [temporarily frozen — .bak preserves our patches during regen]
30+
src/deepgram/types/listen_v1response_results_utterances_item.py.bak
31+
src/deepgram/types/listen_v1response_results_utterances_item_words_item.py.bak
32+
src/deepgram/types/listen_v1response_results_channels_item_alternatives_item_paragraphs_paragraphs_item.py.bak
2933

3034
# Redact type with Union[str, Sequence[str]] support (Fern narrows to Union[Literal, Any])
31-
src/deepgram/types/listen_v1redact.py
35+
# [temporarily frozen — .bak preserves our patch during regen]
36+
src/deepgram/types/listen_v1redact.py.bak
3237

3338
# Listen client files with Union[str, Sequence[str]] array param support
34-
src/deepgram/listen/v1/client.py
35-
src/deepgram/listen/v2/client.py
39+
# [temporarily frozen — .bak preserves our patches during regen]
40+
src/deepgram/listen/v1/client.py.bak
41+
src/deepgram/listen/v2/client.py.bak
3642

3743
# Hand-written custom tests
3844
tests/custom/test_text_builder.py
@@ -75,4 +81,4 @@ AGENTS.md
7581
# Folders to ignore
7682
.github
7783
docs
78-
examples
84+
examples
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# This file was auto-generated by Fern from our API Definition.
2+
3+
import json
4+
import typing
5+
from json.decoder import JSONDecodeError
6+
7+
import websockets
8+
import websockets.sync.connection as websockets_sync_connection
9+
from ...core.events import EventEmitterMixin, EventType
10+
from ...core.unchecked_base_model import construct_type
11+
from .types.agent_v1agent_audio_done import AgentV1AgentAudioDone
12+
from .types.agent_v1agent_started_speaking import AgentV1AgentStartedSpeaking
13+
from .types.agent_v1agent_thinking import AgentV1AgentThinking
14+
from .types.agent_v1conversation_text import AgentV1ConversationText
15+
from .types.agent_v1error import AgentV1Error
16+
from .types.agent_v1function_call_request import AgentV1FunctionCallRequest
17+
from .types.agent_v1inject_agent_message import AgentV1InjectAgentMessage
18+
from .types.agent_v1inject_user_message import AgentV1InjectUserMessage
19+
from .types.agent_v1injection_refused import AgentV1InjectionRefused
20+
from .types.agent_v1keep_alive import AgentV1KeepAlive
21+
from .types.agent_v1prompt_updated import AgentV1PromptUpdated
22+
from .types.agent_v1receive_function_call_response import AgentV1ReceiveFunctionCallResponse
23+
from .types.agent_v1send_function_call_response import AgentV1SendFunctionCallResponse
24+
from .types.agent_v1settings import AgentV1Settings
25+
from .types.agent_v1settings_applied import AgentV1SettingsApplied
26+
from .types.agent_v1speak_updated import AgentV1SpeakUpdated
27+
from .types.agent_v1update_prompt import AgentV1UpdatePrompt
28+
from .types.agent_v1update_speak import AgentV1UpdateSpeak
29+
from .types.agent_v1user_started_speaking import AgentV1UserStartedSpeaking
30+
from .types.agent_v1warning import AgentV1Warning
31+
from .types.agent_v1welcome import AgentV1Welcome
32+
33+
try:
34+
from websockets.legacy.client import WebSocketClientProtocol # type: ignore
35+
except ImportError:
36+
from websockets import WebSocketClientProtocol # type: ignore
37+
38+
def _sanitize_numeric_types(obj: typing.Any) -> typing.Any:
39+
"""
40+
Recursively convert float values that are whole numbers to int.
41+
42+
Workaround for Fern-generated models that type integer API fields
43+
(like sample_rate) as float, causing JSON serialization to produce
44+
values like 44100.0 instead of 44100. The Deepgram API rejects
45+
float representations of integer fields.
46+
47+
See: https://github.com/deepgram/internal-api-specs/issues/205
48+
"""
49+
if isinstance(obj, dict):
50+
return {k: _sanitize_numeric_types(v) for k, v in obj.items()}
51+
elif isinstance(obj, list):
52+
return [_sanitize_numeric_types(item) for item in obj]
53+
elif isinstance(obj, float) and obj.is_integer():
54+
return int(obj)
55+
return obj
56+
57+
58+
V1SocketClientResponse = typing.Union[
59+
AgentV1ReceiveFunctionCallResponse,
60+
AgentV1PromptUpdated,
61+
AgentV1SpeakUpdated,
62+
AgentV1InjectionRefused,
63+
AgentV1Welcome,
64+
AgentV1SettingsApplied,
65+
AgentV1ConversationText,
66+
AgentV1UserStartedSpeaking,
67+
AgentV1AgentThinking,
68+
AgentV1FunctionCallRequest,
69+
AgentV1AgentStartedSpeaking,
70+
AgentV1AgentAudioDone,
71+
AgentV1Error,
72+
AgentV1Warning,
73+
bytes,
74+
]
75+
76+
77+
class AsyncV1SocketClient(EventEmitterMixin):
78+
def __init__(self, *, websocket: WebSocketClientProtocol):
79+
super().__init__()
80+
self._websocket = websocket
81+
82+
async def __aiter__(self):
83+
async for message in self._websocket:
84+
if isinstance(message, bytes):
85+
yield message
86+
else:
87+
yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore
88+
89+
async def start_listening(self):
90+
"""
91+
Start listening for messages on the websocket connection.
92+
93+
Emits events in the following order:
94+
- EventType.OPEN when connection is established
95+
- EventType.MESSAGE for each message received
96+
- EventType.ERROR if an error occurs
97+
- EventType.CLOSE when connection is closed
98+
"""
99+
await self._emit_async(EventType.OPEN, None)
100+
try:
101+
async for raw_message in self._websocket:
102+
if isinstance(raw_message, bytes):
103+
parsed = raw_message
104+
else:
105+
json_data = json.loads(raw_message)
106+
parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore
107+
await self._emit_async(EventType.MESSAGE, parsed)
108+
except Exception as exc:
109+
await self._emit_async(EventType.ERROR, exc)
110+
finally:
111+
await self._emit_async(EventType.CLOSE, None)
112+
113+
async def send_settings(self, message: AgentV1Settings) -> None:
114+
"""
115+
Send a message to the websocket connection.
116+
The message will be sent as a AgentV1Settings.
117+
"""
118+
await self._send_model(message)
119+
120+
async def send_update_speak(self, message: AgentV1UpdateSpeak) -> None:
121+
"""
122+
Send a message to the websocket connection.
123+
The message will be sent as a AgentV1UpdateSpeak.
124+
"""
125+
await self._send_model(message)
126+
127+
async def send_inject_user_message(self, message: AgentV1InjectUserMessage) -> None:
128+
"""
129+
Send a message to the websocket connection.
130+
The message will be sent as a AgentV1InjectUserMessage.
131+
"""
132+
await self._send_model(message)
133+
134+
async def send_inject_agent_message(self, message: AgentV1InjectAgentMessage) -> None:
135+
"""
136+
Send a message to the websocket connection.
137+
The message will be sent as a AgentV1InjectAgentMessage.
138+
"""
139+
await self._send_model(message)
140+
141+
async def send_function_call_response(self, message: AgentV1SendFunctionCallResponse) -> None:
142+
"""
143+
Send a message to the websocket connection.
144+
The message will be sent as a AgentV1SendFunctionCallResponse.
145+
"""
146+
await self._send_model(message)
147+
148+
async def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None:
149+
"""
150+
Send a message to the websocket connection.
151+
The message will be sent as a AgentV1KeepAlive.
152+
"""
153+
await self._send_model(message or AgentV1KeepAlive())
154+
155+
async def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None:
156+
"""
157+
Send a message to the websocket connection.
158+
The message will be sent as a AgentV1UpdatePrompt.
159+
"""
160+
await self._send_model(message)
161+
162+
async def send_media(self, message: bytes) -> None:
163+
"""
164+
Send a message to the websocket connection.
165+
The message will be sent as a bytes.
166+
"""
167+
await self._send(message)
168+
169+
async def recv(self) -> V1SocketClientResponse:
170+
"""
171+
Receive a message from the websocket connection.
172+
"""
173+
data = await self._websocket.recv()
174+
if isinstance(data, bytes):
175+
return data # type: ignore
176+
json_data = json.loads(data)
177+
return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore
178+
179+
async def _send(self, data: typing.Any) -> None:
180+
"""
181+
Send a message to the websocket connection.
182+
"""
183+
if isinstance(data, dict):
184+
data = json.dumps(data)
185+
await self._websocket.send(data)
186+
187+
async def _send_model(self, data: typing.Any) -> None:
188+
"""
189+
Send a Pydantic model to the websocket connection.
190+
"""
191+
await self._send(_sanitize_numeric_types(data.dict()))
192+
193+
194+
class V1SocketClient(EventEmitterMixin):
195+
def __init__(self, *, websocket: websockets_sync_connection.Connection):
196+
super().__init__()
197+
self._websocket = websocket
198+
199+
def __iter__(self):
200+
for message in self._websocket:
201+
if isinstance(message, bytes):
202+
yield message
203+
else:
204+
yield construct_type(type_=V1SocketClientResponse, object_=json.loads(message)) # type: ignore
205+
206+
def start_listening(self):
207+
"""
208+
Start listening for messages on the websocket connection.
209+
210+
Emits events in the following order:
211+
- EventType.OPEN when connection is established
212+
- EventType.MESSAGE for each message received
213+
- EventType.ERROR if an error occurs
214+
- EventType.CLOSE when connection is closed
215+
"""
216+
self._emit(EventType.OPEN, None)
217+
try:
218+
for raw_message in self._websocket:
219+
if isinstance(raw_message, bytes):
220+
parsed = raw_message
221+
else:
222+
json_data = json.loads(raw_message)
223+
parsed = construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore
224+
self._emit(EventType.MESSAGE, parsed)
225+
except Exception as exc:
226+
self._emit(EventType.ERROR, exc)
227+
finally:
228+
self._emit(EventType.CLOSE, None)
229+
230+
def send_settings(self, message: AgentV1Settings) -> None:
231+
"""
232+
Send a message to the websocket connection.
233+
The message will be sent as a AgentV1Settings.
234+
"""
235+
self._send_model(message)
236+
237+
def send_update_speak(self, message: AgentV1UpdateSpeak) -> None:
238+
"""
239+
Send a message to the websocket connection.
240+
The message will be sent as a AgentV1UpdateSpeak.
241+
"""
242+
self._send_model(message)
243+
244+
def send_inject_user_message(self, message: AgentV1InjectUserMessage) -> None:
245+
"""
246+
Send a message to the websocket connection.
247+
The message will be sent as a AgentV1InjectUserMessage.
248+
"""
249+
self._send_model(message)
250+
251+
def send_inject_agent_message(self, message: AgentV1InjectAgentMessage) -> None:
252+
"""
253+
Send a message to the websocket connection.
254+
The message will be sent as a AgentV1InjectAgentMessage.
255+
"""
256+
self._send_model(message)
257+
258+
def send_function_call_response(self, message: AgentV1SendFunctionCallResponse) -> None:
259+
"""
260+
Send a message to the websocket connection.
261+
The message will be sent as a AgentV1SendFunctionCallResponse.
262+
"""
263+
self._send_model(message)
264+
265+
def send_keep_alive(self, message: typing.Optional[AgentV1KeepAlive] = None) -> None:
266+
"""
267+
Send a message to the websocket connection.
268+
The message will be sent as a AgentV1KeepAlive.
269+
"""
270+
self._send_model(message or AgentV1KeepAlive())
271+
272+
def send_update_prompt(self, message: AgentV1UpdatePrompt) -> None:
273+
"""
274+
Send a message to the websocket connection.
275+
The message will be sent as a AgentV1UpdatePrompt.
276+
"""
277+
self._send_model(message)
278+
279+
def send_media(self, message: bytes) -> None:
280+
"""
281+
Send a message to the websocket connection.
282+
The message will be sent as a bytes.
283+
"""
284+
self._send(message)
285+
286+
def recv(self) -> V1SocketClientResponse:
287+
"""
288+
Receive a message from the websocket connection.
289+
"""
290+
data = self._websocket.recv()
291+
if isinstance(data, bytes):
292+
return data # type: ignore
293+
json_data = json.loads(data)
294+
return construct_type(type_=V1SocketClientResponse, object_=json_data) # type: ignore
295+
296+
def _send(self, data: typing.Any) -> None:
297+
"""
298+
Send a message to the websocket connection.
299+
"""
300+
if isinstance(data, dict):
301+
data = json.dumps(data)
302+
self._websocket.send(data)
303+
304+
def _send_model(self, data: typing.Any) -> None:
305+
"""
306+
Send a Pydantic model to the websocket connection.
307+
"""
308+
self._send(_sanitize_numeric_types(data.dict()))

0 commit comments

Comments
 (0)