Skip to content

Commit 965100d

Browse files
Merge pull request #186 from dvonthenen/refactor-live-client
Refactor Live Client for v3
2 parents bd398fc + 2870f22 commit 965100d

30 files changed

+859
-259
lines changed

.env.example

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,2 @@
11
DG_API_KEY=""
2-
DG_API_KEY_MANAGE=""
3-
DG_PROJECT_ID="85bb84f9-02d8-742a-df82-"
4-
DG_API_KEY_ID=""
5-
DG_MEMBER_ID=""
6-
DG_REQUEST_ID=""
7-
DG_BALANCE_ID=""
8-
EMAIL=""
2+
DG_PROJECT_ID=""

deepgram/__init__.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@
66
__version__ = '0.0.0'
77

88
# entry point for the deepgram python sdk
9-
from .client import DeepgramClient
9+
from .client import DeepgramClient, DeepgramApiKeyError
1010
from .options import DeepgramClientOptions
11-
from .errors import DeepgramError, DeepgramApiError, DeepgramUnknownApiError
1211

1312
# live
1413
from .clients.live.enums import LiveTranscriptionEvents
15-
from .clients.live.client import LiveClient, LiveOptions
14+
from .clients.live.client import LiveClient, LegacyLiveClient, LiveOptions
1615

1716
# prerecorded
1817
from .clients.prerecorded.client import PreRecordedClient, PrerecordedOptions, PrerecordedSource, FileSource, UrlSource
1918

2019
# manage
2120
from .clients.manage.client import ManageClient, ProjectOptions, KeyOptions, ScopeOptions, InviteOptions, UsageRequestOptions, UsageSummaryOptions, UsageFieldsOptions
21+
22+
# utilities
23+
from .audio.microphone.microphone import Microphone
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2023 Deepgram SDK contributors. All Rights Reserved.
2+
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
3+
# SPDX-License-Identifier: MIT
4+
5+
class DeepgramMicrophoneError(Exception):
6+
"""
7+
Exception raised for known errors related to Microphone library.
8+
9+
Attributes:
10+
message (str): The error message describing the exception.
11+
status (str): The HTTP status associated with the API error.
12+
original_error (str - json): The original error that was raised.
13+
"""
14+
def __init__(self, message: str):
15+
super().__init__(message)
16+
self.name = "DeepgramMicrophoneError"
17+
self.message = message
18+
19+
def __str__(self):
20+
return f"{self.name}: {self.message}"
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Copyright 2023 Deepgram SDK contributors. All Rights Reserved.
2+
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
3+
# SPDX-License-Identifier: MIT
4+
5+
import inspect
6+
import asyncio
7+
import threading
8+
import pyaudio
9+
from array import array
10+
from sys import byteorder
11+
12+
from .errors import DeepgramMicrophoneError
13+
14+
FORMAT = pyaudio.paInt16
15+
CHANNELS = 1
16+
RATE = 16000
17+
CHUNK = 8000
18+
19+
class Microphone:
20+
"""
21+
TODO
22+
"""
23+
def __init__(self, push_callback, format=FORMAT, rate=RATE, chunk=CHUNK, channels=CHANNELS):
24+
self.audio = pyaudio.PyAudio()
25+
self.chunk = chunk
26+
self.rate = rate
27+
self.format = format
28+
self.channels = channels
29+
self.push_callback = push_callback
30+
self.stream = None
31+
32+
def is_active(self):
33+
if self.stream is None:
34+
return False
35+
return self.stream.is_active()
36+
37+
def start(self):
38+
if self.stream is not None:
39+
raise DeepgramMicrophoneError("Microphone already started")
40+
41+
self.stream = self.audio.open(
42+
format=self.format,
43+
channels=self.channels,
44+
rate=self.rate,
45+
input=True,
46+
frames_per_buffer=CHUNK,
47+
)
48+
49+
self.exit = False
50+
self.lock = threading.Lock()
51+
52+
self.stream.start_stream()
53+
self.thread = threading.Thread(target=self.processing)
54+
self.thread.start()
55+
56+
def processing(self):
57+
try:
58+
while True:
59+
data = self.stream.read(self.chunk)
60+
61+
self.lock.acquire()
62+
localExit = self.exit
63+
self.lock.release()
64+
if localExit:
65+
break
66+
if data is None:
67+
continue
68+
69+
if inspect.iscoroutinefunction(self.push_callback):
70+
asyncio.run(self.push_callback(data))
71+
else:
72+
self.push_callback(data)
73+
74+
except Exception as e:
75+
print(f"Error while sending: {str(e)}")
76+
raise
77+
78+
def finish(self):
79+
self.lock.acquire()
80+
self.exit = True
81+
self.lock.release()
82+
83+
self.thread.join()
84+
self.thread = None
85+
86+
self.stream.stop_stream()
87+
self.stream.close()
88+
self.stream = None
89+

deepgram/client.py

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from typing import Optional
88

99
from .options import DeepgramClientOptions
10-
from .errors import DeepgramError
10+
from .errors import DeepgramApiKeyError
1111

1212
from .clients.listen import ListenClient
1313
from .clients.manage.client import ManageClient # FUTURE VERSIONINING:, ManageClientV1
@@ -24,59 +24,43 @@ class DeepgramClient:
2424
config_options (DeepgramClientOptions): An optional configuration object specifying client options.
2525
2626
Raises:
27-
DeepgramError: If the API key is missing or invalid.
27+
DeepgramApiKeyError: If the API key is missing or invalid.
2828
2929
Methods:
3030
listen: Returns a ListenClient instance for interacting with Deepgram's transcription services.
3131
manage: Returns a ManageClient instance for managing Deepgram resources.
3232
onprem: Returns an OnPremClient instance for interacting with Deepgram's on-premises API.
3333
3434
"""
35-
def __init__(self, api_key: str, config_options: Optional[DeepgramClientOptions] = None):
35+
def __init__(self, api_key: str, config: Optional[DeepgramClientOptions] = None):
3636
if not api_key:
37-
raise DeepgramError("Deepgram API key is required")
37+
raise DeepgramApiKeyError("Deepgram API key is required")
3838

3939
self.api_key = api_key
40-
41-
"""
42-
This block is responsible for determining the client's configuration options and headers based on the provided or default settings.
43-
"""
44-
45-
if config_options is None: # Use default configuration
46-
self.config_options = DeepgramClientOptions(self.api_key).global_options
47-
self.headers = DeepgramClientOptions(self.api_key).global_options['headers']
48-
else: # Use custom configuration
49-
self.config_options = config_options['global_options']
50-
if config_options['global_options'].get('headers'):
51-
self.headers = {**config_options['global_options']['headers'], **DeepgramClientOptions(self.api_key).global_options['headers']}
52-
else:
53-
self.headers = DeepgramClientOptions(self.api_key).global_options['headers']
54-
self.url = self._get_url(self.config_options)
55-
56-
def _get_url(self, config_options):
57-
url = config_options['url']
58-
if not re.match(r'^https?://', url, re.IGNORECASE):
59-
url = 'https://' + url
60-
return url.strip('/')
40+
if config is None: # Use default configuration
41+
self.config = DeepgramClientOptions(self.api_key)
42+
else:
43+
config.set_apikey(self.api_key)
44+
self.config = config
6145

6246
@property
6347
def listen(self):
64-
return ListenClient(self.url, self.api_key, self.headers)
48+
return ListenClient(self.config)
6549

6650
@property
6751
def manage(self):
68-
return ManageClient(self.url, self.headers)
52+
return ManageClient(self.config)
6953

7054
# FUTURE VERSIONINING:
7155
# @property
7256
# def manage_v1(self):
73-
# return ManageClientV1(self.url, self.headers)
57+
# return ManageClientV1(self.config)
7458

7559
@property
7660
def onprem(self):
77-
return OnPremClient(self.url, self.headers)
61+
return OnPremClient(self.config)
7862

7963
# FUTURE VERSIONINING:
8064
# @property
8165
# def onprem_v1(self):
82-
# return OnPremClientV1(self.url, self.headers)
66+
# return OnPremClientV1(self.config)

deepgram/clients/abstract_client.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import json
77
from typing import Dict, Any, Optional
88

9-
from ..errors import DeepgramApiError, DeepgramUnknownApiError
9+
from ..options import DeepgramClientOptions
10+
from .errors import DeepgramError, DeepgramApiError, DeepgramUnknownApiError
1011

1112
class AbstractRestfulClient:
1213
"""
@@ -28,30 +29,27 @@ class AbstractRestfulClient:
2829
DeepgramApiError: Raised for known API errors.
2930
DeepgramUnknownApiError: Raised for unknown API errors.
3031
"""
31-
def __init__(self, url: Dict[str, str], headers: Optional[Dict[str, Any]]):
32-
self.url = url
32+
def __init__(self, config: DeepgramClientOptions):
33+
if config is None:
34+
raise DeepgramError("Config are required")
35+
36+
self.config = config
3337
self.client = httpx.AsyncClient()
34-
self.headers = headers
3538

3639
async def get(self, url: str, options=None):
37-
headers = self.headers
38-
return await self._handle_request('GET', url, params=options, headers=headers)
40+
return await self._handle_request('GET', url, params=options, headers=self.config.headers)
3941

4042
async def post(self, url: str, options=None, **kwargs):
41-
headers = self.headers
42-
return await self._handle_request('POST', url, params=options, headers=headers, **kwargs)
43+
return await self._handle_request('POST', url, params=options, headers=self.config.headers, **kwargs)
4344

4445
async def put(self, url: str, options=None, **kwargs):
45-
headers = self.headers
46-
return await self._handle_request('PUT', url, params=options, headers=headers, **kwargs)
46+
return await self._handle_request('PUT', url, params=options, headers=self.config.headers, **kwargs)
4747

4848
async def patch(self, url: str, options=None, **kwargs):
49-
headers = self.headers
50-
return await self._handle_request('PATCH', url, params=options, headers=headers, **kwargs)
49+
return await self._handle_request('PATCH', url, params=options, headers=self.config.headers, **kwargs)
5150

5251
async def delete(self, url: str):
53-
headers = self.headers
54-
return await self._handle_request('DELETE', url, headers=headers)
52+
return await self._handle_request('DELETE', url, headers=self.config.headers)
5553

5654
async def _handle_request(self, method, url, **kwargs):
5755
try:

deepgram/clients/errors.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Copyright 2023 Deepgram SDK contributors. All Rights Reserved.
2+
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
3+
# SPDX-License-Identifier: MIT
4+
5+
class DeepgramError(Exception):
6+
"""
7+
Exception raised for unknown errors related to the Deepgram API.
8+
9+
Attributes:
10+
message (str): The error message describing the exception.
11+
status (str): The HTTP status associated with the API error.
12+
"""
13+
def __init__(self, message: str):
14+
super().__init__(message)
15+
self.name = "DeepgramError"
16+
self.message = message
17+
18+
def __str__(self):
19+
return f"{self.name}: {self.message}"
20+
21+
class DeepgramApiError(Exception):
22+
"""
23+
Exception raised for known errors (in json response format) related to the Deepgram API.
24+
25+
Attributes:
26+
message (str): The error message describing the exception.
27+
status (str): The HTTP status associated with the API error.
28+
original_error (str - json): The original error that was raised.
29+
"""
30+
def __init__(self, message: str, status: str, original_error = None):
31+
super().__init__(message)
32+
self.name = "DeepgramApiError"
33+
self.status = status
34+
self.message = message
35+
self.original_error = original_error
36+
37+
def __str__(self):
38+
return f"{self.name}: {self.message} (Status: {self.status})"
39+
40+
class DeepgramUnknownApiError(Exception):
41+
"""
42+
Exception raised for unknown errors related to the Deepgram API.
43+
44+
Attributes:
45+
message (str): The error message describing the exception.
46+
status (str): The HTTP status associated with the API error.
47+
"""
48+
def __init__(self, message: str, status: str):
49+
super().__init__(message, status)
50+
self.name = "DeepgramUnknownApiError"
51+
self.status = status
52+
self.message = message
53+
54+
def __str__(self):
55+
return f"{self.name}: {self.message} (Status: {self.status})"

deepgram/clients/listen.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,33 @@
22
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
33
# SPDX-License-Identifier: MIT
44

5+
from ..options import DeepgramClientOptions
56
from .prerecorded.client import PreRecordedClient # FUTURE VERSIONINING:, PreRecordedClientV1
6-
from .live.client import LiveClient # FUTURE VERSIONINING:, LiveClientV1
7+
from .live.client import LiveClient, LegacyLiveClient # FUTURE VERSIONINING:, LiveClientV1
78
from typing import Dict, Any, Optional
89

910
class ListenClient:
10-
def __init__(self, url: str, api_key: str, headers: Optional[Dict[str, Any]]):
11-
self.url = url
12-
self.api_key = api_key
13-
self.headers = headers
11+
def __init__(self, config: DeepgramClientOptions):
12+
self.config = config
1413

1514
@property
1615
def prerecorded(self):
17-
return PreRecordedClient(self.url, self.headers)
16+
return PreRecordedClient(self.config)
1817

1918
# FUTURE VERSIONINING:
2019
# @property
2120
# def prerecorded_v1(self):
22-
# return PreRecordedClientV1(self.url, self.headers)
21+
# return PreRecordedClientV1(self.config)
2322

2423
@property
2524
def live(self):
26-
return LiveClient(self.url, self.api_key, self.headers)
25+
return LiveClient(self.config)
26+
27+
@property
28+
def legacylive(self):
29+
return LegacyLiveClient(self.config)
2730

2831
# FUTURE VERSIONINING:
2932
# @property
3033
# def live_v1(self):
31-
# return LiveClientV1(self.url, self.api_key, self.headers)
34+
# return LiveClientV1(self.config)

0 commit comments

Comments
 (0)