Skip to content

Commit

Permalink
Refactor Live Client
Browse files Browse the repository at this point in the history
  • Loading branch information
dvonthenen committed Nov 22, 2023
1 parent bd398fc commit 4113b70
Show file tree
Hide file tree
Showing 30 changed files with 860 additions and 259 deletions.
8 changes: 1 addition & 7 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
DG_API_KEY=""
DG_API_KEY_MANAGE=""
DG_PROJECT_ID="85bb84f9-02d8-742a-df82-"
DG_API_KEY_ID=""
DG_MEMBER_ID=""
DG_REQUEST_ID=""
DG_BALANCE_ID=""
EMAIL=""
DG_PROJECT_ID=""
8 changes: 5 additions & 3 deletions deepgram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
__version__ = '0.0.0'

# entry point for the deepgram python sdk
from .client import DeepgramClient
from .client import DeepgramClient, DeepgramApiKeyError
from .options import DeepgramClientOptions
from .errors import DeepgramError, DeepgramApiError, DeepgramUnknownApiError

# live
from .clients.live.enums import LiveTranscriptionEvents
from .clients.live.client import LiveClient, LiveOptions
from .clients.live.client import LiveClient, LegacyLiveClient, LiveOptions

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

# manage
from .clients.manage.client import ManageClient, ProjectOptions, KeyOptions, ScopeOptions, InviteOptions, UsageRequestOptions, UsageSummaryOptions, UsageFieldsOptions

# utilities
from .audio.microphone.microphone import Microphone
20 changes: 20 additions & 0 deletions deepgram/audio/microphone/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright 2023 Deepgram SDK contributors. All Rights Reserved.
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
# SPDX-License-Identifier: MIT

class DeepgramMicrophoneError(Exception):
"""
Exception raised for known errors related to Microphone library.
Attributes:
message (str): The error message describing the exception.
status (str): The HTTP status associated with the API error.
original_error (str - json): The original error that was raised.
"""
def __init__(self, message: str):
super().__init__(message)
self.name = "DeepgramMicrophoneError"
self.message = message

def __str__(self):
return f"{self.name}: {self.message}"
89 changes: 89 additions & 0 deletions deepgram/audio/microphone/microphone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright 2023 Deepgram SDK contributors. All Rights Reserved.
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
# SPDX-License-Identifier: MIT

import inspect
import asyncio
import threading
import pyaudio
from array import array
from sys import byteorder

from .errors import DeepgramMicrophoneError

FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 16000
CHUNK = 8000

class Microphone:
"""
TODO
"""
def __init__(self, push_callback, format=FORMAT, rate=RATE, chunk=CHUNK, channels=CHANNELS):
self.audio = pyaudio.PyAudio()
self.chunk = chunk
self.rate = rate
self.format = format
self.channels = channels
self.push_callback = push_callback
self.stream = None

def is_active(self):
if self.stream is None:
return False
return self.stream.is_active()

def start(self):
if self.stream is not None:
raise DeepgramMicrophoneError("Microphone already started")

self.stream = self.audio.open(
format=self.format,
channels=self.channels,
rate=self.rate,
input=True,
frames_per_buffer=CHUNK,
)

self.exit = False
self.lock = threading.Lock()

self.stream.start_stream()
self.thread = threading.Thread(target=self.processing)
self.thread.start()

def processing(self):
try:
while True:
data = self.stream.read(self.chunk)

self.lock.acquire()
localExit = self.exit
self.lock.release()
if localExit:
break
if data is None:
continue

if inspect.iscoroutinefunction(self.push_callback):
asyncio.run(self.push_callback(data))
else:
self.push_callback(data)

except Exception as e:
print(f"Error while sending: {str(e)}")
raise

def finish(self):
self.lock.acquire()
self.exit = True
self.lock.release()

self.thread.join()
self.thread = None

self.stream.stop_stream()
self.stream.close()
self.stream = None

44 changes: 14 additions & 30 deletions deepgram/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from typing import Optional

from .options import DeepgramClientOptions
from .errors import DeepgramError
from .errors import DeepgramApiKeyError

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

self.api_key = api_key

"""
This block is responsible for determining the client's configuration options and headers based on the provided or default settings.
"""

if config_options is None: # Use default configuration
self.config_options = DeepgramClientOptions(self.api_key).global_options
self.headers = DeepgramClientOptions(self.api_key).global_options['headers']
else: # Use custom configuration
self.config_options = config_options['global_options']
if config_options['global_options'].get('headers'):
self.headers = {**config_options['global_options']['headers'], **DeepgramClientOptions(self.api_key).global_options['headers']}
else:
self.headers = DeepgramClientOptions(self.api_key).global_options['headers']
self.url = self._get_url(self.config_options)

def _get_url(self, config_options):
url = config_options['url']
if not re.match(r'^https?://', url, re.IGNORECASE):
url = 'https://' + url
return url.strip('/')
if config is None: # Use default configuration
self.config = DeepgramClientOptions(self.api_key)
else:
config.set_apikey(self.api_key)
self.config = config

@property
def listen(self):
return ListenClient(self.url, self.api_key, self.headers)
return ListenClient(self.config)

@property
def manage(self):
return ManageClient(self.url, self.headers)
return ManageClient(self.config)

# FUTURE VERSIONINING:
# @property
# def manage_v1(self):
# return ManageClientV1(self.url, self.headers)
# return ManageClientV1(self.config)

@property
def onprem(self):
return OnPremClient(self.url, self.headers)
return OnPremClient(self.config)

# FUTURE VERSIONINING:
# @property
# def onprem_v1(self):
# return OnPremClientV1(self.url, self.headers)
# return OnPremClientV1(self.config)
26 changes: 12 additions & 14 deletions deepgram/clients/abstract_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
import json
from typing import Dict, Any, Optional

from ..errors import DeepgramApiError, DeepgramUnknownApiError
from ..options import DeepgramClientOptions
from .errors import DeepgramError, DeepgramApiError, DeepgramUnknownApiError

class AbstractRestfulClient:
"""
Expand All @@ -28,30 +29,27 @@ class AbstractRestfulClient:
DeepgramApiError: Raised for known API errors.
DeepgramUnknownApiError: Raised for unknown API errors.
"""
def __init__(self, url: Dict[str, str], headers: Optional[Dict[str, Any]]):
self.url = url
def __init__(self, config: DeepgramClientOptions):
if config is None:
raise DeepgramError("Config are required")

self.config = config
self.client = httpx.AsyncClient()
self.headers = headers

async def get(self, url: str, options=None):
headers = self.headers
return await self._handle_request('GET', url, params=options, headers=headers)
return await self._handle_request('GET', url, params=options, headers=self.config.headers)

async def post(self, url: str, options=None, **kwargs):
headers = self.headers
return await self._handle_request('POST', url, params=options, headers=headers, **kwargs)
return await self._handle_request('POST', url, params=options, headers=self.config.headers, **kwargs)

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

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

async def delete(self, url: str):
headers = self.headers
return await self._handle_request('DELETE', url, headers=headers)
return await self._handle_request('DELETE', url, headers=self.config.headers)

async def _handle_request(self, method, url, **kwargs):
try:
Expand Down
55 changes: 55 additions & 0 deletions deepgram/clients/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2023 Deepgram SDK contributors. All Rights Reserved.
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
# SPDX-License-Identifier: MIT

class DeepgramError(Exception):
"""
Exception raised for unknown errors related to the Deepgram API.
Attributes:
message (str): The error message describing the exception.
status (str): The HTTP status associated with the API error.
"""
def __init__(self, message: str):
super().__init__(message)
self.name = "DeepgramError"
self.message = message

def __str__(self):
return f"{self.name}: {self.message}"

class DeepgramApiError(Exception):
"""
Exception raised for known errors (in json response format) related to the Deepgram API.
Attributes:
message (str): The error message describing the exception.
status (str): The HTTP status associated with the API error.
original_error (str - json): The original error that was raised.
"""
def __init__(self, message: str, status: str, original_error = None):
super().__init__(message)
self.name = "DeepgramApiError"
self.status = status
self.message = message
self.original_error = original_error

def __str__(self):
return f"{self.name}: {self.message} (Status: {self.status})"

class DeepgramUnknownApiError(Exception):
"""
Exception raised for unknown errors related to the Deepgram API.
Attributes:
message (str): The error message describing the exception.
status (str): The HTTP status associated with the API error.
"""
def __init__(self, message: str, status: str):
super().__init__(message, status)
self.name = "DeepgramUnknownApiError"
self.status = status
self.message = message

def __str__(self):
return f"{self.name}: {self.message} (Status: {self.status})"
21 changes: 12 additions & 9 deletions deepgram/clients/listen.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,33 @@
# Use of this source code is governed by a MIT license that can be found in the LICENSE file.
# SPDX-License-Identifier: MIT

from ..options import DeepgramClientOptions
from .prerecorded.client import PreRecordedClient # FUTURE VERSIONINING:, PreRecordedClientV1
from .live.client import LiveClient # FUTURE VERSIONINING:, LiveClientV1
from .live.client import LiveClient, LegacyLiveClient # FUTURE VERSIONINING:, LiveClientV1
from typing import Dict, Any, Optional

class ListenClient:
def __init__(self, url: str, api_key: str, headers: Optional[Dict[str, Any]]):
self.url = url
self.api_key = api_key
self.headers = headers
def __init__(self, config: DeepgramClientOptions):
self.config = config

@property
def prerecorded(self):
return PreRecordedClient(self.url, self.headers)
return PreRecordedClient(self.config)

# FUTURE VERSIONINING:
# @property
# def prerecorded_v1(self):
# return PreRecordedClientV1(self.url, self.headers)
# return PreRecordedClientV1(self.config)

@property
def live(self):
return LiveClient(self.url, self.api_key, self.headers)
return LiveClient(self.config)

@property
def legacylive(self):
return LegacyLiveClient(self.config)

# FUTURE VERSIONINING:
# @property
# def live_v1(self):
# return LiveClientV1(self.url, self.api_key, self.headers)
# return LiveClientV1(self.config)
Loading

0 comments on commit 4113b70

Please sign in to comment.