Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Live Client for v3 #186

Merged
merged 1 commit into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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