From 18ea3386911d502a1aedf607862fdd3292d0d6e8 Mon Sep 17 00:00:00 2001 From: David vonThenen <12752197+dvonthenen@users.noreply.github.com> Date: Wed, 22 May 2024 18:27:45 -0700 Subject: [PATCH] PyLint --- .github/workflows/check-all.yaml | 12 +- .github/workflows/check-lint.yaml | 41 + .github/workflows/check-lint.yaml.DISABLE | 53 -- .github/workflows/check-static.yaml | 41 + .pylintrc | 719 ++++++++++++++++++ Makefile | 42 +- deepgram/__init__.py | 35 +- deepgram/audio/microphone/constants.py | 9 +- deepgram/audio/microphone/microphone.py | 237 +++--- deepgram/client.py | 205 +++-- deepgram/clients/__init__.py | 28 +- deepgram/clients/abstract_async_client.py | 153 +++- deepgram/clients/abstract_sync_client.py | 150 +++- deepgram/clients/analyze/__init__.py | 11 +- deepgram/clients/analyze/client.py | 116 +-- deepgram/clients/analyze/v1/__init__.py | 8 +- deepgram/clients/analyze/v1/async_client.py | 305 ++++---- deepgram/clients/analyze/v1/client.py | 323 ++++---- deepgram/clients/analyze/v1/helpers.py | 9 + deepgram/clients/analyze/v1/options.py | 93 +-- deepgram/clients/analyze/v1/response.py | 161 ++-- deepgram/clients/common/__init__.py | 21 + deepgram/clients/common/v1/__init__.py | 13 + .../clients/{analyze => common/v1}/enums.py | 12 +- deepgram/clients/common/v1/options.py | 65 ++ deepgram/clients/helpers.py | 6 +- deepgram/clients/listen.py | 156 ++-- deepgram/clients/live/__init__.py | 4 +- deepgram/clients/live/client.py | 105 +-- deepgram/clients/live/enums.py | 12 +- deepgram/clients/live/helpers.py | 31 +- deepgram/clients/live/v1/__init__.py | 2 +- deepgram/clients/live/v1/async_client.py | 610 +++++++++------ deepgram/clients/live/v1/client.py | 615 ++++++++------- deepgram/clients/live/v1/options.py | 78 +- deepgram/clients/live/v1/response.py | 117 ++- deepgram/clients/manage/__init__.py | 2 +- deepgram/clients/manage/client.py | 218 +----- deepgram/clients/manage/v1/__init__.py | 2 +- deepgram/clients/manage/v1/async_client.py | 567 +++++++------- deepgram/clients/manage/v1/client.py | 565 +++++++------- deepgram/clients/manage/v1/options.py | 118 +-- deepgram/clients/manage/v1/response.py | 295 ++++--- deepgram/clients/onprem/__init__.py | 2 +- deepgram/clients/onprem/client.py | 24 +- deepgram/clients/onprem/v1/__init__.py | 2 +- deepgram/clients/onprem/v1/async_client.py | 125 +-- deepgram/clients/onprem/v1/client.py | 108 ++- deepgram/clients/prerecorded/__init__.py | 6 +- deepgram/clients/prerecorded/client.py | 90 +-- deepgram/clients/prerecorded/enums.py | 16 - deepgram/clients/prerecorded/v1/__init__.py | 9 +- .../clients/prerecorded/v1/async_client.py | 321 ++++---- deepgram/clients/prerecorded/v1/client.py | 322 ++++---- deepgram/clients/prerecorded/v1/helpers.py | 9 + deepgram/clients/prerecorded/v1/options.py | 152 ++-- deepgram/clients/prerecorded/v1/response.py | 341 +++++---- deepgram/clients/read.py | 115 +-- deepgram/clients/speak/__init__.py | 6 +- deepgram/clients/speak/client.py | 64 +- deepgram/clients/speak/v1/__init__.py | 5 +- deepgram/clients/speak/v1/async_client.py | 164 ++-- deepgram/clients/speak/v1/client.py | 168 ++-- deepgram/clients/speak/v1/helpers.py | 6 + deepgram/clients/speak/v1/options.py | 63 +- deepgram/clients/speak/v1/response.py | 24 +- deepgram/options.py | 83 +- deepgram/utils/verboselogs/__init__.py | 168 ++++ .../prerecorded/direct_invocation/main.py | 8 +- .../streaming/direct_invocation/main.py | 5 +- .../streaming/microphone_inheritance/main.py | 5 +- .../streaming/mute-microphone/main.py | 7 +- examples/analyze/intent/main.py | 5 +- examples/analyze/legacy_dict_intent/main.py | 5 +- examples/analyze/sentiment/main.py | 5 +- examples/analyze/stream_intent/main.py | 5 +- examples/analyze/summary/main.py | 5 +- examples/analyze/topic/main.py | 5 +- examples/manage/balances/main.py | 5 +- examples/manage/keys/main.py | 5 +- examples/manage/usage/main.py | 5 +- .../prerecorded/callback/callback/main.py | 5 +- examples/prerecorded/file/main.py | 5 +- examples/prerecorded/intent/main.py | 5 +- examples/prerecorded/legacy_dict_url/main.py | 3 +- examples/prerecorded/sentiment/main.py | 5 +- examples/prerecorded/stream_file/main.py | 9 +- examples/prerecorded/summary/main.py | 5 +- examples/prerecorded/topic/main.py | 5 +- examples/prerecorded/url/main.py | 3 +- examples/requirements-examples.txt | 2 +- examples/speak/file/async_hello_world/main.py | 3 +- examples/speak/file/hello_world/main.py | 7 +- .../file/legacy_dict_hello_world/main.py | 3 +- examples/speak/file/woodchuck/main.py | 7 +- examples/streaming/async_http/main.py | 11 +- examples/streaming/async_microphone/main.py | 3 +- examples/streaming/http/main.py | 5 +- .../streaming/legacy_dict_microphone/main.py | 5 +- examples/streaming/microphone/main.py | 5 +- mypy.ini | 26 + pyproject.toml | 8 +- requirements-dev.txt | 9 + requirements.txt | 2 +- setup.py | 2 +- tests/edge_cases/keepalive/async/main.py | 5 +- tests/edge_cases/keepalive/sync/main.py | 5 +- .../reconnect_same_object/async/main.py | 5 +- .../reconnect_same_object/sync/main.py | 5 +- .../exercise_timeout/async/main.py | 5 +- .../exercise_timeout/sync/main.py | 5 +- 111 files changed, 5198 insertions(+), 3803 deletions(-) create mode 100644 .github/workflows/check-lint.yaml delete mode 100644 .github/workflows/check-lint.yaml.DISABLE create mode 100644 .github/workflows/check-static.yaml create mode 100644 .pylintrc create mode 100644 deepgram/clients/common/__init__.py create mode 100644 deepgram/clients/common/v1/__init__.py rename deepgram/clients/{analyze => common/v1}/enums.py (68%) create mode 100644 deepgram/clients/common/v1/options.py delete mode 100644 deepgram/clients/prerecorded/enums.py create mode 100644 deepgram/utils/verboselogs/__init__.py create mode 100644 mypy.ini diff --git a/.github/workflows/check-all.yaml b/.github/workflows/check-all.yaml index e996d019..915c464e 100644 --- a/.github/workflows/check-all.yaml +++ b/.github/workflows/check-all.yaml @@ -18,17 +18,21 @@ jobs: - name: Checkout code by commit uses: actions/checkout@v4 - - name: Set up Go 1.x - uses: actions/setup-go@v3 + - uses: actions/setup-python@v4 with: - go-version: "1.19" - id: go + python-version: '3.10' - name: Ensure dependencies installed shell: bash run: | make ensure-deps + - name: Install Dependencies + run: | + pip install -r requirements.txt + pip install -r requirements-dev.txt + pip install -r examples/requirements-examples.txt + - name: Run all checks shell: bash run: | diff --git a/.github/workflows/check-lint.yaml b/.github/workflows/check-lint.yaml new file mode 100644 index 00000000..d6b15c48 --- /dev/null +++ b/.github/workflows/check-lint.yaml @@ -0,0 +1,41 @@ +name: Check - lint + +on: + pull_request: + types: + - assigned + - opened + - synchronize + - reopened + paths: + - "deepgram/**.py" + +jobs: + checklint: + name: Check shell + # Only run this job if we're in the main repo, not a fork. + if: github.repository == 'deepgram/deepgram-python-sdk' + runs-on: ubuntu-latest + steps: + + - name: Checkout code by commit + uses: actions/checkout@v4 + + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Ensure dependencies installed + shell: bash + run: | + make ensure-deps + + - name: Install Dependencies + run: | + pip install -r requirements.txt + pip install -r requirements-dev.txt + pip install -r examples/requirements-examples.txt + + - name: Run mdlint + run: | + make lint diff --git a/.github/workflows/check-lint.yaml.DISABLE b/.github/workflows/check-lint.yaml.DISABLE deleted file mode 100644 index f3da7692..00000000 --- a/.github/workflows/check-lint.yaml.DISABLE +++ /dev/null @@ -1,53 +0,0 @@ -name: Check - lint - -on: - pull_request: - types: - - assigned - - opened - - synchronize - - reopened - paths: - - "**.go" - - "**/go.mod" - - ".golangci.yaml" - -jobs: - checklint: - name: Lint - # Only run this job if we're in the main repo, not a fork. - if: github.repository == 'deepgram/deepgram-go-sdk' - runs-on: ubuntu-latest - steps: - - - name: Checkout code by commit - uses: actions/checkout@v4 - - - name: Set up Go 1.x - uses: actions/setup-go@v3 - with: - go-version: "1.19" - id: go - - # - name: Get Date - # id: get-date - # shell: bash - # run: | - # echo "date=$(date -u "+%Y-%m")" >> $GITHUB_OUTPUT - - - name: Restore Lint Cache - uses: actions/cache@v3 - timeout-minutes: 10 - continue-on-error: true - with: - path: ${{ runner.temp }}/lint_cache - # key: ${{ runner.os }}-lint-cache-${{ steps.get-date.outputs.date }} - key: ${{ runner.os }}-lint-cache - restore-keys: | - ${{ runner.os }}-lint-cache - - - name: Run golangci-lint - env: - GOLANGCI_LINT_CACHE: ${{ runner.temp }}/lint_cache - run: | - make lint diff --git a/.github/workflows/check-static.yaml b/.github/workflows/check-static.yaml new file mode 100644 index 00000000..f692ab0a --- /dev/null +++ b/.github/workflows/check-static.yaml @@ -0,0 +1,41 @@ +name: Check - static + +on: + pull_request: + types: + - assigned + - opened + - synchronize + - reopened + paths: + - "deepgram/**.py" + +jobs: + checkstatic: + name: Check static + # Only run this job if we're in the main repo, not a fork. + if: github.repository == 'deepgram/deepgram-python-sdk' + runs-on: ubuntu-latest + steps: + + - name: Checkout code by commit + uses: actions/checkout@v4 + + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Ensure dependencies installed + shell: bash + run: | + make ensure-deps + + - name: Install Dependencies + run: | + pip install -r requirements.txt + pip install -r requirements-dev.txt + pip install -r examples/requirements-examples.txt + + - name: Run mdlint + run: | + make static diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..8a701470 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,719 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=10 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=.git + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked and +# will not be imported (useful for modules/projects where namespaces are +# manipulated during runtime and thus existing member attributes cannot be +# deduced by static analysis). It supports qualified module names, as well as +# Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=4 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Resolve imports to .pyi stubs if available. May reduce no-member messages and +# increase not-an-iterable messages. +prefer-stubs=no + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.10 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages. +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=12 + +# Maximum number of locals for function / method body. +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=6 + +# Maximum number of statements in function / method body. +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=100 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + use-implicit-booleaness-not-comparison-to-string, + use-implicit-booleaness-not-comparison-to-zero, + line-too-long, + missing-module-docstring, + too-many-arguments, + too-few-public-methods, + cyclic-import, + duplicate-code + +# sample from k8s +# disable=import-star-module-level, +# old-octal-literal, +# oct-method, +# print-statement, +# unpacking-in-except, +# parameter-unpacking, +# backtick, +# old-raise-syntax, +# old-ne-operator, +# long-suffix, +# dict-view-method, +# dict-iter-method, +# metaclass-assignment, +# next-method-called, +# raising-string, +# indexing-exception, +# raw_input-builtin, +# long-builtin, +# file-builtin, +# execfile-builtin, +# coerce-builtin, +# cmp-builtin, +# buffer-builtin, +# basestring-builtin, +# apply-builtin, +# filter-builtin-not-iterating, +# using-cmp-argument, +# useless-suppression, +# range-builtin-not-iterating, +# suppressed-message, +# missing-docstring, +# no-absolute-import, +# old-division, +# cmp-method, +# reload-builtin, +# zip-builtin-not-iterating, +# intern-builtin, +# unichr-builtin, +# reduce-builtin, +# standarderror-builtin, +# unicode-builtin, +# xrange-builtin, +# coerce-method, +# delslice-method, +# getslice-method, +# setslice-method, +# input-builtin, +# round-builtin, +# hex-method, +# nonzero-method, +# map-builtin-not-iterating, +# relative-import, +# invalid-name, +# bad-continuation, +# no-member, +# locally-disabled, +# fixme, +# import-error, +# too-many-locals, +# no-name-in-module, +# too-many-instance-attributes, +# no-self-use, +# logging-fstring-interpolation + + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + +# Let 'consider-using-join' be raised when the separator to join on would be +# non-empty (resulting in expected fixes of the type: ``"- " + " - +# ".join(items)``) +suggest-join-with-non-empty-separator=yes + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: text, parseable, colorized, +# json2 (improved json format), json (old json format) and msvs (visual +# studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values, + thread._local, + _thread._local, + argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/Makefile b/Makefile index 7b7b3c93..0696486e 100644 --- a/Makefile +++ b/Makefile @@ -34,8 +34,8 @@ version: #### display version of components @echo 'GOARCH: $(GOARCH)' @echo 'go version: $(shell go version)' -.PHONY: check mdlint shellcheck actionlint yamllint ### Performs all of the checks, lint'ing, etc available -check: mdlint shellcheck actionlint yamllint +.PHONY: check lint pylint format black blackformat lint_files lint_diff static mypy mdlint shellcheck actionlint yamllint ### Performs all of the checks, lint'ing, etc available +check: lint static mdlint shellcheck actionlint yamllint .PHONY: ensure-deps ensure-deps: #### Ensure that all required dependency utilities are downloaded or installed @@ -43,24 +43,26 @@ ensure-deps: #### Ensure that all required dependency utilities are downloaded o GO_MODULES=$(shell find . -path "*/go.mod" | xargs -I _ dirname _) -# lint: tools #### Performs Golang programming lint -# @for i in $(GO_MODULES); do \ -# echo "-- Linting $$i --"; \ -# working_dir=`pwd`; \ -# if [ "$${i}" = "." ]; then \ -# $(GOLANGCI_LINT) run -v --timeout=5m; \ -# else \ -# cd $${i}; \ -# $(MAKE) lint || exit 1; \ -# cd $$working_dir; \ -# fi; \ -# done; \ -# CHECK=$$(grep -r --include="*.go" ioutil ./); \ -# if [ -n "$${CHECK}" ]; then \ -# echo "ioutil is deprecated, use io or os replacements"; \ -# echo "$${CHECK}"; \ -# exit 1; \ -# fi +# pystatic: #### Performs Python static analysis +# pylint --rcfile .pylintrc deepgram + +PYTHON_FILES=. +lint_files: PYTHON_FILES=deepgram/ examples/ +lint_diff: PYTHON_FILES=$(shell git diff --name-only --diff-filter=d main | grep -E '\.py$$') + +lint_files lint_diff: #### Performs Python formatting + black --target-version py310 $(PYTHON_FILES) + +black blackformat format: lint_files + +pylint: lint_files #### Performs Python linting + pylint --rcfile .pylintrc deepgram + +lint: pylint #### Performs Golang programming lint + +static_files: PYTHON_FILES=deepgram/ +static mypy: #### Performs static analysis + mypy --config-file mypy.ini --python-version 3.10 --exclude examples --exclude tests/edge_cases --exclude tests/expected_failures $(PYTHON_FILES) mdlint: #### Performs Markdown lint # mdlint rules with common errors and possible fixes can be found here: diff --git a/deepgram/__init__.py b/deepgram/__init__.py index 76ca944f..c9a67542 100644 --- a/deepgram/__init__.py +++ b/deepgram/__init__.py @@ -6,13 +6,24 @@ __version__ = "0.0.0" # entry point for the deepgram python sdk +from .client import verboselogs from .client import Deepgram, DeepgramClient -from .options import DeepgramClientOptions, ClientOptionsFromEnv -import logging, verboselogs +from .client import DeepgramClientOptions, ClientOptionsFromEnv +from .client import DeepgramApiKeyError, DeepgramModuleError -# listen client +# listen/read client from .client import Listen, Read +# common +from .client import ( + TextSource, + BufferSource, + StreamSource, + FileSource, + UrlSource, + Sentiment, +) + # live from .client import LiveTranscriptionEvents from .client import LiveClient, AsyncLiveClient @@ -31,13 +42,9 @@ # prerecorded from .client import PreRecordedClient, AsyncPreRecordedClient from .client import ( - FileSource, - PrerecordedSource, - UrlSource, - BufferSource, - ReadStreamSource, PrerecordedOptions, - Sentiment, + PreRecordedStreamSource, + PrerecordedSource, ) from .client import ( AsyncPrerecordedResponse, @@ -49,13 +56,9 @@ from .client import ReadClient, AsyncReadClient from .client import AnalyzeClient, AsyncAnalyzeClient from .client import ( - AnalyzeSource, - TextSource, - UrlSource, - BufferSource, - AnalyzeStreamSource, AnalyzeOptions, - Sentiment, + AnalyzeStreamSource, + AnalyzeSource, ) from .client import ( AsyncAnalyzeResponse, @@ -65,7 +68,7 @@ # speak from .client import SpeakClient, AsyncSpeakClient -from .client import SpeakSource, TextSource, SpeakStreamSource, SpeakOptions +from .client import SpeakOptions, SpeakStreamSource, SpeakSource from .client import SpeakResponse # manage diff --git a/deepgram/audio/microphone/constants.py b/deepgram/audio/microphone/constants.py index 0e65807a..f7bcc545 100644 --- a/deepgram/audio/microphone/constants.py +++ b/deepgram/audio/microphone/constants.py @@ -2,12 +2,11 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import logging, verboselogs +from deepgram.utils import verboselogs -""" -constants for microphone -""" -LOGGING = logging.WARNING +# Constants for microphone + +LOGGING = verboselogs.WARNING CHANNELS = 1 RATE = 16000 CHUNK = 8194 diff --git a/deepgram/audio/microphone/microphone.py b/deepgram/audio/microphone/microphone.py index aea11253..572fb20e 100644 --- a/deepgram/audio/microphone/microphone.py +++ b/deepgram/audio/microphone/microphone.py @@ -5,21 +5,41 @@ import inspect import asyncio import threading -from typing import Optional -import logging, verboselogs +from typing import Optional, Callable +import logging -from .errors import DeepgramMicrophoneError +from deepgram.utils import verboselogs from .constants import LOGGING, CHANNELS, RATE, CHUNK -class Microphone: +class Microphone: # pylint: disable=too-many-instance-attributes """ This implements a microphone for local audio input. This uses PyAudio under the hood. """ + _logger: verboselogs.VerboseLogger + _exit: threading.Event + + import pyaudio # pylint: disable=import-outside-toplevel + + _audio: pyaudio.PyAudio + _stream: pyaudio.Stream + _chunk: int + _rate: int + _format: int + _channels: int + _input_device_index: Optional[int] + _is_muted: bool + + _asyncio_loop: asyncio.AbstractEventLoop + _asyncio_thread: threading.Thread + + _push_callback_org: object + _push_callback: object + def __init__( self, - push_callback=None, + push_callback: Optional[Callable] = None, verbose: int = LOGGING, rate: int = RATE, chunk: int = CHUNK, @@ -27,189 +47,192 @@ def __init__( input_device_index: Optional[int] = None, ): # dynamic import of pyaudio as not to force the requirements on the SDK (and users) - import pyaudio - - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(verbose) - self.exit = threading.Event() - - self.audio = pyaudio.PyAudio() - self.chunk = chunk - self.rate = rate - self.format = pyaudio.paInt16 - self.channels = channels - self.input_device_index = input_device_index - self.push_callback_org = push_callback - - self.asyncio_loop = None - self.asyncio_thread = None - self.stream = None - self.is_muted = False + import pyaudio # pylint: disable=import-outside-toplevel + + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(verbose) + self._exit = threading.Event() + + self._audio = pyaudio.PyAudio() + self._chunk = chunk + self._rate = rate + self._format = pyaudio.paInt16 + self._channels = channels + self._is_muted = False + + self._input_device_index = input_device_index + self._push_callback_org = push_callback def _start_asyncio_loop(self) -> None: - self.asyncio_loop = asyncio.new_event_loop() - self.asyncio_loop.run_forever() + self._asyncio_loop = asyncio.new_event_loop() + self._asyncio_loop.run_forever() def is_active(self) -> bool: """ returns True if the stream is active, False otherwise """ - self.logger.debug("Microphone.is_active ENTER") - if self.stream is None: - self.logger.error("stream is None") - self.logger.debug("Microphone.is_active LEAVE") + self._logger.debug("Microphone.is_active ENTER") + if self._stream is None: + self._logger.error("stream is None") + self._logger.debug("Microphone.is_active LEAVE") return False - val = self.stream.is_active() - self.logger.info("is_active: %s", val) - self.logger.info("is_exiting: %s", self.exit.is_set()) - self.logger.debug("Microphone.is_active LEAVE") + val = self._stream.is_active() + self._logger.info("is_active: %s", val) + self._logger.info("is_exiting: %s", self._exit.is_set()) + self._logger.debug("Microphone.is_active LEAVE") return val - def set_callback(self, push_callback) -> None: + def set_callback(self, push_callback: Callable) -> None: """ Set the callback function to be called when data is received. """ - self.push_callback_org = push_callback + self._push_callback_org = push_callback def start(self) -> bool: """ starts the microphone stream """ - self.logger.debug("Microphone.start ENTER") + self._logger.debug("Microphone.start ENTER") - if self.stream is not None: - self.logger.error("start() failed. Library already initialized.") - self.logger.debug("Microphone.start LEAVE") - return False + self._logger.info("format: %s", self._format) + self._logger.info("channels: %d", self._channels) + self._logger.info("rate: %d", self._rate) + self._logger.info("chunk: %d", self._chunk) + self._logger.info("input_device_id: %d", self._input_device_index) - self.logger.info("format: %s", self.format) - self.logger.info("channels: %d", self.channels) - self.logger.info("rate: %d", self.rate) - self.logger.info("chunk: %d", self.chunk) - self.logger.info("input_device_id: %d", self.input_device_index) + if self._push_callback_org is None: + self._logger.error("start() failed. No callback set.") + self._logger.debug("Microphone.start LEAVE") + return False - if inspect.iscoroutinefunction(self.push_callback_org): - self.logger.verbose("async/await callback - wrapping") + if inspect.iscoroutinefunction(self._push_callback_org): + self._logger.verbose("async/await callback - wrapping") # Run our own asyncio loop. - self.asyncio_thread = threading.Thread(target=self._start_asyncio_loop) - self.asyncio_thread.start() + self._asyncio_thread = threading.Thread(target=self._start_asyncio_loop) + self._asyncio_thread.start() - self.push_callback = lambda data: asyncio.run_coroutine_threadsafe( - self.push_callback_org(data), self.asyncio_loop + self._push_callback = lambda data: asyncio.run_coroutine_threadsafe( + self._push_callback_org(data), self._asyncio_loop ).result() else: - self.logger.verbose("regular threaded callback") - self.push_callback = self.push_callback_org + self._logger.verbose("regular threaded callback") + self._push_callback = self._push_callback_org - self.stream = self.audio.open( - format=self.format, - channels=self.channels, - rate=self.rate, + self._stream = self._audio.open( + format=self._format, + channels=self._channels, + rate=self._rate, input=True, - frames_per_buffer=self.chunk, - input_device_index=self.input_device_index, + frames_per_buffer=self._chunk, + input_device_index=self._input_device_index, stream_callback=self._callback, ) - self.exit.clear() - self.stream.start_stream() + self._exit.clear() + self._stream.start_stream() - self.logger.notice("start() succeeded") - self.logger.debug("Microphone.start LEAVE") + self._logger.notice("start() succeeded") + self._logger.debug("Microphone.start LEAVE") return True - def _callback(self, input_data, frame_count, time_info, status_flags): + def _callback( + self, input_data, frame_count, time_info, status_flags + ): # pylint: disable=unused-argument """ The callback used to process data in callback mode. """ - import pyaudio + # dynamic import of pyaudio as not to force the requirements on the SDK (and users) + import pyaudio # pylint: disable=import-outside-toplevel - self.logger.debug("Microphone._callback ENTER") + self._logger.debug("Microphone._callback ENTER") - if self.exit.is_set(): - self.logger.info("exit is Set") - self.logger.notice("_callback stopping...") - self.logger.debug("Microphone._callback LEAVE") + if self._exit.is_set(): + self._logger.info("exit is Set") + self._logger.notice("_callback stopping...") + self._logger.debug("Microphone._callback LEAVE") return None, pyaudio.paAbort if input_data is None: - self.logger.warning("input_data is None") - self.logger.debug("Microphone._callback LEAVE") + self._logger.warning("input_data is None") + self._logger.debug("Microphone._callback LEAVE") return None, pyaudio.paContinue try: - if self.is_muted: + if self._is_muted: size = len(input_data) input_data = b"\x00" * size - self.push_callback(input_data) + self._push_callback(input_data) except Exception as e: - self.logger.error("Error while sending: %s", str(e)) - self.logger.debug("Microphone._callback LEAVE") + self._logger.error("Error while sending: %s", str(e)) + self._logger.debug("Microphone._callback LEAVE") raise - self.logger.debug("Microphone._callback LEAVE") + self._logger.debug("Microphone._callback LEAVE") return input_data, pyaudio.paContinue def mute(self) -> bool: """ Mutes the microphone stream """ - self.logger.debug("Microphone.mute ENTER") + self._logger.debug("Microphone.mute ENTER") - if self.stream is None: - self.logger.error("mute() failed. Library not initialized.") - self.logger.debug("Microphone.mute LEAVE") + if self._stream is None: + self._logger.error("mute() failed. Library not initialized.") + self._logger.debug("Microphone.mute LEAVE") return False - self.is_muted = True + self._is_muted = True - self.logger.notice("mute() succeeded") - self.logger.debug("Microphone.mute LEAVE") + self._logger.notice("mute() succeeded") + self._logger.debug("Microphone.mute LEAVE") return True def unmute(self) -> bool: """ Unmutes the microphone stream """ - self.logger.debug("Microphone.unmute ENTER") + self._logger.debug("Microphone.unmute ENTER") - if self.stream is None: - self.logger.error("unmute() failed. Library not initialized.") - self.logger.debug("Microphone.unmute LEAVE") + if self._stream is None: + self._logger.error("unmute() failed. Library not initialized.") + self._logger.debug("Microphone.unmute LEAVE") return False - self.is_muted = False + self._is_muted = False - self.logger.notice("unmute() succeeded") - self.logger.debug("Microphone.unmute LEAVE") + self._logger.notice("unmute() succeeded") + self._logger.debug("Microphone.unmute LEAVE") return True def finish(self) -> bool: """ Stops the microphone stream """ - self.logger.debug("Microphone.finish ENTER") + self._logger.debug("Microphone.finish ENTER") - self.logger.notice("signal exit") - self.exit.set() + self._logger.notice("signal exit") + self._exit.set() # Stop the stream. - if self.stream is not None: - self.stream.stop_stream() - self.stream.close() - self.stream = None + if self._stream is not None: + self._stream.stop_stream() + self._stream.close() + self._stream = None # type: ignore # clean up the thread - if self.asyncio_thread is not None: - self.asyncio_loop.call_soon_threadsafe(self.asyncio_loop.stop) - self.asyncio_thread.join() - self.asyncio_thread = None - self.logger.notice("stream/recv thread joined") - - self.logger.notice("finish succeeded") - self.logger.debug("Microphone.finish LEAVE") + if ( + inspect.iscoroutinefunction(self._push_callback_org) + and self._asyncio_thread is not None + ): + self._asyncio_loop.call_soon_threadsafe(self._asyncio_loop.stop) + self._asyncio_thread.join() + self._asyncio_thread = None # type: ignore + self._logger.notice("stream/recv thread joined") + + self._logger.notice("finish succeeded") + self._logger.debug("Microphone.finish LEAVE") return True diff --git a/deepgram/client.py b/deepgram/client.py index b9cea277..273ebd6c 100644 --- a/deepgram/client.py +++ b/deepgram/client.py @@ -4,9 +4,22 @@ from typing import Optional from importlib import import_module -import logging, verboselogs import os +import logging +from deepgram.utils import verboselogs + +# common +# pylint: disable=unused-import +from .clients import ( + TextSource, + BufferSource, + StreamSource, + FileSource, + UrlSource, + Sentiment, +) + # listen client from .clients import Listen, Read @@ -30,15 +43,14 @@ ) # prerecorded -from .clients import PreRecordedClient, AsyncPreRecordedClient from .clients import ( - FileSource, - PrerecordedSource, - UrlSource, - BufferSource, - ReadStreamSource, + PreRecordedClient, + AsyncPreRecordedClient, +) +from .clients import ( PrerecordedOptions, - Sentiment, + PreRecordedStreamSource, + PrerecordedSource, ) # prerecorded client responses @@ -52,13 +64,9 @@ from .clients import ReadClient, AsyncReadClient from .clients import AnalyzeClient, AsyncAnalyzeClient from .clients import ( - AnalyzeSource, - TextSource, - UrlSource, - BufferSource, - AnalyzeStreamSource, AnalyzeOptions, - Sentiment, + AnalyzeStreamSource, + AnalyzeSource, ) # read client responses @@ -71,7 +79,7 @@ # speak client classes/input from .clients import SpeakClient, AsyncSpeakClient from .clients import SpeakOptions -from .clients import SpeakSource, TextSource, SpeakStreamSource +from .clients import SpeakStreamSource, SpeakSource # speak client responses from .clients import SpeakResponse @@ -108,14 +116,23 @@ ) # on-prem -from .clients.onprem.client import OnPremClient -from .clients.onprem.v1.async_client import AsyncOnPremClient +from .clients import ( + OnPremClient, + AsyncOnPremClient, +) +# client errors and options from .options import DeepgramClientOptions, ClientOptionsFromEnv from .errors import DeepgramApiKeyError, DeepgramModuleError +# pylint: enable=unused-import + + +class Deepgram: # pylint: disable=broad-exception-raised + """ + The Deepgram class is no longer a class in version 3 of this SDK. + """ -class Deepgram: def __init__(self, *anything): raise Exception( """ @@ -161,145 +178,183 @@ class DeepgramClient: asynconprem: Returns an (Async) OnPremClient instance for interacting with Deepgram's on-premises API. """ + _config: DeepgramClientOptions + _logger: verboselogs.VerboseLogger + def __init__( self, api_key: str = "", config: Optional[DeepgramClientOptions] = None, ): - verboselogs.install() - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) if api_key == "" and config is not None: - self.logger.info("Attempting to set API key from config object") + self._logger.info("Attempting to set API key from config object") api_key = config.api_key if api_key == "": - self.logger.info("Attempting to set API key from environment variable") + self._logger.info("Attempting to set API key from environment variable") api_key = os.getenv("DEEPGRAM_API_KEY", "") if api_key == "": - self.logger.warning("WARNING: API key is missing") + self._logger.warning("WARNING: API key is missing") self.api_key = api_key if config is None: # Use default configuration - self.config = DeepgramClientOptions(self.api_key) + self._config = DeepgramClientOptions(self.api_key) else: config.set_apikey(self.api_key) - self.config = config + self._config = config @property def listen(self): - return Listen(self.config) + """ + Returns a ListenClient instance for interacting with Deepgram's transcription services. + """ + return Listen(self._config) @property def read(self): - return Read(self.config) + """ + Returns a ReadClient instance for interacting with Deepgram's read services. + """ + return Read(self._config) @property def speak(self): - return self.Version(self.config, "speak") + """ + Returns a SpeakClient instance for interacting with Deepgram's speak services. + """ + return self.Version(self._config, "speak") @property def asyncspeak(self): - return self.Version(self.config, "asyncspeak") + """ + Returns an AsyncSpeakClient instance for interacting with Deepgram's speak services. + """ + return self.Version(self._config, "asyncspeak") @property def manage(self): - return self.Version(self.config, "manage") + """ + Returns a ManageClient instance for managing Deepgram resources. + """ + return self.Version(self._config, "manage") @property def asyncmanage(self): - return self.Version(self.config, "asyncmanage") + """ + Returns an AsyncManageClient instance for managing Deepgram resources. + """ + return self.Version(self._config, "asyncmanage") @property def onprem(self): - return self.Version(self.config, "onprem") + """ + Returns an OnPremClient instance for interacting with Deepgram's on-premises API. + """ + return self.Version(self._config, "onprem") @property def asynconprem(self): - return self.Version(self.config, "asynconprem") + """ + Returns an AsyncOnPremClient instance for interacting with Deepgram's on-premises API. + """ + return self.Version(self._config, "asynconprem") # INTERNAL CLASSES class Version: + """ + Represents a version of the Deepgram API. + """ + + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + _parent: str + def __init__(self, config, parent: str): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - self.parent = parent + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + + self._config = config + self._parent = parent # FUTURE VERSIONING: # When v2 or v1.1beta1 or etc. This allows easy access to the latest version of the API. # @property # def latest(self): - # match self.parent: + # match self._parent: # case "manage": - # return ManageClient(self.config) + # return ManageClient(self._config) # case "onprem": - # return OnPremClient(self.config) + # return OnPremClient(self._config) # case _: # raise DeepgramModuleError("Invalid parent") def v(self, version: str = ""): - self.logger.debug("Version.v ENTER") - self.logger.info("version: %s", version) + """ + Returns a client for the specified version of the API. + """ + self._logger.debug("Version.v ENTER") + self._logger.info("version: %s", version) if len(version) == 0: - self.logger.error("version is empty") - self.logger.debug("Version.v LEAVE") + self._logger.error("version is empty") + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Invalid module version") parent = "" - fileName = "" - className = "" - match self.parent: + filename = "" + classname = "" + match self._parent: case "manage": parent = "manage" - fileName = "client" - className = "ManageClient" + filename = "client" + classname = "ManageClient" case "asyncmanage": parent = "manage" - fileName = "async_client" - className = "AsyncManageClient" + filename = "async_client" + classname = "AsyncManageClient" case "speak": parent = "speak" - fileName = "client" - className = "SpeakClient" + filename = "client" + classname = "SpeakClient" case "asyncspeak": parent = "speak" - fileName = "async_client" - className = "AsyncSpeakClient" + filename = "async_client" + classname = "AsyncSpeakClient" case "onprem": parent = "onprem" - fileName = "client" - className = "OnPremClient" + filename = "client" + classname = "OnPremClient" case "asynconprem": parent = "onprem" - fileName = "async_client" - className = "AsyncOnPremClient" + filename = "async_client" + classname = "AsyncOnPremClient" case _: - self.logger.error("parent unknown: %s", self.parent) - self.logger.debug("Version.v LEAVE") + self._logger.error("parent unknown: %s", self._parent) + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Invalid parent type") # create class path - path = f"deepgram.clients.{parent}.v{version}.{fileName}" - self.logger.info("path: %s", path) - self.logger.info("className: %s", className) + path = f"deepgram.clients.{parent}.v{version}.{filename}" + self._logger.info("path: %s", path) + self._logger.info("classname: %s", classname) # import class mod = import_module(path) if mod is None: - self.logger.error("module path is None") - self.logger.debug("Version.v LEAVE") + self._logger.error("module path is None") + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Unable to find package") - my_class = getattr(mod, className) + my_class = getattr(mod, classname) if my_class is None: - self.logger.error("my_class is None") - self.logger.debug("Version.v LEAVE") + self._logger.error("my_class is None") + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Unable to find class") # instantiate class - myClass = my_class(self.config) - self.logger.notice("Version.v succeeded") - self.logger.debug("Version.v LEAVE") - return myClass + my_class_instance = my_class(self._config) + self._logger.notice("Version.v succeeded") + self._logger.debug("Version.v LEAVE") + return my_class_instance diff --git a/deepgram/clients/__init__.py b/deepgram/clients/__init__.py index 8e8e7a70..b94d9144 100644 --- a/deepgram/clients/__init__.py +++ b/deepgram/clients/__init__.py @@ -2,6 +2,16 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +# common +from .common import ( + TextSource, + BufferSource, + StreamSource, + FileSource, + UrlSource, + Sentiment, +) + # listen from .listen import Listen from .read import Read @@ -24,13 +34,9 @@ # prerecorded from .prerecorded import PreRecordedClient, AsyncPreRecordedClient from .prerecorded import PrerecordedOptions -from .prerecorded import Sentiment from .prerecorded import ( - FileSource, + PreRecordedStreamSource, PrerecordedSource, - UrlSource, - BufferSource, - ReadStreamSource, ) from .prerecorded import ( AsyncPrerecordedResponse, @@ -42,13 +48,9 @@ from .analyze import ReadClient, AsyncReadClient from .analyze import AnalyzeClient, AsyncAnalyzeClient from .analyze import AnalyzeOptions -from .analyze import Sentiment from .analyze import ( - AnalyzeSource, - TextSource, - UrlSource, - BufferSource, AnalyzeStreamSource, + AnalyzeSource, ) from .analyze import ( AsyncAnalyzeResponse, @@ -60,9 +62,8 @@ from .speak import SpeakClient, AsyncSpeakClient from .speak import SpeakOptions from .speak import ( - SpeakSource, - TextSource, SpeakStreamSource, + SpeakSource, ) from .speak import SpeakResponse @@ -97,6 +98,3 @@ # onprem from .onprem import OnPremClient, AsyncOnPremClient - -# client options -from ..options import DeepgramClientOptions diff --git a/deepgram/clients/abstract_async_client.py b/deepgram/clients/abstract_async_client.py index ecf4cd5f..c5bf3fc6 100644 --- a/deepgram/clients/abstract_async_client.py +++ b/deepgram/clients/abstract_async_client.py @@ -2,15 +2,15 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import httpx import json import io -from typing import Dict, Optional, List +from typing import Dict, Optional, List, Union + +import httpx from .helpers import append_query_params from ..options import DeepgramClientOptions from .errors import DeepgramError, DeepgramApiError, DeepgramUnknownApiError -from .helpers import append_query_params class AbstractAsyncRestClient: @@ -31,10 +31,12 @@ class AbstractAsyncRestClient: DeepgramUnknownApiError: Raised for unknown API errors. """ + _config: DeepgramClientOptions + def __init__(self, config: DeepgramClientOptions): if config is None: raise DeepgramError("Config are required") - self.config = config + self._config = config async def get( self, @@ -45,6 +47,9 @@ async def get( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a GET request to the specified URL. + """ return await self._handle_request( "GET", url, @@ -58,14 +63,17 @@ async def get( async def post_file( self, url: str, + file_result: List, options: Optional[Dict] = None, addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, - file_result: Optional[List] = None, **kwargs, - ) -> Dict: - return await self._handle_request( + ) -> Dict[str, Union[str, object]]: + """ + Make a POST request to the specified URL and return a file response. + """ + return await self._handle_request_file( "POST", url, file_result=file_result, @@ -85,6 +93,9 @@ async def post( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a POST request to the specified URL. + """ return await self._handle_request( "POST", url, @@ -104,6 +115,9 @@ async def put( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a PUT request to the specified URL. + """ return await self._handle_request( "PUT", url, @@ -123,6 +137,9 @@ async def patch( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a PATCH request to the specified URL. + """ return await self._handle_request( "PATCH", url, @@ -142,6 +159,9 @@ async def delete( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a DELETE request to the specified URL. + """ return await self._handle_request( "DELETE", url, @@ -152,6 +172,7 @@ async def delete( **kwargs, ) + # pylint: disable-msg=too-many-locals,too-many-branches,too-many-locals async def _handle_request( self, method: str, @@ -160,15 +181,14 @@ async def _handle_request( addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, - file_result: Optional[List] = None, **kwargs, - ): + ) -> str: _url = url if params is not None: _url = append_query_params(_url, params) if addons is not None: _url = append_query_params(_url, addons) - _headers = self.config.headers + _headers = self._config.headers if headers is not None: _headers.update(headers) if timeout is None: @@ -180,40 +200,91 @@ async def _handle_request( method, _url, headers=_headers, **kwargs ) response.raise_for_status() - - # handle file response - if file_result is not None: - ret = dict() - for item in file_result: - if item in response.headers: - ret[item] = response.headers[item] - continue - tmpItem = f"dg-{item}" - if tmpItem in response.headers: - ret[item] = response.headers[tmpItem] - continue - tmpItem = f"x-dg-{item}" - if tmpItem in response.headers: - ret[item] = response.headers[tmpItem] - ret["stream"] = io.BytesIO(response.content) - return ret - - # standard response return response.text - except httpx._exceptions.HTTPError as e: - if isinstance(e, httpx.HTTPStatusError): - status_code = e.response.status_code or 500 + except httpx.HTTPError as e1: + if isinstance(e1, httpx.HTTPStatusError): + status_code = e1.response.status_code or 500 try: - json_object = json.loads(e.response.text) + json_object = json.loads(e1.response.text) raise DeepgramApiError( - json_object.get("err_msg"), status_code, json.dumps(json_object) - ) from e - except json.decoder.JSONDecodeError: - raise DeepgramUnknownApiError(e.response.text, status_code) from e - except ValueError as e: - raise DeepgramUnknownApiError(e.response.text, status_code) from e + json_object.get("err_msg"), + str(status_code), + json.dumps(json_object), + ) from e1 + except json.decoder.JSONDecodeError as e2: + raise DeepgramUnknownApiError(e2.msg, str(status_code)) from e2 + except ValueError as e2: + raise DeepgramUnknownApiError(str(e2), str(status_code)) from e2 else: - raise - except Exception as e: + raise # pylint: disable-msg=try-except-raise + except Exception: # pylint: disable-msg=try-except-raise raise + + # pylint: enable-msg=too-many-locals,too-many-branches,too-many-locals + + # pylint: disable-msg=too-many-locals,too-many-branches + async def _handle_request_file( + self, + method: str, + url: str, + file_result: List, + params: Optional[Dict] = None, + addons: Optional[Dict] = None, + headers: Optional[Dict] = None, + timeout: Optional[httpx.Timeout] = None, + **kwargs, + ) -> Dict[str, Union[str, object]]: + _url = url + if params is not None: + _url = append_query_params(_url, params) + if addons is not None: + _url = append_query_params(_url, addons) + _headers = self._config.headers + if headers is not None: + _headers.update(headers) + if timeout is None: + timeout = httpx.Timeout(30.0, connect=10.0) + + try: + async with httpx.AsyncClient(timeout=timeout) as client: + response = await client.request( + method, _url, headers=_headers, **kwargs + ) + response.raise_for_status() + + ret: Dict[str, Union[str, object]] = {} + for item in file_result: + if item in response.headers: + ret[item] = response.headers[item] + continue + tmp_item = f"dg-{item}" + if tmp_item in response.headers: + ret[item] = response.headers[tmp_item] + continue + tmp_item = f"x-dg-{item}" + if tmp_item in response.headers: + ret[item] = response.headers[tmp_item] + ret["stream"] = io.BytesIO(response.content) + return ret + + except httpx.HTTPError as e1: + if isinstance(e1, httpx.HTTPStatusError): + status_code = e1.response.status_code or 500 + try: + json_object = json.loads(e1.response.text) + raise DeepgramApiError( + json_object.get("err_msg"), + str(status_code), + json.dumps(json_object), + ) from e1 + except json.decoder.JSONDecodeError as e2: + raise DeepgramUnknownApiError(e2.msg, str(status_code)) from e2 + except ValueError as e2: + raise DeepgramUnknownApiError(str(e2), str(status_code)) from e2 + else: + raise # pylint: disable-msg=try-except-raise + except Exception: # pylint: disable-msg=try-except-raise + raise + + # pylint: enable-msg=too-many-locals,too-many-branches diff --git a/deepgram/clients/abstract_sync_client.py b/deepgram/clients/abstract_sync_client.py index 35161c26..a6a94bf1 100644 --- a/deepgram/clients/abstract_sync_client.py +++ b/deepgram/clients/abstract_sync_client.py @@ -2,10 +2,11 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import httpx import json import io -from typing import Dict, Optional, List +from typing import Dict, Optional, List, Union + +import httpx from .helpers import append_query_params from ..options import DeepgramClientOptions @@ -30,10 +31,12 @@ class AbstractSyncRestClient: DeepgramUnknownApiError: Raised for unknown API errors. """ + _config: DeepgramClientOptions + def __init__(self, config: DeepgramClientOptions): if config is None: raise DeepgramError("Config are required") - self.config = config + self._config = config def get( self, @@ -44,6 +47,9 @@ def get( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a GET request to the specified URL. + """ return self._handle_request( "GET", url, @@ -57,14 +63,17 @@ def get( def post_file( self, url: str, + file_result: List, options: Optional[Dict] = None, addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, - file_result: Optional[List] = None, **kwargs, - ) -> Dict: - return self._handle_request( + ) -> Dict[str, Union[str, io.BytesIO, object]]: + """ + Make a POST request to the specified URL and return a file response. + """ + return self._handle_request_file( "POST", url, file_result=file_result, @@ -84,6 +93,9 @@ def post( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a POST request to the specified URL. + """ return self._handle_request( "POST", url, @@ -103,6 +115,9 @@ def put( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a PUT request to the specified URL. + """ return self._handle_request( "PUT", url, @@ -122,6 +137,9 @@ def patch( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a PATCH request to the specified URL. + """ return self._handle_request( "PATCH", url, @@ -141,6 +159,9 @@ def delete( timeout: Optional[httpx.Timeout] = None, **kwargs, ) -> str: + """ + Make a DELETE request to the specified URL. + """ return self._handle_request( "DELETE", url, @@ -151,6 +172,7 @@ def delete( **kwargs, ) + # pylint: disable-msg=too-many-locals,too-many-branches def _handle_request( self, method: str, @@ -159,15 +181,14 @@ def _handle_request( addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, - file_result: Optional[List] = None, **kwargs, - ): + ) -> str: _url = url if params is not None: _url = append_query_params(_url, params) if addons is not None: _url = append_query_params(_url, addons) - _headers = self.config.headers + _headers = self._config.headers if headers is not None: _headers.update(headers) if timeout is None: @@ -177,40 +198,89 @@ def _handle_request( with httpx.Client(timeout=timeout) as client: response = client.request(method, _url, headers=_headers, **kwargs) response.raise_for_status() - - # handle file response - if file_result is not None: - ret = dict() - for item in file_result: - if item in response.headers: - ret[item] = response.headers[item] - continue - tmpItem = f"dg-{item}" - if tmpItem in response.headers: - ret[item] = response.headers[tmpItem] - continue - tmpItem = f"x-dg-{item}" - if tmpItem in response.headers: - ret[item] = response.headers[tmpItem] - ret["stream"] = io.BytesIO(response.content) - return ret - - # standard response return response.text - except httpx._exceptions.HTTPError as e: - if isinstance(e, httpx.HTTPStatusError): - status_code = e.response.status_code or 500 + except httpx.HTTPError as e1: + if isinstance(e1, httpx.HTTPStatusError): + status_code = e1.response.status_code or 500 + try: + json_object = json.loads(e1.response.text) + raise DeepgramApiError( + json_object.get("err_msg"), + str(status_code), + json.dumps(json_object), + ) from e1 + except json.decoder.JSONDecodeError as e2: + raise DeepgramUnknownApiError(e2.msg, str(status_code)) from e2 + except ValueError as e2: + raise DeepgramUnknownApiError(str(e2), str(status_code)) from e2 + else: + raise # pylint: disable-msg=try-except-raise + except Exception: # pylint: disable-msg=try-except-raise + raise + + # pylint: enable-msg=too-many-locals,too-many-branches + + # pylint: disable-msg=too-many-branches,too-many-locals + def _handle_request_file( + self, + method: str, + url: str, + file_result: List, + params: Optional[Dict] = None, + addons: Optional[Dict] = None, + headers: Optional[Dict] = None, + timeout: Optional[httpx.Timeout] = None, + **kwargs, + ) -> Dict[str, Union[str, io.BytesIO, object]]: + _url = url + if params is not None: + _url = append_query_params(_url, params) + if addons is not None: + _url = append_query_params(_url, addons) + _headers = self._config.headers + if headers is not None: + _headers.update(headers) + if timeout is None: + timeout = httpx.Timeout(30.0, connect=10.0) + + try: + with httpx.Client(timeout=timeout) as client: + response = client.request(method, _url, headers=_headers, **kwargs) + response.raise_for_status() + + ret: Dict[str, Union[str, object]] = {} + for item in file_result: + if item in response.headers: + ret[item] = response.headers[item] + continue + tmp_item = f"dg-{item}" + if tmp_item in response.headers: + ret[item] = response.headers[tmp_item] + continue + tmp_item = f"x-dg-{item}" + if tmp_item in response.headers: + ret[item] = response.headers[tmp_item] + ret["stream"] = io.BytesIO(response.content) + return ret + + except httpx.HTTPError as e1: + if isinstance(e1, httpx.HTTPStatusError): + status_code = e1.response.status_code or 500 try: - json_object = json.loads(e.response.text) + json_object = json.loads(e1.response.text) raise DeepgramApiError( - json_object.get("err_msg"), status_code, json.dumps(json_object) - ) from e - except json.decoder.JSONDecodeError: - raise DeepgramUnknownApiError(e.response.text, status_code) from e - except ValueError as e: - raise DeepgramUnknownApiError(e.response.text, status_code) from e + json_object.get("err_msg"), + str(status_code), + json.dumps(json_object), + ) from e1 + except json.decoder.JSONDecodeError as e2: + raise DeepgramUnknownApiError(e2.msg, str(status_code)) from e2 + except ValueError as e2: + raise DeepgramUnknownApiError(str(e2), str(status_code)) from e2 else: - raise - except Exception as e: + raise # pylint: disable-msg=try-except-raise + except Exception: # pylint: disable-msg=try-except-raise raise + + # pylint: disable-msg=too-many-branches,too-many-locals diff --git a/deepgram/clients/analyze/__init__.py b/deepgram/clients/analyze/__init__.py index 81612afd..c782482b 100644 --- a/deepgram/clients/analyze/__init__.py +++ b/deepgram/clients/analyze/__init__.py @@ -5,14 +5,7 @@ from .client import AnalyzeClient, AsyncAnalyzeClient from .client import ReadClient, AsyncReadClient from .client import AnalyzeOptions -from .client import ( - UrlSource, - BufferSource, - AnalyzeStreamSource, - AnalyzeSource, - TextSource, -) -from .client import Sentiment +from .client import UrlSource, FileSource, AnalyzeStreamSource, AnalyzeSource from .client import AsyncAnalyzeResponse, AnalyzeResponse, SyncAnalyzeResponse -from ...options import DeepgramClientOptions +from ...options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/analyze/client.py b/deepgram/clients/analyze/client.py index 132ca704..53f73396 100644 --- a/deepgram/clients/analyze/client.py +++ b/deepgram/clients/analyze/client.py @@ -2,128 +2,44 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -from typing import Union - from .v1.client import AnalyzeClient as AnalyzeClientLatest from .v1.async_client import AsyncAnalyzeClient as AsyncAnalyzeClientLatest from .v1.options import ( AnalyzeOptions as AnalyzeOptionsLatest, - AnalyzeStreamSource as AnalyzeStreamSourceLatest, - BufferSource as BufferSourceLatest, UrlSource as UrlSourceLatest, + FileSource as FileSourceLatest, + AnalyzeStreamSource as AnalyzeStreamSourceLatest, AnalyzeSource as AnalyzeSourceLatest, - TextSource as TextSourceLatest, ) from .v1.response import ( SyncAnalyzeResponse as SyncAnalyzeResponseLatest, AnalyzeResponse as AnalyzeResponseLatest, AsyncAnalyzeResponse as AsyncAnalyzeResponseLatest, ) -from .enums import Sentiment -""" -The client.py points to the current supported version in the SDK. -Older versions are supported in the SDK for backwards compatibility. -""" +# The client.py points to the current supported version in the SDK. +# Older versions are supported in the SDK for backwards compatibility. # input -class AnalyzeOptions(AnalyzeOptionsLatest): - """ - Please see AnalyzeOptionsLatest for details - """ - - pass - - -class AnalyzeStreamSource(AnalyzeStreamSourceLatest): - """ - Please see AnalyzeStreamSourceLatest for details - """ - - pass - - -class BufferSource(BufferSourceLatest): - """ - Please see BufferSourceLatest for details - """ - - pass - - -class UrlSource(UrlSourceLatest): - """ - Please see UrlSourceLatest for details - """ - - pass - - +AnalyzeOptions = AnalyzeOptionsLatest +AnalyzeStreamSource = AnalyzeStreamSourceLatest +FileSource = FileSourceLatest +UrlSource = UrlSourceLatest AnalyzeSource = AnalyzeSourceLatest -TextSource = TextSourceLatest # responses - - -class AsyncAnalyzeResponse(AsyncAnalyzeResponseLatest): - """ - Please see AnalyzeResponseLatest for details - """ - - pass - - -class AnalyzeResponse(AnalyzeResponseLatest): - """ - Please see AnalyzeResponseLatest for details - """ - - pass - - -class SyncAnalyzeResponse(SyncAnalyzeResponseLatest): - """ - Please see AnalyzeResponseLatest for details - """ - - pass +AsyncAnalyzeResponse = AsyncAnalyzeResponseLatest +AnalyzeResponse = AnalyzeResponseLatest +SyncAnalyzeResponse = SyncAnalyzeResponseLatest # clients -class AnalyzeClient(AnalyzeClientLatest): - """ - Please see AnalyzeClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) - - -class AsyncAnalyzeClient(AsyncAnalyzeClientLatest): - """ - Please see AsyncAnalyzeClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) - - -class ReadClient(AnalyzeClientLatest): - """ - Just an alias for the Analyze Client - """ - - def __init__(self, config): - self.config = config - super().__init__(config) - +AnalyzeClient = AnalyzeClientLatest +AsyncAnalyzeClient = AsyncAnalyzeClientLatest -class AsyncReadClient(AsyncAnalyzeClientLatest): - """ - Just an alias for the Async Analyze Client - """ - def __init__(self, config): - super().__init__(config) +# aliases +ReadClient = AnalyzeClientLatest +AsyncReadClient = AsyncAnalyzeClientLatest diff --git a/deepgram/clients/analyze/v1/__init__.py b/deepgram/clients/analyze/v1/__init__.py index 9125351e..fbb8e58c 100644 --- a/deepgram/clients/analyze/v1/__init__.py +++ b/deepgram/clients/analyze/v1/__init__.py @@ -7,11 +7,9 @@ from .options import ( AnalyzeOptions, UrlSource, - BufferSource, + FileSource, AnalyzeStreamSource, - TextSource, AnalyzeSource, ) -from ....options import DeepgramClientOptions -from .response import AsyncAnalyzeResponse, AnalyzeResponse -from ..enums import Sentiment +from ....options import DeepgramClientOptions, ClientOptionsFromEnv +from .response import AsyncAnalyzeResponse, AnalyzeResponse, Sentiment diff --git a/deepgram/clients/analyze/v1/async_client.py b/deepgram/clients/analyze/v1/async_client.py index 640c6312..e9676b43 100644 --- a/deepgram/clients/analyze/v1/async_client.py +++ b/deepgram/clients/analyze/v1/async_client.py @@ -2,23 +2,21 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import httpx -import logging, verboselogs -import json +import logging from typing import Dict, Union, Optional +import httpx + +from deepgram.utils import verboselogs +from ....options import DeepgramClientOptions from ...abstract_async_client import AbstractAsyncRestClient from ..errors import DeepgramError, DeepgramTypeError -from .helpers import is_buffer_source, is_readstream_source, is_url_source -from ..enums import Sentiment +from .helpers import is_buffer_source, is_readstream_source, is_url_source from .options import ( AnalyzeOptions, UrlSource, - BufferSource, - AnalyzeStreamSource, - TextSource, - AnalyzeSource, + FileSource, ) from .response import AsyncAnalyzeResponse, AnalyzeResponse @@ -29,27 +27,15 @@ class AsyncAnalyzeClient(AbstractAsyncRestClient): Provides methods for transcribing text from URLs and files. """ - def __init__(self, config): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - super().__init__(config) + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions - """ - Analyze text from a URL source. - - Args: - source (UrlSource): The URL source of the text to ingest. - options (AnalyzeOptions): Additional options for the ingest (default is None). - endpoint (str): The API endpoint for the ingest (default is "v1/read"). - - Returns: - AnalyzeResponse: An object containing the result. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ + def __init__(self, config: DeepgramClientOptions): + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + super().__init__(config) async def analyze_url( self, @@ -59,11 +45,25 @@ async def analyze_url( headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/read", - ) -> AnalyzeResponse: - self.logger.debug("AsyncAnalyzeClient.analyze_url ENTER") + ) -> Union[AsyncAnalyzeResponse, AnalyzeResponse]: + """ + Analyze text from a URL source. + + Args: + source (UrlSource): The URL source of the text to ingest. + options (AnalyzeOptions): Additional options for the ingest (default is None). + endpoint (str): The API endpoint for the ingest (default is "v1/read"). + + Returns: + AnalyzeResponse: An object containing the result. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AsyncAnalyzeClient.analyze_url ENTER") if options is not None and options["callback"] is not None: - self.logger.debug("AsyncAnalyzeClient.analyze_url LEAVE") + self._logger.debug("AsyncAnalyzeClient.analyze_url LEAVE") return await self.analyze_url_callback( source, callback=options["callback"], @@ -74,27 +74,27 @@ async def analyze_url( endpoint=endpoint, ) - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if is_url_source(source): body = source else: - self.logger.error("Unknown transcription source type") - self.logger.debug("AsyncAnalyzeClient.analyze_url LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("AsyncAnalyzeClient.analyze_url LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, AnalyzeOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AsyncAnalyzeClient.analyze_url LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AsyncAnalyzeClient.analyze_url LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, AnalyzeOptions): - self.logger.info("AnalyzeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("AnalyzeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, options=options, @@ -103,29 +103,13 @@ async def analyze_url( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AnalyzeResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("analyze_url succeeded") - self.logger.debug("AsyncAnalyzeClient.analyze_url LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("analyze_url succeeded") + self._logger.debug("AsyncAnalyzeClient.analyze_url LEAVE") return res - """ - Transcribes audio from a URL source and sends the result to a callback URL. - - Args: - source (UrlSource): The URL source of the audio to transcribe. - callback (str): The callback URL where the transcription results will be sent. - options (AnalyzeOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/read"). - - Returns: - AsyncAnalyzeResponse: An object containing the request_id or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - async def analyze_url_callback( self, source: UrlSource, @@ -136,11 +120,26 @@ async def analyze_url_callback( timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/read", ) -> AsyncAnalyzeResponse: - self.logger.debug("AnalyzeClient.analyze_url_callback ENTER") + """ + Transcribes audio from a URL source and sends the result to a callback URL. - url = f"{self.config.url}/{endpoint}" + Args: + source (UrlSource): The URL source of the audio to transcribe. + callback (str): The callback URL where the transcription results will be sent. + options (AnalyzeOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/read"). + + Returns: + AsyncAnalyzeResponse: An object containing the request_id or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AnalyzeClient.analyze_url_callback ENTER") + + url = f"{self._config.url}/{endpoint}" if options is None: - options = dict() + options = {} if isinstance(options, AnalyzeOptions): options.callback = callback else: @@ -148,23 +147,23 @@ async def analyze_url_callback( if is_url_source(source): body = source else: - self.logger.error("Unknown transcription source type") - self.logger.debug("AnalyzeClient.analyze_url_callback LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("AnalyzeClient.analyze_url_callback LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, AnalyzeOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AnalyzeClient.analyze_url_callback LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AnalyzeClient.analyze_url_callback LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, AnalyzeOptions): - self.logger.info("AnalyzeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("AnalyzeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, options=options, @@ -173,41 +172,40 @@ async def analyze_url_callback( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AsyncAnalyzeResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("analyze_url_callback succeeded") - self.logger.debug("AnalyzeClient.analyze_url_callback LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("analyze_url_callback succeeded") + self._logger.debug("AnalyzeClient.analyze_url_callback LEAVE") return res - """ - Analyze text from a local file source. - - Args: - source (TextSource): The local file source of the text to ingest. - options (AnalyzeOptions): Additional options for the ingest (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/read"). - - Returns: - AnalyzeResponse: An object containing the transcription result or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - async def analyze_text( self, - source: TextSource, + source: FileSource, options: Optional[Union[AnalyzeOptions, Dict]] = None, addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/read", - ) -> AnalyzeResponse: - self.logger.debug("AsyncAnalyzeClient.analyze_text ENTER") + ) -> Union[AsyncAnalyzeResponse, AnalyzeResponse]: + """ + Analyze text from a local file source. + + Args: + source (TextSource): The local file source of the text to ingest. + options (AnalyzeOptions): Additional options for the ingest (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/read"). + + Returns: + AnalyzeResponse: An object containing the transcription result or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AsyncAnalyzeClient.analyze_text ENTER") if options is not None and options["callback"] is not None: - self.logger.debug("AsyncAnalyzeClient.analyze_text LEAVE") + self._logger.debug("AsyncAnalyzeClient.analyze_text LEAVE") return await self.analyze_text_callback( source, callback=options["callback"], @@ -218,28 +216,28 @@ async def analyze_text( endpoint=endpoint, ) - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if is_buffer_source(source): - body = source["buffer"] + body = source["buffer"] # type: ignore elif is_readstream_source(source): - body = source["stream"] + body = source["stream"] # type: ignore else: - self.logger.error("Unknown transcription source type") - self.logger.debug("AsyncAnalyzeClient.analyze_text LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("AsyncAnalyzeClient.analyze_text LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, AnalyzeOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AsyncAnalyzeClient.analyze_text LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AsyncAnalyzeClient.analyze_text LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) + self._logger.info("url: %s", url) if isinstance(options, AnalyzeOptions): - self.logger.info("AnalyzeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("AnalyzeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, options=options, @@ -248,32 +246,16 @@ async def analyze_text( content=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AnalyzeResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("analyze_text succeeded") - self.logger.debug("AsyncAnalyzeClient.analyze_text LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("analyze_text succeeded") + self._logger.debug("AsyncAnalyzeClient.analyze_text LEAVE") return res - """ - Transcribes audio from a local file source and sends the result to a callback URL. - - Args: - source (TextSource): The local file source of the audio to transcribe. - callback (str): The callback URL where the transcription results will be sent. - options (AnalyzeOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/read"). - - Returns: - AsyncAnalyzeResponse: An object containing the request_id or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - async def analyze_text_callback( self, - source: TextSource, + source: FileSource, callback: str, options: Optional[Union[AnalyzeOptions, Dict]] = None, addons: Optional[Dict] = None, @@ -281,36 +263,51 @@ async def analyze_text_callback( timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/read", ) -> AsyncAnalyzeResponse: - self.logger.debug("AnalyzeClient.analyze_text_callback ENTER") + """ + Transcribes audio from a local file source and sends the result to a callback URL. + + Args: + source (TextSource): The local file source of the audio to transcribe. + callback (str): The callback URL where the transcription results will be sent. + options (AnalyzeOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/read"). + + Returns: + AsyncAnalyzeResponse: An object containing the request_id or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AnalyzeClient.analyze_text_callback ENTER") - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if options is None: - options = dict() + options = {} if isinstance(options, AnalyzeOptions): options.callback = callback else: options["callback"] = callback if is_buffer_source(source): - body = source["buffer"] + body = source["buffer"] # type: ignore elif is_readstream_source(source): - body = source["stream"] + body = source["stream"] # type: ignore else: - self.logger.error("Unknown transcription source type") - self.logger.debug("AnalyzeClient.analyze_text_callback LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("AnalyzeClient.analyze_text_callback LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, AnalyzeOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AnalyzeClient.analyze_text_callback LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AnalyzeClient.analyze_text_callback LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) + self._logger.info("url: %s", url) if isinstance(options, AnalyzeOptions): - self.logger.info("AnalyzeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("AnalyzeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, options=options, @@ -319,9 +316,9 @@ async def analyze_text_callback( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AsyncAnalyzeResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("analyze_text_callback succeeded") - self.logger.debug("AnalyzeClient.analyze_text_callback LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("analyze_text_callback succeeded") + self._logger.debug("AnalyzeClient.analyze_text_callback LEAVE") return res diff --git a/deepgram/clients/analyze/v1/client.py b/deepgram/clients/analyze/v1/client.py index 9152ec86..373a09ed 100644 --- a/deepgram/clients/analyze/v1/client.py +++ b/deepgram/clients/analyze/v1/client.py @@ -2,23 +2,21 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import httpx -import logging, verboselogs -import json +import logging from typing import Dict, Union, Optional +import httpx + +from deepgram.utils import verboselogs +from ....options import DeepgramClientOptions from ...abstract_sync_client import AbstractSyncRestClient from ..errors import DeepgramError, DeepgramTypeError -from .helpers import is_buffer_source, is_readstream_source, is_url_source -from ..enums import Sentiment +from .helpers import is_buffer_source, is_readstream_source, is_url_source from .options import ( AnalyzeOptions, UrlSource, - BufferSource, - AnalyzeStreamSource, - TextSource, - AnalyzeSource, + FileSource, ) from .response import AsyncAnalyzeResponse, AnalyzeResponse @@ -29,27 +27,15 @@ class AnalyzeClient(AbstractSyncRestClient): Provides methods for transcribing text from URLs, files, etc. """ - def __init__(self, config): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - super().__init__(config) - - """ - Analyze text from a URL source. - - Args: - source (UrlSource): The URL source of the text to ingest. - options (AnalyzeOptions): Additional options for the ingest (default is None). - endpoint (str): The API endpoint for the ingest (default is "v1/read"). - - Returns: - AnalyzeResponse: An object containing the result. + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions - Raises: - DeepgramTypeError: Raised for known API errors. - """ + def __init__(self, config: DeepgramClientOptions): + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + super().__init__(config) def analyze_url( self, @@ -60,15 +46,31 @@ def analyze_url( timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/read", ) -> Union[AnalyzeResponse, AsyncAnalyzeResponse]: - self.logger.debug("AnalyzeClient.analyze_url ENTER") - - if (options is Dict and "callback" in options is not None) or ( - isinstance(options, AnalyzeOptions) and options.callback is not None - ): - self.logger.debug("AnalyzeClient.analyze_url LEAVE") + """ + Analyze text from a URL source. + + Args: + source (UrlSource): The URL source of the text to ingest. + options (AnalyzeOptions): Additional options for the ingest (default is None). + endpoint (str): The API endpoint for the ingest (default is "v1/read"). + + Returns: + AnalyzeResponse: An object containing the result. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AnalyzeClient.analyze_url ENTER") + + if ( + isinstance(options, dict) + and "callback" in options + and options["callback"] is not None + ) or (isinstance(options, AnalyzeOptions) and options.callback is not None): + self._logger.debug("AnalyzeClient.analyze_url LEAVE") return self.analyze_url_callback( source, - callback=options.callback, + callback=options["callback"], options=options, addons=addons, headers=headers, @@ -76,27 +78,27 @@ def analyze_url( endpoint=endpoint, ) - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if is_url_source(source): body = source else: - self.logger.error("Unknown transcription source type") - self.logger.debug("AnalyzeClient.analyze_url LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("AnalyzeClient.analyze_url LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, AnalyzeOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AnalyzeClient.analyze_url LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AnalyzeClient.analyze_url LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, AnalyzeOptions): - self.logger.info("AnalyzeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("AnalyzeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, options=options, @@ -105,29 +107,13 @@ def analyze_url( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AnalyzeResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("analyze_url succeeded") - self.logger.debug("AnalyzeClient.analyze_url LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("analyze_url succeeded") + self._logger.debug("AnalyzeClient.analyze_url LEAVE") return res - """ - Transcribes audio from a URL source and sends the result to a callback URL. - - Args: - source (UrlSource): The URL source of the audio to transcribe. - callback (str): The callback URL where the transcription results will be sent. - options (AnalyzeOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/read"). - - Returns: - AsyncAnalyzeResponse: An object containing the request_id or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - def analyze_url_callback( self, source: UrlSource, @@ -138,9 +124,24 @@ def analyze_url_callback( timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/read", ) -> AsyncAnalyzeResponse: - self.logger.debug("AnalyzeClient.analyze_url_callback ENTER") + """ + Transcribes audio from a URL source and sends the result to a callback URL. - url = f"{self.config.url}/{endpoint}" + Args: + source (UrlSource): The URL source of the audio to transcribe. + callback (str): The callback URL where the transcription results will be sent. + options (AnalyzeOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/read"). + + Returns: + AsyncAnalyzeResponse: An object containing the request_id or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AnalyzeClient.analyze_url_callback ENTER") + + url = f"{self._config.url}/{endpoint}" if options is None: options = {} if isinstance(options, AnalyzeOptions): @@ -150,23 +151,23 @@ def analyze_url_callback( if is_url_source(source): body = source else: - self.logger.error("Unknown transcription source type") - self.logger.debug("AnalyzeClient.analyze_url_callback LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("AnalyzeClient.analyze_url_callback LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, AnalyzeOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AnalyzeClient.analyze_url_callback LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AnalyzeClient.analyze_url_callback LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, AnalyzeOptions): - self.logger.info("AnalyzeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("AnalyzeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, options=options, @@ -175,46 +176,47 @@ def analyze_url_callback( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AsyncAnalyzeResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("analyze_url_callback succeeded") - self.logger.debug("AnalyzeClient.analyze_url_callback LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("analyze_url_callback succeeded") + self._logger.debug("AnalyzeClient.analyze_url_callback LEAVE") return res - """ - Analyze text from a local file source. - - Args: - source (TextSource): The local file source of the text to ingest. - options (AnalyzeOptions): Additional options for the ingest (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/read"). - - Returns: - AnalyzeResponse: An object containing the transcription result or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - def analyze_text( self, - source: TextSource, + source: FileSource, options: Optional[Union[AnalyzeOptions, Dict]] = None, addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/read", ) -> Union[AnalyzeResponse, AsyncAnalyzeResponse]: - self.logger.debug("AnalyzeClient.analyze_text ENTER") - - if (options is Dict and "callback" in options is not None) or ( - isinstance(options, AnalyzeOptions) and options.callback is not None - ): - self.logger.debug("AnalyzeClient.analyze_text LEAVE") + """ + Analyze text from a local file source. + + Args: + source (TextSource): The local file source of the text to ingest. + options (AnalyzeOptions): Additional options for the ingest (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/read"). + + Returns: + AnalyzeResponse: An object containing the transcription result or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AnalyzeClient.analyze_text ENTER") + + if ( + isinstance(options, dict) + and "callback" in options + and options["callback"] is not None + ) or (isinstance(options, AnalyzeOptions) and options.callback is not None): + self._logger.debug("AnalyzeClient.analyze_text LEAVE") return self.analyze_text_callback( source, - callback=options.callback, + callback=options["callback"], options=options, addons=addons, headers=headers, @@ -222,28 +224,28 @@ def analyze_text( endpoint=endpoint, ) - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if is_buffer_source(source): - body = source["buffer"] + body = source["buffer"] # type: ignore elif is_readstream_source(source): - body = source["stream"] + body = source["stream"] # type: ignore else: - self.logger.error("Unknown transcription source type") - self.logger.debug("AnalyzeClient.analyze_text LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("AnalyzeClient.analyze_text LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, AnalyzeOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AnalyzeClient.analyze_text LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AnalyzeClient.analyze_text LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) + self._logger.info("url: %s", url) if isinstance(options, AnalyzeOptions): - self.logger.info("AnalyzeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("AnalyzeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, options=options, @@ -252,32 +254,16 @@ def analyze_text( content=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AnalyzeResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("analyze_text succeeded") - self.logger.debug("AnalyzeClient.analyze_text LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("analyze_text succeeded") + self._logger.debug("AnalyzeClient.analyze_text LEAVE") return res - """ - Transcribes audio from a local file source and sends the result to a callback URL. - - Args: - source (TextSource): The local file source of the audio to transcribe. - callback (str): The callback URL where the transcription results will be sent. - options (AnalyzeOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/read"). - - Returns: - AsyncAnalyzeResponse: An object containing the request_id or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - - def analyze_file_callback( + def analyze_text_callback( self, - source: TextSource, + source: FileSource, callback: str, options: Optional[Union[AnalyzeOptions, Dict]] = None, addons: Optional[Dict] = None, @@ -285,9 +271,24 @@ def analyze_file_callback( timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/read", ) -> AsyncAnalyzeResponse: - self.logger.debug("AnalyzeClient.analyze_file_callback ENTER") + """ + Transcribes audio from a local file source and sends the result to a callback URL. + + Args: + source (TextSource): The local file source of the audio to transcribe. + callback (str): The callback URL where the transcription results will be sent. + options (AnalyzeOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/read"). + + Returns: + AsyncAnalyzeResponse: An object containing the request_id or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AnalyzeClient.analyze_file_callback ENTER") - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if options is None: options = {} if isinstance(options, AnalyzeOptions): @@ -295,26 +296,26 @@ def analyze_file_callback( else: options["callback"] = callback if is_buffer_source(source): - body = source["buffer"] + body = source["buffer"] # type: ignore elif is_readstream_source(source): - body = source["stream"] + body = source["stream"] # type: ignore else: - self.logger.error("Unknown transcription source type") - self.logger.debug("AnalyzeClient.analyze_file_callback LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("AnalyzeClient.analyze_file_callback LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, AnalyzeOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AnalyzeClient.analyze_file_callback LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AnalyzeClient.analyze_file_callback LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) + self._logger.info("url: %s", url) if isinstance(options, AnalyzeOptions): - self.logger.info("AnalyzeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("AnalyzeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, options=options, @@ -323,9 +324,9 @@ def analyze_file_callback( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AsyncAnalyzeResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("analyze_file_callback succeeded") - self.logger.debug("AnalyzeClient.analyze_file_callback LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("analyze_file_callback succeeded") + self._logger.debug("AnalyzeClient.analyze_file_callback LEAVE") return res diff --git a/deepgram/clients/analyze/v1/helpers.py b/deepgram/clients/analyze/v1/helpers.py index 0dfb2589..ef6746b3 100644 --- a/deepgram/clients/analyze/v1/helpers.py +++ b/deepgram/clients/analyze/v1/helpers.py @@ -6,12 +6,21 @@ def is_buffer_source(provided_source: AnalyzeSource) -> bool: + """ + Check if the provided source is a buffer source. + """ return "buffer" in provided_source def is_readstream_source(provided_source: AnalyzeSource) -> bool: + """ + Check if the provided source is a readstream source. + """ return "stream" in provided_source def is_url_source(provided_source: AnalyzeSource) -> bool: + """ + Check if the provided source is a url source. + """ return "url" in provided_source diff --git a/deepgram/clients/analyze/v1/options.py b/deepgram/clients/analyze/v1/options.py index faf3a3cd..7b23e0e9 100644 --- a/deepgram/clients/analyze/v1/options.py +++ b/deepgram/clients/analyze/v1/options.py @@ -2,18 +2,20 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +from typing import List, Union, Optional +import logging + from dataclasses import dataclass, field -from dataclasses_json import dataclass_json, config +from dataclasses_json import config as dataclass_config, DataClassJsonMixin -from io import BufferedReader -from typing import List, Union, Optional -from typing_extensions import TypedDict -import logging, verboselogs +from deepgram.utils import verboselogs +from ...common import FileSource, StreamSource, UrlSource -@dataclass_json @dataclass -class AnalyzeOptions: +class AnalyzeOptions( + DataClassJsonMixin +): # pylint: disable=too-many-instance-attributes """ Contains all the options for the AnalyzeOptions. @@ -22,37 +24,37 @@ class AnalyzeOptions: """ callback: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) callback_method: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) custom_intent: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) custom_intent_mode: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) custom_topic: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) custom_topic_mode: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) intents: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) language: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) summarize: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) topics: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -66,11 +68,13 @@ def __str__(self) -> str: return self.to_json(indent=4) def check(self): - verboselogs.install() - logger = logging.getLogger(__name__) + """ + Check the options for the AnalyzeOptions. + """ + logger = verboselogs.VerboseLogger(__name__) logger.addHandler(logging.StreamHandler()) prev = logger.level - logger.setLevel(logging.ERROR) + logger.setLevel(verboselogs.ERROR) # no op at the moment @@ -79,47 +83,8 @@ def check(self): return True -class AnalyzeStreamSource(TypedDict): - """ - Represents a data source for reading binary data from a stream-like source. - - This class is used to specify a source of binary data that can be read from - a stream, such as an audio file in .wav format. - - Attributes: - stream (BufferedReader): A BufferedReader object for reading binary data. - """ - - stream: BufferedReader - - -class UrlSource(TypedDict): - """ - Represents a data source for specifying the location of a file via a URL. - - This class is used to specify a hosted file URL, typically pointing to an - externally hosted file, such as an audio file hosted on a server or the internet. - - Attributes: - url (str): The URL pointing to the hosted file. - """ - - url: str - - -class BufferSource(TypedDict): - """ - Represents a data source for handling raw binary data. - - This class is used to specify raw binary data, such as audio data in its - binary form, which can be captured from a microphone or generated synthetically. - - Attributes: - buffer (bytes): The binary data. - """ - - buffer: bytes - +AnalyzeStreamSource = StreamSource -AnalyzeSource = Union[UrlSource, BufferSource, AnalyzeStreamSource] -TextSource = Union[BufferSource, AnalyzeStreamSource] +# FileSource = FileSource +# UrlSource = UrlSource +AnalyzeSource = Union[FileSource, UrlSource, AnalyzeStreamSource] diff --git a/deepgram/clients/analyze/v1/response.py b/deepgram/clients/analyze/v1/response.py index 1c1bc712..346daa1b 100644 --- a/deepgram/clients/analyze/v1/response.py +++ b/deepgram/clients/analyze/v1/response.py @@ -2,18 +2,23 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +from typing import List, Optional + from dataclasses import dataclass, field -from dataclasses_json import config, dataclass_json -from typing import List, Optional, TypedDict, Dict -from ..enums import Sentiment +from dataclasses_json import config as dataclass_config, DataClassJsonMixin + +from ...common import Sentiment # Async Analyze Response Types: -@dataclass_json @dataclass -class AsyncAnalyzeResponse: +class AsyncAnalyzeResponse(DataClassJsonMixin): + """ + Async Analyze Response + """ + request_id: str = "" def __getitem__(self, key): @@ -27,9 +32,12 @@ def __str__(self) -> str: # Analyze Response Types: -@dataclass_json @dataclass -class IntentsInfo: +class IntentsInfo(DataClassJsonMixin): + """ + Intents Info + """ + model_uuid: str = "" input_tokens: int = 0 output_tokens: int = 0 @@ -42,9 +50,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class SentimentInfo: +class SentimentInfo(DataClassJsonMixin): + """ + Sentiment Info + """ + model_uuid: str = "" input_tokens: int = 0 output_tokens: int = 0 @@ -57,9 +68,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class SummaryInfo: +class SummaryInfo(DataClassJsonMixin): + """ + Summary Info + """ + model_uuid: str = "" input_tokens: int = 0 output_tokens: int = 0 @@ -72,9 +86,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class TopicsInfo: +class TopicsInfo(DataClassJsonMixin): + """ + Topics Info + """ + model_uuid: str = "" input_tokens: int = 0 output_tokens: int = 0 @@ -87,23 +104,26 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Metadata: +class Metadata(DataClassJsonMixin): + """ + Metadata + """ + request_id: str = "" created: str = "" language: str = "" intents_info: Optional[IntentsInfo] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment_info: Optional[SentimentInfo] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) summary_info: Optional[SummaryInfo] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) topics_info: Optional[TopicsInfo] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -122,10 +142,13 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Average: - sentiment: Sentiment = None +class Average(DataClassJsonMixin): + """ + Average + """ + + sentiment: Sentiment sentiment_score: float = 0 def __getitem__(self, key): @@ -138,9 +161,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Summary: +class Summary(DataClassJsonMixin): + """ + Summary + """ + text: str = "" def __getitem__(self, key): @@ -151,9 +177,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Topic: +class Topic(DataClassJsonMixin): + """ + Topic + """ + topic: str = "" confidence_score: float = 0 @@ -165,9 +194,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Intent: +class Intent(DataClassJsonMixin): + """ + Intent + """ + intent: str = "" confidence_score: float = 0 @@ -179,21 +211,24 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Segment: +class Segment(DataClassJsonMixin): + """ + Segment + """ + text: str = "" start_word: int = 0 end_word: int = 0 sentiment: Optional[Sentiment] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment_score: Optional[float] = 0 intents: Optional[List[Intent]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) topics: Optional[List[Topic]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -210,11 +245,14 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Sentiments: - segments: List[Segment] = None - average: Average = None +class Sentiments(DataClassJsonMixin): + """ + Sentiments + """ + + average: Average + segments: List[Segment] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -230,10 +268,13 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Topics: - segments: List[Segment] = None +class Topics(DataClassJsonMixin): + """ + Topics + """ + + segments: List[Segment] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -247,10 +288,13 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Intents: - segments: List[Segment] = None +class Intents(DataClassJsonMixin): + """ + Intents + """ + + segments: List[Segment] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -264,20 +308,23 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Results: +class Results(DataClassJsonMixin): + """ + Results + """ + summary: Optional[Summary] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiments: Optional[Sentiments] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) topics: Optional[Topics] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) intents: Optional[Intents] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -299,14 +346,17 @@ def __str__(self) -> str: # Analyze Response Result: -@dataclass_json @dataclass -class AnalyzeResponse: +class AnalyzeResponse(DataClassJsonMixin): + """ + Analyze Response + """ + metadata: Optional[Metadata] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) results: Optional[Results] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -321,7 +371,4 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json -@dataclass -class SyncAnalyzeResponse(AnalyzeResponse): - pass +SyncAnalyzeResponse = AnalyzeResponse diff --git a/deepgram/clients/common/__init__.py b/deepgram/clients/common/__init__.py new file mode 100644 index 00000000..ef39395d --- /dev/null +++ b/deepgram/clients/common/__init__.py @@ -0,0 +1,21 @@ +# Copyright 2023-2024 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 + +from .v1 import ( + TextSource as TextSourceLatest, + BufferSource as BufferSourceLatest, + StreamSource as StreamSourceLatest, + FileSource as FileSourceLatest, + UrlSource as UrlSourceLatest, + Sentiment as SentimentLatest, +) + +UrlSource = UrlSourceLatest +TextSource = TextSourceLatest +BufferSource = BufferSourceLatest +StreamSource = StreamSourceLatest + + +Sentiment = SentimentLatest +FileSource = FileSourceLatest diff --git a/deepgram/clients/common/v1/__init__.py b/deepgram/clients/common/v1/__init__.py new file mode 100644 index 00000000..4f283bb0 --- /dev/null +++ b/deepgram/clients/common/v1/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2023-2024 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 + +from .enums import Sentiment + +from .options import ( + TextSource, + BufferSource, + StreamSource, + FileSource, + UrlSource, +) diff --git a/deepgram/clients/analyze/enums.py b/deepgram/clients/common/v1/enums.py similarity index 68% rename from deepgram/clients/analyze/enums.py rename to deepgram/clients/common/v1/enums.py index e910bc7f..a25893e9 100644 --- a/deepgram/clients/analyze/enums.py +++ b/deepgram/clients/common/v1/enums.py @@ -2,14 +2,16 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -from enum import Enum +from aenum import StrEnum -""" -Constants mapping to events from the Deepgram API -""" +# Constants mapping to events from the Deepgram API -class Sentiment(str, Enum): +class Sentiment(StrEnum): + """ + Sentiment values. + """ + UNKNOWN: str = "" NEGATIVE: str = "negative" NEUTRAL: str = "neutral" diff --git a/deepgram/clients/common/v1/options.py b/deepgram/clients/common/v1/options.py new file mode 100644 index 00000000..c2db15d9 --- /dev/null +++ b/deepgram/clients/common/v1/options.py @@ -0,0 +1,65 @@ +# Copyright 2024 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 + +from io import BufferedReader +from typing import Union +from typing_extensions import TypedDict + + +class StreamSource(TypedDict): + """ + Represents a data source for reading binary data from a stream-like source. + + This class is used to specify a source of binary data that can be read from + a stream, such as an audio file in .wav format. + + Attributes: + stream (BufferedReader): A BufferedReader object for reading binary data. + """ + + stream: BufferedReader + + +class UrlSource(TypedDict): + """ + Represents a data source for specifying the location of a file via a URL. + + This class is used to specify a hosted file URL, typically pointing to an + externally hosted file, such as an audio file hosted on a server or the internet. + + Attributes: + url (str): The URL pointing to the hosted file. + """ + + url: str + + +class BufferSource(TypedDict): + """ + Represents a data source for handling raw binary data. + + This class is used to specify raw binary data, such as audio data in its + binary form, which can be captured from a microphone or generated synthetically. + + Attributes: + buffer (bytes): The binary data. + """ + + buffer: bytes + + +class TextSource(TypedDict): + """ + Represents a data source for reading binary data from a text-like source. + + This class is used to specify a source of text data that can be read from. + + Attributes: + text (str): A string for reading text data. + """ + + text: str + + +FileSource = Union[TextSource, BufferSource, StreamSource] diff --git a/deepgram/clients/helpers.py b/deepgram/clients/helpers.py index d01df6e1..1a634cca 100644 --- a/deepgram/clients/helpers.py +++ b/deepgram/clients/helpers.py @@ -2,12 +2,14 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -from urllib.parse import urlparse, urlunparse, parse_qs, urlencode +from urllib.parse import urlparse, parse_qs, urlencode from typing import Dict -# appends query parameters to a URL def append_query_params(url: str, params: Dict) -> str: + """ + Appends query parameters to a URL. + """ parsed_url = urlparse(url) query_params = parse_qs(parsed_url.query) diff --git a/deepgram/clients/listen.py b/deepgram/clients/listen.py index c61ac789..9e2278c2 100644 --- a/deepgram/clients/listen.py +++ b/deepgram/clients/listen.py @@ -3,48 +3,12 @@ # SPDX-License-Identifier: MIT from importlib import import_module -import logging, verboselogs -from typing import Union +import logging +from deepgram.utils import verboselogs from ..options import DeepgramClientOptions from .errors import DeepgramModuleError -# live client -# classes and input -from .prerecorded import ( - PreRecordedClient, - AsyncPreRecordedClient, - PrerecordedOptions, -) - -# responses -from .prerecorded import ( - AsyncPrerecordedResponse, - PrerecordedResponse, - SyncPrerecordedResponse, -) - -# live client -# classes and input -from .live import ( - LiveClient, - AsyncLiveClient, - LiveOptions, - LiveTranscriptionEvents, -) - -# responses -from .live import ( - OpenResponse, - LiveResultResponse, - MetadataResponse, - SpeechStartedResponse, - UtteranceEndResponse, - CloseResponse, - ErrorResponse, - UnhandledResponse, -) - class Listen: """ @@ -67,102 +31,128 @@ class Listen: asyncprerecorded: Returns an (Async) PreRecordedClient instance for interacting with Deepgram's prerecorded transcription services. """ + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + def __init__(self, config: DeepgramClientOptions): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config @property def prerecorded(self): - return self.Version(self.config, "prerecorded") + """ + Returns a PreRecordedClient instance for interacting with Deepgram's prerecorded transcription services. + """ + return self.Version(self._config, "prerecorded") @property def asyncprerecorded(self): - return self.Version(self.config, "asyncprerecorded") + """ + Returns an AsyncPreRecordedClient instance for interacting with Deepgram's prerecorded transcription services. + """ + return self.Version(self._config, "asyncprerecorded") @property def live(self): - return self.Version(self.config, "live") + """ + Returns a LiveClient instance for interacting with Deepgram's transcription services. + """ + return self.Version(self._config, "live") @property def asynclive(self): - return self.Version(self.config, "asynclive") + """ + Returns an AsyncLiveClient instance for interacting with Deepgram's transcription services. + """ + return self.Version(self._config, "asynclive") # INTERNAL CLASSES class Version: + """ + Represents a version of the Deepgram API. + """ + + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + _parent: str + def __init__(self, config, parent: str): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - self.parent = parent + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + self._parent = parent # FUTURE VERSIONING: # When v2 or v1.1beta1 or etc. This allows easy access to the latest version of the API. # @property # def latest(self): - # match self.parent: + # match self._parent: # case "live": - # return LiveClient(self.config) + # return LiveClient(self._config) # case "prerecorded": - # return PreRecordedClient(self.config) + # return PreRecordedClient(self._config) # case _: # raise DeepgramModuleError("Invalid parent") def v(self, version: str = ""): - self.logger.debug("Version.v ENTER") - self.logger.info("version: %s", version) + """ + Returns a specific version of the Deepgram API. + """ + self._logger.debug("Version.v ENTER") + self._logger.info("version: %s", version) if len(version) == 0: - self.logger.error("version is empty") - self.logger.debug("Version.v LEAVE") + self._logger.error("version is empty") + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Invalid module version") parent = "" - fileName = "" - className = "" - match self.parent: + file_name = "" + class_name = "" + match self._parent: case "live": parent = "live" - fileName = "client" - className = "LiveClient" + file_name = "client" + class_name = "LiveClient" case "asynclive": parent = "live" - fileName = "async_client" - className = "AsyncLiveClient" + file_name = "async_client" + class_name = "AsyncLiveClient" case "prerecorded": parent = "prerecorded" - fileName = "client" - className = "PreRecordedClient" + file_name = "client" + class_name = "PreRecordedClient" case "asyncprerecorded": parent = "prerecorded" - fileName = "async_client" - className = "AsyncPreRecordedClient" + file_name = "async_client" + class_name = "AsyncPreRecordedClient" case _: - self.logger.error("parent unknown: %s", self.parent) - self.logger.debug("Version.v LEAVE") + self._logger.error("parent unknown: %s", self._parent) + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Invalid parent type") # create class path - path = f"deepgram.clients.{parent}.v{version}.{fileName}" - self.logger.info("path: %s", path) - self.logger.info("className: %s", className) + path = f"deepgram.clients.{parent}.v{version}.{file_name}" + self._logger.info("path: %s", path) + self._logger.info("class_name: %s", class_name) # import class mod = import_module(path) if mod is None: - self.logger.error("module path is None") - self.logger.debug("Version.v LEAVE") + self._logger.error("module path is None") + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Unable to find package") - my_class = getattr(mod, className) + my_class = getattr(mod, class_name) if my_class is None: - self.logger.error("my_class is None") - self.logger.debug("Version.v LEAVE") + self._logger.error("my_class is None") + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Unable to find class") # instantiate class - myClass = my_class(self.config) - self.logger.notice("Version.v succeeded") - self.logger.debug("Version.v LEAVE") - return myClass + my_class = my_class(self._config) + self._logger.notice("Version.v succeeded") + self._logger.debug("Version.v LEAVE") + return my_class diff --git a/deepgram/clients/live/__init__.py b/deepgram/clients/live/__init__.py index 66b6d1cc..71e167c3 100644 --- a/deepgram/clients/live/__init__.py +++ b/deepgram/clients/live/__init__.py @@ -5,7 +5,7 @@ from .client import LiveClient from .client import AsyncLiveClient from .client import LiveOptions -from .client import LiveTranscriptionEvents +from .enums import LiveTranscriptionEvents from .client import ( OpenResponse, LiveResultResponse, @@ -17,4 +17,4 @@ UnhandledResponse, ) -from ...options import DeepgramClientOptions +from ...options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/live/client.py b/deepgram/clients/live/client.py index de9d1dbe..d884b455 100644 --- a/deepgram/clients/live/client.py +++ b/deepgram/clients/live/client.py @@ -5,7 +5,6 @@ from .v1.client import LiveClient as LiveClientLatest from .v1.async_client import AsyncLiveClient as AsyncLiveClientLatest from .v1.options import LiveOptions as LiveOptionsLatest -from .enums import LiveTranscriptionEvents from .v1.response import ( OpenResponse as OpenResponseLatest, LiveResultResponse as LiveResultResponseLatest, @@ -17,100 +16,22 @@ UnhandledResponse as UnhandledResponseLatest, ) -""" -The vX/client.py points to the current supported version in the SDK. -Older versions are supported in the SDK for backwards compatibility. -""" +# The vX/client.py points to the current supported version in the SDK. +# Older versions are supported in the SDK for backwards compatibility. # input -class LiveOptions(LiveOptionsLatest): - """ - pass through for LiveOptions based on API version - """ - - pass - - -# responses -class OpenResponse(OpenResponseLatest): - """ - pass through for OpenResponse based on API version - """ - - pass - - -class LiveResultResponse(LiveResultResponseLatest): - """ - pass through for LiveResultResponse based on API version - """ - - pass - - -class MetadataResponse(MetadataResponseLatest): - """ - pass through for MetadataResponse based on API version - """ - - pass - - -class SpeechStartedResponse(SpeechStartedResponseLatest): - """ - pass through for SpeechStartedResponse based on API version - """ - - pass - - -class UtteranceEndResponse(UtteranceEndResponseLatest): - """ - pass through for UtteranceEndResponse based on API version - """ - - pass - - -class CloseResponse(CloseResponseLatest): - """ - pass through for CloseResponse based on API version - """ - - pass - - -class ErrorResponse(ErrorResponseLatest): - """ - pass through for ErrorResponse based on API version - """ - - pass - - -class UnhandledResponse(UnhandledResponseLatest): - """ - pass through for UnhandledResponse based on API version - """ - - pass +LiveOptions = LiveOptionsLatest +OpenResponse = OpenResponseLatest +LiveResultResponse = LiveResultResponseLatest +MetadataResponse = MetadataResponseLatest +SpeechStartedResponse = SpeechStartedResponseLatest +UtteranceEndResponse = UtteranceEndResponseLatest +CloseResponse = CloseResponseLatest +ErrorResponse = ErrorResponseLatest +UnhandledResponse = UnhandledResponseLatest # clients -class LiveClient(LiveClientLatest): - """ - Please see LiveClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) - - -class AsyncLiveClient(AsyncLiveClientLatest): - """ - Please see LiveClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) +LiveClient = LiveClientLatest +AsyncLiveClient = AsyncLiveClientLatest diff --git a/deepgram/clients/live/enums.py b/deepgram/clients/live/enums.py index 6db4852f..8e39ae02 100644 --- a/deepgram/clients/live/enums.py +++ b/deepgram/clients/live/enums.py @@ -2,14 +2,16 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -from enum import Enum +from aenum import StrEnum -""" -Constants mapping to events from the Deepgram API -""" +# Constants mapping to events from the Deepgram API -class LiveTranscriptionEvents(str, Enum): +class LiveTranscriptionEvents(StrEnum): + """ + Enumerates the possible events that can be received from the Deepgram API + """ + Open: str = "Open" Close: str = "Close" Transcript: str = "Results" diff --git a/deepgram/clients/live/helpers.py b/deepgram/clients/live/helpers.py index d125cdf2..ece75622 100644 --- a/deepgram/clients/live/helpers.py +++ b/deepgram/clients/live/helpers.py @@ -3,25 +3,29 @@ # SPDX-License-Identifier: MIT from urllib.parse import urlparse, urlunparse, parse_qs, urlencode -from typing import Dict +from typing import Dict, Optional import re # This function appends query parameters to a URL -def append_query_params(url: str, params: Dict): +def append_query_params(url: str, params: Optional[Dict] = None): + """ + Appends query parameters to a URL + """ parsed_url = urlparse(url) query_params = parse_qs(parsed_url.query) - for key, value in params.items(): - if value is None: - continue - if isinstance(value, bool): - value = str(value).lower() - if isinstance(value, list): - for item in value: - query_params[key] = query_params.get(key, []) + [str(item)] - else: - query_params[key] = [str(value)] + if params is not None: + for key, value in params.items(): + if value is None: + continue + if isinstance(value, bool): + value = str(value).lower() + if isinstance(value, list): + for item in value: + query_params[key] = query_params.get(key, []) + [str(item)] + else: + query_params[key] = [str(value)] updated_query_string = urlencode(query_params, doseq=True) updated_url = parsed_url._replace(query=updated_query_string).geturl() @@ -30,6 +34,9 @@ def append_query_params(url: str, params: Dict): # This function converts a URL to a WebSocket URL def convert_to_websocket_url(base_url: str, endpoint: str): + """ + Converts a URL to a WebSocket URL + """ if re.match(r"^https?://", base_url, re.IGNORECASE): base_url = base_url.replace("https://", "").replace("http://", "") if not re.match(r"^wss?://", base_url, re.IGNORECASE): diff --git a/deepgram/clients/live/v1/__init__.py b/deepgram/clients/live/v1/__init__.py index 17980286..7ff55de4 100644 --- a/deepgram/clients/live/v1/__init__.py +++ b/deepgram/clients/live/v1/__init__.py @@ -5,7 +5,7 @@ from .client import LiveClient from .async_client import AsyncLiveClient from .options import LiveOptions -from ....options import DeepgramClientOptions +from ....options import DeepgramClientOptions, ClientOptionsFromEnv from .response import ( OpenResponse, LiveResultResponse, diff --git a/deepgram/clients/live/v1/async_client.py b/deepgram/clients/live/v1/async_client.py index 6296085c..36facb3d 100644 --- a/deepgram/clients/live/v1/async_client.py +++ b/deepgram/clients/live/v1/async_client.py @@ -3,14 +3,17 @@ # SPDX-License-Identifier: MIT import asyncio import json +import logging +from typing import Dict, Union, Optional, cast, Any + import websockets -import logging, verboselogs -from typing import Dict, Union, Optional +from websockets.client import WebSocketClientProtocol +from deepgram.utils import verboselogs from ....options import DeepgramClientOptions from ..enums import LiveTranscriptionEvents from ..helpers import convert_to_websocket_url, append_query_params -from ..errors import DeepgramError, DeepgramWebsocketError +from ..errors import DeepgramError from .response import ( OpenResponse, @@ -29,7 +32,7 @@ PING_INTERVAL = 20 -class AsyncLiveClient: +class AsyncLiveClient: # pylint: disable=too-many-instance-attributes """ Client for interacting with Deepgram's live transcription services over WebSockets. @@ -39,22 +42,38 @@ class AsyncLiveClient: config (DeepgramClientOptions): all the options for the client. """ + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + _endpoint: str + _websocket_url: str + + _socket: WebSocketClientProtocol + _event_handlers: Dict[LiveTranscriptionEvents, list] + _listen_thread: asyncio.Task + _keep_alive_thread: asyncio.Task + + _kwargs: Optional[Dict] = None + _addons: Optional[Dict] = None + _options: Optional[Dict] = None + _headers: Optional[Dict] = None + def __init__(self, config: DeepgramClientOptions): if config is None: raise DeepgramError("Config are required") - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) - self.config = config - self.endpoint = "v1/listen" - self._socket = None + self._config = config + self._endpoint = "v1/listen" self._exit_event = asyncio.Event() - self._event_handlers = {event: [] for event in LiveTranscriptionEvents} - self.websocket_url = convert_to_websocket_url(self.config.url, self.endpoint) + self._event_handlers = { + event: [] for event in LiveTranscriptionEvents.__members__.values() + } + self._websocket_url = convert_to_websocket_url(self._config.url, self._endpoint) - # starts the WebSocket connection for live transcription + # pylint: disable=too-many-branches,too-many-statements async def start( self, options: Optional[Union[LiveOptions, Dict]] = None, @@ -66,26 +85,20 @@ async def start( """ Starts the WebSocket connection for live transcription. """ - self.logger.debug("AsyncLiveClient.start ENTER") - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) - self.logger.info("members: %s", members) - self.logger.info("kwargs: %s", kwargs) + self._logger.debug("AsyncLiveClient.start ENTER") + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) + self._logger.info("members: %s", members) + self._logger.info("kwargs: %s", kwargs) if isinstance(options, LiveOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AsyncLiveClient.start LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AsyncLiveClient.start LEAVE") raise DeepgramError("Fatal transcription options error") - if self._socket is not None: - self.logger.error("socket is already initialized") - self.logger.debug("AsyncLiveClient.start LEAVE") - raise DeepgramWebsocketError("Websocket already started") - - self.options = options - self.addons = addons - self.headers = headers + self._addons = addons + self._headers = headers # add "members" as members of the class if members is not None: @@ -93,29 +106,33 @@ async def start( # set kwargs as members of the class if kwargs is not None: - self.kwargs = kwargs + self._kwargs = kwargs else: - self.kwargs = dict() + self._kwargs = {} if isinstance(options, LiveOptions): - self.logger.info("LiveOptions switching class -> dict") - self.options = self.options.to_dict() + self._logger.info("LiveOptions switching class -> dict") + self._options = cast(Dict[str, str], options.to_dict()) + elif options is not None: + self._options = options + else: + self._options = {} - combined_options = self.options - if addons is not None: - self.logger.info("merging addons to options") - combined_options.update(self.addons) - self.logger.info("new options: %s", combined_options) - self.logger.debug("combined_options: %s", combined_options) + combined_options = self._options + if self._addons is not None: + self._logger.info("merging addons to options") + combined_options.update(self._addons) + self._logger.info("new options: %s", combined_options) + self._logger.debug("combined_options: %s", combined_options) - combined_headers = self.config.headers - if headers is not None: - self.logger.info("merging headers to options") - combined_headers.update(self.headers) - self.logger.info("new headers: %s", combined_headers) - self.logger.debug("combined_headers: %s", combined_headers) + combined_headers = self._config.headers + if self._headers is not None: + self._logger.info("merging headers to options") + combined_headers.update(self._headers) + self._logger.info("new headers: %s", combined_headers) + self._logger.debug("combined_headers: %s", combined_headers) - url_with_params = append_query_params(self.websocket_url, combined_options) + url_with_params = append_query_params(self._websocket_url, combined_options) try: self._socket = await websockets.connect( @@ -129,211 +146,267 @@ async def start( self._listen_thread = asyncio.create_task(self._listening()) # keepalive thread - if self.config.options.get("keepalive") == "true": - self.logger.notice("keepalive is enabled") + if self._config.options.get("keepalive") == "true": + self._logger.notice("keepalive is enabled") self._keep_alive_thread = asyncio.create_task(self._keep_alive()) else: - self.logger.notice("keepalive is disabled") - self._keep_alive_thread = None + self._logger.notice("keepalive is disabled") # push open event await self._emit( - LiveTranscriptionEvents.Open, - OpenResponse(type=LiveTranscriptionEvents.Open.value), + LiveTranscriptionEvents(LiveTranscriptionEvents.Open), + OpenResponse(type=LiveTranscriptionEvents.Open), ) - self.logger.notice("start succeeded") - self.logger.debug("AsyncLiveClient.start LEAVE") + self._logger.notice("start succeeded") + self._logger.debug("AsyncLiveClient.start LEAVE") return True except websockets.ConnectionClosed as e: - self.logger.error("exception: websockets.ConnectionClosed") - self.logger.debug("AsyncLiveClient.start LEAVE") - if self.config.options.get("termination_exception_connect") == "true": + self._logger.error("ConnectionClosed in AsyncLiveClient.start: %s", e) + self._logger.debug("AsyncLiveClient.start LEAVE") + if self._config.options.get("termination_exception_connect") == "true": raise return False except websockets.exceptions.WebSocketException as e: - self.logger.error("WebSocketException in AsyncLiveClient.start: %s", e) - self.logger.debug("AsyncLiveClient.start LEAVE") - if self.config.options.get("termination_exception_connect") == "true": + self._logger.error("WebSocketException in AsyncLiveClient.start: %s", e) + self._logger.debug("AsyncLiveClient.start LEAVE") + if self._config.options.get("termination_exception_connect") == "true": raise return False - except Exception as e: - self.logger.error("WebSocketException in AsyncLiveClient.start: %s", e) - self.logger.debug("AsyncLiveClient.start LEAVE") - if self.config.options.get("termination_exception_connect") == "true": + except Exception as e: # pylint: disable=broad-except + self._logger.error("WebSocketException in AsyncLiveClient.start: %s", e) + self._logger.debug("AsyncLiveClient.start LEAVE") + if self._config.options.get("termination_exception_connect") == "true": raise return False - # registers event handlers for specific events + # pylint: enable=too-many-branches,too-many-statements + def on(self, event: LiveTranscriptionEvents, handler) -> None: """ Registers event handlers for specific events. """ - self.logger.info("event fired: %s", event) - if event in LiveTranscriptionEvents and callable(handler): + self._logger.info("event fired: %s", event) + if event in LiveTranscriptionEvents.__members__.values() and callable(handler): self._event_handlers[event].append(handler) # triggers the registered event handlers for a specific event async def _emit(self, event: LiveTranscriptionEvents, *args, **kwargs) -> None: + """ + Emits events to the registered event handlers. + """ for handler in self._event_handlers[event]: - asyncio.create_task(handler(self, *args, **kwargs)) + if asyncio.iscoroutinefunction(handler): + await handler(self, *args, **kwargs) + else: + asyncio.create_task(handler(self, *args, **kwargs)) - # main loop for handling incoming messages + # pylint: disable=too-many-return-statements,too-many-statements,too-many-locals async def _listening(self) -> None: - self.logger.debug("AsyncLiveClient._listening ENTER") + """ + Listens for messages from the WebSocket connection. + """ + self._logger.debug("AsyncLiveClient._listening ENTER") while True: try: if self._exit_event.is_set(): - self.logger.notice("_listening exiting gracefully") - self.logger.debug("AsyncLiveClient._listening LEAVE") + self._logger.notice("_listening exiting gracefully") + self._logger.debug("AsyncLiveClient._listening LEAVE") return if self._socket is None: - self.logger.warning("socket is empty") - self.logger.debug("AsyncLiveClient._listening LEAVE") + self._logger.warning("socket is empty") + self._logger.debug("AsyncLiveClient._listening LEAVE") return - message = await self._socket.recv() + message = str(await self._socket.recv()) if message is None: - self.logger.spam("message is None") + self._logger.spam("message is None") continue data = json.loads(message) response_type = data.get("type") - self.logger.debug("response_type: %s, data: %s", response_type, data) + self._logger.debug("response_type: %s, data: %s", response_type, data) match response_type: - case LiveTranscriptionEvents.Open.value: - result = OpenResponse.from_json(message) - self.logger.verbose("OpenResponse: %s", result) + case LiveTranscriptionEvents.Open: + open_result: OpenResponse = OpenResponse.from_json(message) + self._logger.verbose("OpenResponse: %s", open_result) await self._emit( - LiveTranscriptionEvents.Open, - open=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Open), + open=open_result, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - case LiveTranscriptionEvents.Transcript.value: - result = LiveResultResponse.from_json(message) - self.logger.verbose("LiveResultResponse: %s", result) + case LiveTranscriptionEvents.Transcript: + msg_result: LiveResultResponse = LiveResultResponse.from_json( + message + ) + self._logger.verbose("LiveResultResponse: %s", msg_result) await self._emit( - LiveTranscriptionEvents.Transcript, - result=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Transcript), + result=msg_result, + **dict(cast(Dict[Any, Any], self._kwargs)), + ) + case LiveTranscriptionEvents.Metadata: + meta_result: MetadataResponse = MetadataResponse.from_json( + message ) - case LiveTranscriptionEvents.Metadata.value: - result = MetadataResponse.from_json(message) - self.logger.verbose("MetadataResponse: %s", result) + self._logger.verbose("MetadataResponse: %s", meta_result) await self._emit( - LiveTranscriptionEvents.Metadata, - metadata=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Metadata), + metadata=meta_result, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - case LiveTranscriptionEvents.SpeechStarted.value: - result = SpeechStartedResponse.from_json(message) - self.logger.verbose("SpeechStartedResponse: %s", result) + case LiveTranscriptionEvents.SpeechStarted: + ss_result: SpeechStartedResponse = ( + SpeechStartedResponse.from_json(message) + ) + self._logger.verbose("SpeechStartedResponse: %s", ss_result) await self._emit( - LiveTranscriptionEvents.SpeechStarted, - speech_started=result, - **dict(self.kwargs), + LiveTranscriptionEvents( + LiveTranscriptionEvents.SpeechStarted + ), + speech_started=ss_result, + **dict(cast(Dict[Any, Any], self._kwargs)), + ) + case LiveTranscriptionEvents.UtteranceEnd: + ue_result: UtteranceEndResponse = ( + UtteranceEndResponse.from_json(message) ) - case LiveTranscriptionEvents.UtteranceEnd.value: - result = UtteranceEndResponse.from_json(message) - self.logger.verbose("UtteranceEndResponse: %s", result) + self._logger.verbose("UtteranceEndResponse: %s", ue_result) await self._emit( - LiveTranscriptionEvents.UtteranceEnd, - utterance_end=result, - **dict(self.kwargs), + LiveTranscriptionEvents( + LiveTranscriptionEvents.UtteranceEnd + ), + utterance_end=ue_result, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - case LiveTranscriptionEvents.Close.value: - result = CloseResponse.from_json(message) - self.logger.verbose("CloseResponse: %s", result) + case LiveTranscriptionEvents.Close: + close_result: CloseResponse = CloseResponse.from_json(message) + self._logger.verbose("CloseResponse: %s", close_result) await self._emit( - LiveTranscriptionEvents.Close, - close=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Close), + close=close_result, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - case LiveTranscriptionEvents.Error.value: - result = ErrorResponse.from_json(message) - self.logger.verbose("LiveTranscriptionEvents: %s", result) + case LiveTranscriptionEvents.Error: + err_error: ErrorResponse = ErrorResponse.from_json(message) + self._logger.verbose("ErrorResponse: %s", err_error) await self._emit( - LiveTranscriptionEvents.Error, - error=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), + error=err_error, + **dict(cast(Dict[Any, Any], self._kwargs)), ) case _: - self.logger.warning( + self._logger.warning( "Unknown Message: response_type: %s, data: %s", response_type, data, ) - unhandled = UnhandledResponse( - type=LiveTranscriptionEvents.Unhandled.value, + unhandled_error: UnhandledResponse = UnhandledResponse( + type=LiveTranscriptionEvents( + LiveTranscriptionEvents.Unhandled + ), raw=message, ) - self._emit( - LiveTranscriptionEvents.Unhandled, - unhandled=unhandled, - **dict(self.kwargs), + await self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Unhandled), + unhandled=unhandled_error, + **dict(cast(Dict[Any, Any], self._kwargs)), ) except websockets.exceptions.ConnectionClosedOK as e: - self.logger.notice(f"_listening({e.code}) exiting gracefully") - self.logger.debug("AsyncLiveClient._listening LEAVE") + self._logger.notice(f"_listening({e.code}) exiting gracefully") + self._logger.debug("AsyncLiveClient._listening LEAVE") return - except websockets.exceptions.WebSocketException as e: + except websockets.exceptions.ConnectionClosed as e: if e.code == 1000: - self.logger.notice(f"_listening({e.code}) exiting gracefully") - self.logger.debug("AsyncLiveClient._listening LEAVE") + self._logger.notice(f"_listening({e.code}) exiting gracefully") + self._logger.debug("AsyncLiveClient._listening LEAVE") return - self.logger.error( + self._logger.error( + "ConnectionClosed in AsyncLiveClient._listening with code %s: %s", + e.code, + e.reason, + ) + cc_error: ErrorResponse = ErrorResponse( + "ConnectionClosed in AsyncLiveClient._listening", + f"{e}", + "ConnectionClosed", + ) + await self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), + error=cc_error, + **dict(cast(Dict[Any, Any], self._kwargs)), + ) + + # signal exit and close + await self._signal_exit() + + self._logger.debug("AsyncLiveClient._listening LEAVE") + + if self._config.options.get("termination_exception") == "true": + raise + return + + except websockets.exceptions.WebSocketException as e: + self._logger.error( "WebSocketException in AsyncLiveClient._listening: %s", e ) - error: ErrorResponse = { - "type": "Exception", - "description": "WebSocketException in AsyncLiveClient._listening", - "message": f"{e}", - "variant": "", - } - self.logger.notice( - f"WebSocket exception in AsyncLiveClient._listening with code {e.code}: {e.reason}" + ws_error: ErrorResponse = ErrorResponse( + "WebSocketException in AsyncLiveClient._listening", + f"{e}", + "WebSocketException", + ) + await self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), + error=ws_error, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - await self._emit(LiveTranscriptionEvents.Error, error) # signal exit and close await self._signal_exit() - self.logger.debug("AsyncLiveClient._listening LEAVE") + self._logger.debug("AsyncLiveClient._listening LEAVE") - if self.config.options.get("termination_exception") == "true": + if self._config.options.get("termination_exception") == "true": raise return - except Exception as e: - self.logger.error("Exception in AsyncLiveClient._listening: %s", e) - error: ErrorResponse = { - "type": "Exception", - "description": "Exception in AsyncLiveClient._listening", - "message": f"{e}", - "variant": "", - } - self.logger.error("Exception in AsyncLiveClient._listening: %s", str(e)) - await self._emit(LiveTranscriptionEvents.Error, error) + except Exception as e: # pylint: disable=broad-except + self._logger.error("Exception in AsyncLiveClient._listening: %s", e) + e_error: ErrorResponse = ErrorResponse( + "Exception in AsyncLiveClient._listening", + f"{e}", + "Exception", + ) + await self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), + error=e_error, + **dict(cast(Dict[Any, Any], self._kwargs)), + ) # signal exit and close await self._signal_exit() - self.logger.debug("AsyncLiveClient._listening LEAVE") + self._logger.debug("AsyncLiveClient._listening LEAVE") - if self.config.options.get("termination_exception") == "true": + if self._config.options.get("termination_exception") == "true": raise return - # keep the connection alive by sending keepalive messages + # pylint: enable=too-many-return-statements,too-many-statements + + # pylint: disable=too-many-return-statements async def _keep_alive(self) -> None: - self.logger.debug("AsyncLiveClient._keep_alive ENTER") + """ + Sends keepalive messages to the WebSocket connection. + """ + self._logger.debug("AsyncLiveClient._keep_alive ENTER") counter = 0 while True: @@ -342,211 +415,247 @@ async def _keep_alive(self) -> None: await asyncio.sleep(ONE_SECOND) if self._exit_event.is_set(): - self.logger.notice("_keep_alive exiting gracefully") - self.logger.debug("AsyncLiveClient._keep_alive LEAVE") + self._logger.notice("_keep_alive exiting gracefully") + self._logger.debug("AsyncLiveClient._keep_alive LEAVE") return if self._socket is None: - self.logger.notice("socket is None, exiting keep_alive") - self.logger.debug("AsyncLiveClient._keep_alive LEAVE") + self._logger.notice("socket is None, exiting keep_alive") + self._logger.debug("AsyncLiveClient._keep_alive LEAVE") return # deepgram keepalive if counter % DEEPGRAM_INTERVAL == 0: - self.logger.verbose("Sending KeepAlive...") + self._logger.verbose("Sending KeepAlive...") await self.send(json.dumps({"type": "KeepAlive"})) except websockets.exceptions.ConnectionClosedOK as e: - self.logger.notice(f"_keep_alive({e.code}) exiting gracefully") - self.logger.debug("AsyncLiveClient._keep_alive LEAVE") + self._logger.notice(f"_keep_alive({e.code}) exiting gracefully") + self._logger.debug("AsyncLiveClient._keep_alive LEAVE") return - except websockets.exceptions.WebSocketException as e: + except websockets.exceptions.ConnectionClosed as e: if e.code == 1000: - self.logger.notice(f"_keep_alive({e.code}) exiting gracefully") - self.logger.debug("AsyncLiveClient._keep_alive LEAVE") + self._logger.notice(f"_keep_alive({e.code}) exiting gracefully") + self._logger.debug("AsyncLiveClient._keep_alive LEAVE") return - self.logger.error( + self._logger.error( + "ConnectionClosed in AsyncLiveClient._keep_alive with code %s: %s", + e.code, + e.reason, + ) + cc_error: ErrorResponse = ErrorResponse( + "ConnectionClosed in AsyncLiveClient._keep_alive", + f"{e}", + "ConnectionClosed", + ) + await self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), + error=cc_error, + **dict(cast(Dict[Any, Any], self._kwargs)), + ) + + # signal exit and close + await self._signal_exit() + + self._logger.debug("AsyncLiveClient._keep_alive LEAVE") + + if self._config.options.get("termination_exception") == "true": + raise + return + + except websockets.exceptions.WebSocketException as e: + self._logger.error( "WebSocketException in AsyncLiveClient._keep_alive: %s", e ) - error: ErrorResponse = { - "type": "Exception", - "description": "WebSocketException in AsyncLiveClient._keep_alive", - "message": f"{e}", - "variant": "", - } - self.logger.error( - f"WebSocket connection closed in AsyncLiveClient._keep_alive with code {e.code}: {e.reason}" + ws_error: ErrorResponse = ErrorResponse( + "WebSocketException in AsyncLiveClient._keep_alive", + f"{e}", + "Exception", + ) + await self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), + error=ws_error, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - await self._emit(LiveTranscriptionEvents.Error, error) # signal exit and close await self._signal_exit() - self.logger.debug("AsyncLiveClient._keep_alive LEAVE") + self._logger.debug("AsyncLiveClient._keep_alive LEAVE") - if self.config.options.get("termination_exception") == "true": + if self._config.options.get("termination_exception") == "true": raise return - except Exception as e: - self.logger.error("Exception in AsyncLiveClient._keep_alive: %s", e) - error: ErrorResponse = { - "type": "Exception", - "description": "Exception in _keep_alive", - "message": f"{e}", - "variant": "", - } - self.logger.error( + except Exception as e: # pylint: disable=broad-except + self._logger.error("Exception in AsyncLiveClient._keep_alive: %s", e) + e_error: ErrorResponse = ErrorResponse( + "Exception in AsyncLiveClient._keep_alive", + f"{e}", + "Exception", + ) + self._logger.error( "Exception in AsyncLiveClient._keep_alive: %s", str(e) ) - await self._emit(LiveTranscriptionEvents.Error, error) + await self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), + error=e_error, + **dict(cast(Dict[Any, Any], self._kwargs)), + ) # signal exit and close await self._signal_exit() - self.logger.debug("AsyncLiveClient._keep_alive LEAVE") + self._logger.debug("AsyncLiveClient._keep_alive LEAVE") - if self.config.options.get("termination_exception") == "true": + if self._config.options.get("termination_exception") == "true": raise return - # sends data over the WebSocket connection async def send(self, data: Union[str, bytes]) -> bool: """ Sends data over the WebSocket connection. """ - self.logger.spam("AsyncLiveClient.send ENTER") + self._logger.spam("AsyncLiveClient.send ENTER") if self._exit_event.is_set(): - self.logger.notice("send exiting gracefully") - self.logger.debug("AsyncLiveClient.send LEAVE") + self._logger.notice("send exiting gracefully") + self._logger.debug("AsyncLiveClient.send LEAVE") return False if self._socket is not None: try: await self._socket.send(data) except websockets.exceptions.ConnectionClosedOK as e: - self.logger.notice(f"send() exiting gracefully: {e.code}") - self.logger.debug("AsyncLiveClient.send LEAVE") - if self.config.options.get("termination_exception_send") == "true": + self._logger.notice(f"send() exiting gracefully: {e.code}") + self._logger.debug("AsyncLiveClient.send LEAVE") + if self._config.options.get("termination_exception_send") == "true": raise return True - except websockets.exceptions.WebSocketException as e: + except websockets.exceptions.ConnectionClosed as e: if e.code == 1000: - self.logger.notice(f"send({e.code}) exiting gracefully") - self.logger.debug("AsyncLiveClient.send LEAVE") - if self.config.options.get("termination_exception_send") == "true": + self._logger.notice(f"send({e.code}) exiting gracefully") + self._logger.debug("AsyncLiveClient.send LEAVE") + if self._config.options.get("termination_exception_send") == "true": raise return True - self.logger.error("send() failed - WebSocketException: %s", str(e)) - self.logger.spam("AsyncLiveClient.send LEAVE") - if self.config.options.get("termination_exception_send") == "true": + self._logger.error("send() failed - ConnectionClosed: %s", str(e)) + self._logger.spam("AsyncLiveClient.send LEAVE") + if self._config.options.get("termination_exception_send") == "true": + raise + return False + except websockets.exceptions.WebSocketException as e: + self._logger.error("send() failed - WebSocketException: %s", str(e)) + self._logger.spam("AsyncLiveClient.send LEAVE") + if self._config.options.get("termination_exception_send") == "true": raise return False - except Exception as e: - self.logger.error("send() failed - Exception: %s", str(e)) - self.logger.spam("AsyncLiveClient.send LEAVE") - if self.config.options.get("termination_exception_send") == "true": + except Exception as e: # pylint: disable=broad-except + self._logger.error("send() failed - Exception: %s", str(e)) + self._logger.spam("AsyncLiveClient.send LEAVE") + if self._config.options.get("termination_exception_send") == "true": raise return False - self.logger.spam(f"send() succeeded") - self.logger.spam("AsyncLiveClient.send LEAVE") + self._logger.spam("send() succeeded") + self._logger.spam("AsyncLiveClient.send LEAVE") return True - self.logger.spam("send() failed. socket is None") - self.logger.spam("AsyncLiveClient.send LEAVE") + self._logger.spam("send() failed. socket is None") + self._logger.spam("AsyncLiveClient.send LEAVE") return False + # pylint: enable=too-many-return-statements + async def finalize(self) -> bool: """ Finalizes the Transcript connection by flushing it """ - self.logger.spam("AsyncLiveClient.finalize ENTER") + self._logger.spam("AsyncLiveClient.finalize ENTER") if self._exit_event.is_set(): - self.logger.notice("finalize exiting gracefully") - self.logger.debug("AsyncLiveClient.finalize LEAVE") + self._logger.notice("finalize exiting gracefully") + self._logger.debug("AsyncLiveClient.finalize LEAVE") return False if self._socket is not None: - self.logger.notice("sending Finalize...") + self._logger.notice("sending Finalize...") ret = await self.send(json.dumps({"type": "Finalize"})) if not ret: - self.logger.error("finalize failed") - self.logger.spam("AsyncLiveClient.finalize LEAVE") + self._logger.error("finalize failed") + self._logger.spam("AsyncLiveClient.finalize LEAVE") return False - self.logger.notice("finalize succeeded") - self.logger.spam("AsyncLiveClient.finalize LEAVE") + self._logger.notice("finalize succeeded") + self._logger.spam("AsyncLiveClient.finalize LEAVE") return True - # closes the WebSocket connection gracefully async def finish(self) -> bool: """ Closes the WebSocket connection gracefully. """ - self.logger.debug("AsyncLiveClient.finish ENTER") + self._logger.debug("AsyncLiveClient.finish ENTER") # signal exit await self._signal_exit() # stop the threads - self.logger.verbose("cancelling tasks...") + self._logger.verbose("cancelling tasks...") try: # Before cancelling, check if the tasks were created tasks = [] - if self._keep_alive_thread is not None: - self._keep_alive_thread.cancel() - tasks.append(self._keep_alive_thread) + if self._config.options.get("keepalive") == "true": + if self._keep_alive_thread is not None: + self._keep_alive_thread.cancel() + tasks.append(self._keep_alive_thread) if self._listen_thread is not None: self._listen_thread.cancel() tasks.append(self._listen_thread) # Use asyncio.gather to wait for tasks to be cancelled await asyncio.gather(*filter(None, tasks), return_exceptions=True) - self.logger.notice("threads joined") + self._logger.notice("threads joined") - self._socket = None - - self.logger.notice("finish succeeded") - self.logger.spam("AsyncLiveClient.finish LEAVE") + self._logger.notice("finish succeeded") + self._logger.spam("AsyncLiveClient.finish LEAVE") return True except asyncio.CancelledError as e: - self.logger.error("tasks cancelled error: %s", e) - self.logger.debug("AsyncLiveClient.finish LEAVE") + self._logger.error("tasks cancelled error: %s", e) + self._logger.debug("AsyncLiveClient.finish LEAVE") return False - # signals the WebSocket connection to exit async def _signal_exit(self) -> None: # send close event - self.logger.verbose("closing socket...") + self._logger.verbose("closing socket...") if self._socket is not None: - self.logger.verbose("send CloseStream...") + self._logger.verbose("send CloseStream...") try: # if the socket connection is closed, the following line might throw an error - await self._socket.send(json.dumps({"type": "CloseStream"})) + await self.send(json.dumps({"type": "CloseStream"})) except websockets.exceptions.ConnectionClosedOK as e: - self.logger.notice(f"_signal_exit - connection closed: {e.code}") + self._logger.notice("_signal_exit - ConnectionClosedOK: %s", e.code) + except websockets.exceptions.ConnectionClosed as e: + self._logger.notice("_signal_exit - ConnectionClosed: %s", e.code) except websockets.exceptions.WebSocketException as e: - self.logger.error(f"_signal_exit - WebSocketException: {str(e)}") - except Exception as e: - self.logger.error(f"_signal_exit - Exception: {str(e)}") + self._logger.error("_signal_exit - WebSocketException: %s", str(e)) + except Exception as e: # pylint: disable=broad-except + self._logger.error("_signal_exit - Exception: %s", str(e)) # push close event try: await self._emit( - LiveTranscriptionEvents.Close, - CloseResponse(type=LiveTranscriptionEvents.Close.value), + LiveTranscriptionEvents(LiveTranscriptionEvents.Close), + close=CloseResponse(type=LiveTranscriptionEvents.Close), + **dict(cast(Dict[Any, Any], self._kwargs)), ) - except Exception as e: - self.logger.error(f"_signal_exit - Exception: {str(e)}") + except Exception as e: # pylint: disable=broad-except + self._logger.error("_emit - Exception: %s", e) # wait for task to send await asyncio.sleep(0.5) @@ -555,11 +664,12 @@ async def _signal_exit(self) -> None: self._exit_event.set() # closes the WebSocket connection gracefully - self.logger.verbose("clean up socket...") + self._logger.verbose("clean up socket...") if self._socket is not None: - self.logger.verbose("socket.wait_closed...") + self._logger.verbose("socket.wait_closed...") try: await self._socket.close() - self._socket = None except websockets.exceptions.WebSocketException as e: - self.logger.error("socket.wait_closed failed: %s", e) + self._logger.error("socket.wait_closed failed: %s", e) + + self._socket = None # type: ignore diff --git a/deepgram/clients/live/v1/client.py b/deepgram/clients/live/v1/client.py index 14b7bc9d..8e554582 100644 --- a/deepgram/clients/live/v1/client.py +++ b/deepgram/clients/live/v1/client.py @@ -2,17 +2,19 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT import json -from websockets.sync.client import connect -import websockets import threading import time -import logging, verboselogs -from typing import Dict, Union, Optional +import logging +from typing import Dict, Union, Optional, cast, Any + +from websockets.sync.client import connect, ClientConnection +import websockets +from deepgram.utils import verboselogs from ....options import DeepgramClientOptions from ..enums import LiveTranscriptionEvents from ..helpers import convert_to_websocket_url, append_query_params -from ..errors import DeepgramError, DeepgramWebsocketError +from ..errors import DeepgramError from .response import ( OpenResponse, @@ -31,7 +33,7 @@ PING_INTERVAL = 20 -class LiveClient: +class LiveClient: # pylint: disable=too-many-instance-attributes """ Client for interacting with Deepgram's live transcription services over WebSockets. @@ -41,23 +43,41 @@ class LiveClient: config (DeepgramClientOptions): all the options for the client. """ + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + _endpoint: str + _websocket_url: str + + _socket: ClientConnection + _exit_event: threading.Event + _lock_send: threading.Lock + _event_handlers: Dict[LiveTranscriptionEvents, list] + _listen_thread: threading.Thread + _keep_alive_thread: threading.Thread + + _kwargs: Optional[Dict] = None + _addons: Optional[Dict] = None + _options: Optional[Dict] = None + _headers: Optional[Dict] = None + def __init__(self, config: DeepgramClientOptions): if config is None: raise DeepgramError("Config are required") - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) - self.config = config - self.endpoint = "v1/listen" - self._socket = None + self._config = config + self._endpoint = "v1/listen" self._exit_event = threading.Event() self._lock_send = threading.Lock() - self._event_handlers = {event: [] for event in LiveTranscriptionEvents} - self.websocket_url = convert_to_websocket_url(self.config.url, self.endpoint) + self._event_handlers = { + event: [] for event in LiveTranscriptionEvents.__members__.values() + } + self._websocket_url = convert_to_websocket_url(self._config.url, self._endpoint) - # starts the WebSocket connection for live transcription + # pylint: disable=too-many-statements,too-many-branches def start( self, options: Optional[Union[LiveOptions, Dict]] = None, @@ -69,26 +89,20 @@ def start( """ Starts the WebSocket connection for live transcription. """ - self.logger.debug("LiveClient.start ENTER") - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) - self.logger.info("members: %s", members) - self.logger.info("kwargs: %s", kwargs) + self._logger.debug("LiveClient.start ENTER") + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) + self._logger.info("members: %s", members) + self._logger.info("kwargs: %s", kwargs) if isinstance(options, LiveOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("LiveClient.start LEAVE") + self._logger.error("options.check failed") + self._logger.debug("LiveClient.start LEAVE") raise DeepgramError("Fatal transcription options error") - if self._socket is not None: - self.logger.error("socket is already initialized") - self.logger.debug("LiveClient.start LEAVE") - raise DeepgramWebsocketError("Websocket already started") - - self.options = options - self.addons = addons - self.headers = headers + self._addons = addons + self._headers = headers # add "members" as members of the class if members is not None: @@ -96,29 +110,33 @@ def start( # set kwargs as members of the class if kwargs is not None: - self.kwargs = kwargs + self._kwargs = kwargs else: - self.kwargs = dict() + self._kwargs = {} if isinstance(options, LiveOptions): - self.logger.info("LiveOptions switching class -> dict") - self.options = self.options.to_dict() - - combined_options = self.options - if addons is not None: - self.logger.info("merging addons to options") - combined_options.update(self.addons) - self.logger.info("new options: %s", combined_options) - self.logger.debug("combined_options: %s", combined_options) - - combined_headers = self.config.headers - if headers is not None: - self.logger.info("merging headers to options") - combined_headers.update(self.headers) - self.logger.info("new headers: %s", combined_headers) - self.logger.debug("combined_headers: %s", combined_headers) - - url_with_params = append_query_params(self.websocket_url, combined_options) + self._logger.info("LiveOptions switching class -> dict") + self._options = options.to_dict() + elif options is not None: + self._options = options + else: + self._options = {} + + combined_options: Dict = self._options + if self._addons is not None: + self._logger.info("merging addons to options") + combined_options.update(self._addons) + self._logger.info("new options: %s", combined_options) + self._logger.debug("combined_options: %s", combined_options) + + combined_headers = self._config.headers + if self._headers is not None: + self._logger.info("merging headers to options") + combined_headers.update(self._headers) + self._logger.info("new headers: %s", combined_headers) + self._logger.debug("combined_headers: %s", combined_headers) + + url_with_params = append_query_params(self._websocket_url, combined_options) try: self._socket = connect(url_with_params, additional_headers=combined_headers) self._exit_event.clear() @@ -128,212 +146,260 @@ def start( self._listen_thread.start() # keepalive thread - if self.config.options.get("keepalive") == "true": - self.logger.notice("keepalive is enabled") + if self._config.options.get("keepalive") == "true": + self._logger.notice("keepalive is enabled") self._keep_alive_thread = threading.Thread(target=self._keep_alive) self._keep_alive_thread.start() else: - self.logger.notice("keepalive is disabled") - self._keep_alive_thread = None + self._logger.notice("keepalive is disabled") # push open event self._emit( - LiveTranscriptionEvents.Open, - OpenResponse(type=LiveTranscriptionEvents.Open.value), + LiveTranscriptionEvents(LiveTranscriptionEvents.Open), + OpenResponse(type=LiveTranscriptionEvents.Open), ) - self.logger.notice("start succeeded") - self.logger.debug("LiveClient.start LEAVE") + self._logger.notice("start succeeded") + self._logger.debug("LiveClient.start LEAVE") return True except websockets.ConnectionClosed as e: - self.logger.error("exception: websockets.ConnectionClosed") - self.logger.debug("LiveClient.start LEAVE") - if self.config.options.get("termination_exception_connect") == "true": - raise + self._logger.error("ConnectionClosed in LiveClient.start: %s", e) + self._logger.debug("LiveClient.start LEAVE") + if self._config.options.get("termination_exception_connect") == "true": + raise e return False except websockets.exceptions.WebSocketException as e: - self.logger.error("WebSocketException in LiveClient.start: %s", e) - self.logger.debug("LiveClient.start LEAVE") - if self.config.options.get("termination_exception_connect") == "true": - raise + self._logger.error("WebSocketException in LiveClient.start: %s", e) + self._logger.debug("LiveClient.start LEAVE") + if self._config.options.get("termination_exception_connect") == "true": + raise e return False - except Exception as e: - self.logger.error("WebSocketException in LiveClient.start: %s", e) - self.logger.debug("LiveClient.start LEAVE") - if self.config.options.get("termination_exception_connect") == "true": - raise + except Exception as e: # pylint: disable=broad-except + self._logger.error("WebSocketException in LiveClient.start: %s", e) + self._logger.debug("LiveClient.start LEAVE") + if self._config.options.get("termination_exception_connect") == "true": + raise e return False - # registers event handlers for specific events - def on(self, event: LiveTranscriptionEvents, handler) -> None: + # pylint: enable=too-many-statements,too-many-branches + + def on( + self, event: LiveTranscriptionEvents, handler + ) -> None: # registers event handlers for specific events """ Registers event handlers for specific events. """ - self.logger.info("event fired: %s", event) - if event in LiveTranscriptionEvents and callable(handler): + self._logger.info("event fired: %s", event) + if event in LiveTranscriptionEvents.__members__.values() and callable(handler): self._event_handlers[event].append(handler) - # triggers the registered event handlers for a specific event def _emit(self, event: LiveTranscriptionEvents, *args, **kwargs) -> None: + """ + Emits events to the registered event handlers. + """ for handler in self._event_handlers[event]: handler(self, *args, **kwargs) - # main loop for handling incoming messages - def _listening(self) -> None: - self.logger.debug("LiveClient._listening ENTER") + # pylint: disable=too-many-return-statements,too-many-statements,too-many-locals + def _listening( + self, + ) -> None: + """ + Listens for messages from the WebSocket connection. + """ + self._logger.debug("LiveClient._listening ENTER") while True: try: if self._exit_event.is_set(): - self.logger.notice("_listening exiting gracefully") - self.logger.debug("LiveClient._listening LEAVE") + self._logger.notice("_listening exiting gracefully") + self._logger.debug("LiveClient._listening LEAVE") return if self._socket is None: - self.logger.warning("socket is empty") - self.logger.debug("LiveClient._listening LEAVE") + self._logger.warning("socket is empty") + self._logger.debug("LiveClient._listening LEAVE") return - message = self._socket.recv() + message = str(self._socket.recv()) if message is None: - self.logger.info("message is empty") + self._logger.info("message is empty") continue data = json.loads(message) response_type = data.get("type") - self.logger.debug("response_type: %s, data: %s", response_type, data) + self._logger.debug("response_type: %s, data: %s", response_type, data) match response_type: - case LiveTranscriptionEvents.Open.value: - result = OpenResponse.from_json(message) - self.logger.verbose("OpenResponse: %s", result) + case LiveTranscriptionEvents.Open: + open_result: OpenResponse = OpenResponse.from_json(message) + self._logger.verbose("OpenResponse: %s", open_result) self._emit( - LiveTranscriptionEvents.Open, - open=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Open), + open=open_result, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - case LiveTranscriptionEvents.Transcript.value: - result = LiveResultResponse.from_json(message) - self.logger.verbose("LiveResultResponse: %s", result) + case LiveTranscriptionEvents.Transcript: + msg_result: LiveResultResponse = LiveResultResponse.from_json( + message + ) + self._logger.verbose("LiveResultResponse: %s", msg_result) self._emit( - LiveTranscriptionEvents.Transcript, - result=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Transcript), + result=msg_result, + **dict(cast(Dict[Any, Any], self._kwargs)), + ) + case LiveTranscriptionEvents.Metadata: + meta_result: MetadataResponse = MetadataResponse.from_json( + message ) - case LiveTranscriptionEvents.Metadata.value: - result = MetadataResponse.from_json(message) - self.logger.verbose("MetadataResponse: %s", result) + self._logger.verbose("MetadataResponse: %s", meta_result) self._emit( - LiveTranscriptionEvents.Metadata, - metadata=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Metadata), + metadata=meta_result, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - case LiveTranscriptionEvents.SpeechStarted.value: - result = SpeechStartedResponse.from_json(message) - self.logger.verbose("SpeechStartedResponse: %s", result) + case LiveTranscriptionEvents.SpeechStarted: + ss_result: SpeechStartedResponse = ( + SpeechStartedResponse.from_json(message) + ) + self._logger.verbose("SpeechStartedResponse: %s", ss_result) self._emit( - LiveTranscriptionEvents.SpeechStarted, - speech_started=result, - **dict(self.kwargs), + LiveTranscriptionEvents( + LiveTranscriptionEvents.SpeechStarted + ), + speech_started=ss_result, + **dict(cast(Dict[Any, Any], self._kwargs)), + ) + case LiveTranscriptionEvents.UtteranceEnd: + ue_result: UtteranceEndResponse = ( + UtteranceEndResponse.from_json(message) ) - case LiveTranscriptionEvents.UtteranceEnd.value: - result = UtteranceEndResponse.from_json(message) - self.logger.verbose("UtteranceEndResponse: %s", result) + self._logger.verbose("UtteranceEndResponse: %s", ue_result) self._emit( - LiveTranscriptionEvents.UtteranceEnd, - utterance_end=result, - **dict(self.kwargs), + LiveTranscriptionEvents( + LiveTranscriptionEvents.UtteranceEnd + ), + utterance_end=ue_result, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - case LiveTranscriptionEvents.Close.value: - result = CloseResponse.from_json(message) - self.logger.verbose("CloseResponse: %s", result) + case LiveTranscriptionEvents.Close: + close_result: CloseResponse = CloseResponse.from_json(message) + self._logger.verbose("CloseResponse: %s", close_result) self._emit( - LiveTranscriptionEvents.Close, - close=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Close), + close=close_result, + **dict(cast(Dict[Any, Any], self._kwargs)), ) - case LiveTranscriptionEvents.Error.value: - result = ErrorResponse.from_json(message) - self.logger.verbose("ErrorResponse: %s", result) + case LiveTranscriptionEvents.Error: + err_error: ErrorResponse = ErrorResponse.from_json(message) + self._logger.verbose("ErrorResponse: %s", err_error) self._emit( - LiveTranscriptionEvents.Error, - error=result, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), + error=err_error, + **dict(cast(Dict[Any, Any], self._kwargs)), ) case _: - self.logger.warning( + self._logger.warning( "Unknown Message: response_type: %s, data: %s", response_type, data, ) - unhandled = UnhandledResponse( - type=LiveTranscriptionEvents.Unhandled.value, + unhandled_error: UnhandledResponse = UnhandledResponse( + type=LiveTranscriptionEvents( + LiveTranscriptionEvents.Unhandled + ), raw=message, ) self._emit( - LiveTranscriptionEvents.Unhandled, - unhandled=unhandled, - **dict(self.kwargs), + LiveTranscriptionEvents(LiveTranscriptionEvents.Unhandled), + unhandled=unhandled_error, + **dict(cast(Dict[Any, Any], self._kwargs)), ) except websockets.exceptions.ConnectionClosedOK as e: - self.logger.notice(f"_listening({e.code}) exiting gracefully") - self.logger.debug("LiveClient._listening LEAVE") + self._logger.notice(f"_listening({e.code}) exiting gracefully") + self._logger.debug("LiveClient._listening LEAVE") return - except websockets.exceptions.WebSocketException as e: + except websockets.exceptions.ConnectionClosed as e: if e.code == 1000: - self.logger.notice(f"_listening({e.code}) exiting gracefully") - self.logger.debug("LiveClient._listening LEAVE") + self._logger.notice(f"_listening({e.code}) exiting gracefully") + self._logger.debug("LiveClient._listening LEAVE") return - self.logger.error( - "WebSocketException in AsyncLiveClient._listening: %s", e + self._logger.error( + "ConnectionClosed in LiveClient._listening with code %s: %s", + e.code, + e.reason, + ) + cc_error: ErrorResponse = ErrorResponse( + "ConnectionClosed in LiveClient._listening", + f"{e}", + "ConnectionClosed", + ) + self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), cc_error + ) + + # signal exit and close + self._signal_exit() + + self._logger.debug("LiveClient._listening LEAVE") + + if self._config.options.get("termination_exception") == "true": + raise + return + + except websockets.exceptions.WebSocketException as e: + self._logger.error( + "WebSocketException in LiveClient._listening with: %s", e ) - error: ErrorResponse = { - "type": "Exception", - "description": "WebSocketException in LiveClient._listening", - "message": f"{e}", - "variant": "", - } - self.logger.notice( - f"WebSocket connection in LiveClient._listening closed with code {e.code}: {e.reason}" + ws_error: ErrorResponse = ErrorResponse( + "WebSocketException in LiveClient._listening", + f"{e}", + "WebSocketException", + ) + self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), ws_error ) - self._emit(LiveTranscriptionEvents.Error, error) # signal exit and close self._signal_exit() - self.logger.debug("LiveClient._listening LEAVE") + self._logger.debug("LiveClient._listening LEAVE") - if self.config.options.get("termination_exception") == "true": + if self._config.options.get("termination_exception") == "true": raise return - except Exception as e: - self.logger.error("Exception in AsyncLiveClient._listening: %s", e) - error: ErrorResponse = { - "type": "Exception", - "description": "Exception in LiveClient._listening", - "message": f"{e}", - "variant": "", - } - self.logger.error("Exception in LiveClient._listening: %s", str(e)) - self._emit(LiveTranscriptionEvents.Error, error) + except Exception as e: # pylint: disable=broad-except + self._logger.error("Exception in LiveClient._listening: %s", e) + e_error: ErrorResponse = ErrorResponse( + "Exception in LiveClient._listening", + f"{e}", + "Exception", + ) + self._logger.error("Exception in LiveClient._listening: %s", str(e)) + self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), e_error + ) # signal exit and close self._signal_exit() - self.logger.debug("LiveClient._listening LEAVE") + self._logger.debug("LiveClient._listening LEAVE") - if self.config.options.get("termination_exception") == "true": + if self._config.options.get("termination_exception") == "true": raise return - # keep the connection alive by sending keepalive messages + # pylint: enable=too-many-return-statements,too-many-statements + + ## pylint: disable=too-many-return-statements def _keep_alive(self) -> None: - self.logger.debug("LiveClient._keep_alive ENTER") + self._logger.debug("LiveClient._keep_alive ENTER") counter = 0 while True: @@ -342,84 +408,109 @@ def _keep_alive(self) -> None: self._exit_event.wait(timeout=ONE_SECOND) if self._exit_event.is_set(): - self.logger.notice("_keep_alive exiting gracefully") - self.logger.debug("LiveClient._keep_alive LEAVE") + self._logger.notice("_keep_alive exiting gracefully") + self._logger.debug("LiveClient._keep_alive LEAVE") return if self._socket is None: - self.logger.notice("socket is None, exiting keep_alive") - self.logger.debug("LiveClient._keep_alive LEAVE") + self._logger.notice("socket is None, exiting keep_alive") + self._logger.debug("LiveClient._keep_alive LEAVE") return # deepgram keepalive if counter % DEEPGRAM_INTERVAL == 0: - self.logger.verbose("Sending KeepAlive...") + self._logger.verbose("Sending KeepAlive...") self.send(json.dumps({"type": "KeepAlive"})) except websockets.exceptions.ConnectionClosedOK as e: - self.logger.notice(f"_keep_alive({e.code}) exiting gracefully") - self.logger.debug("LiveClient._keep_alive LEAVE") + self._logger.notice(f"_keep_alive({e.code}) exiting gracefully") + self._logger.debug("LiveClient._keep_alive LEAVE") return - except websockets.exceptions.WebSocketException as e: + except websockets.exceptions.ConnectionClosed as e: if e.code == 1000: - self.logger.notice(f"_keep_alive({e.code}) exiting gracefully") - self.logger.debug("LiveClient._keep_alive LEAVE") + self._logger.notice(f"_keep_alive({e.code}) exiting gracefully") + self._logger.debug("LiveClient._keep_alive LEAVE") return - self.logger.error( - "WebSocketException in AsyncLiveClient._keep_alive: %s", e + self._logger.error( + "ConnectionClosed in LiveClient._keep_alive with code %s: %s", + e.code, + e.reason, ) - error: ErrorResponse = { - "type": "Exception", - "description": "WebSocketException in LiveClient._keep_alive", - "message": f"{e}", - "variant": "", - } - self.logger.error( - f"WebSocket connection closed in LiveClient._keep_alive with code {e.code}: {e.reason}" + cc_error: ErrorResponse = ErrorResponse( + "ConnectionClosed in LiveClient._keep_alive", + f"{e}", + "ConnectionClosed", + ) + self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), cc_error ) - self._emit(LiveTranscriptionEvents.Error, error) # signal exit and close self._signal_exit() - self.logger.debug("LiveClient._keep_alive LEAVE") + self._logger.debug("LiveClient._keep_alive LEAVE") - if self.config.options.get("termination_exception") == "true": + if self._config.options.get("termination_exception") == "true": raise return - except Exception as e: - self.logger.error("Exception in AsyncLiveClient._keep_alive: %s", e) - error: ErrorResponse = { - "type": "Exception", - "description": "Exception in LiveClient._keep_alive", - "message": f"{e}", - "variant": "", - } - self.logger.error("Exception in LiveClient._keep_alive: %s", str(e)) - self._emit(LiveTranscriptionEvents.Error, error) + except websockets.exceptions.WebSocketException as e: + self._logger.error( + "WebSocketException in LiveClient._keep_alive with: %s", e + ) + ws_error: ErrorResponse = ErrorResponse( + "WebSocketException in LiveClient._keep_alive", + f"{e}", + "WebSocketException", + ) + self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), ws_error + ) # signal exit and close self._signal_exit() - self.logger.debug("LiveClient._keep_alive LEAVE") + self._logger.debug("LiveClient._keep_alive LEAVE") - if self.config.options.get("termination_exception") == "true": + if self._config.options.get("termination_exception") == "true": raise return - # sends data over the WebSocket connection + except Exception as e: # pylint: disable=broad-except + self._logger.error("Exception in LiveClient._keep_alive: %s", e) + e_error: ErrorResponse = ErrorResponse( + "Exception in LiveClient._keep_alive", + f"{e}", + "Exception", + ) + self._logger.error("Exception in LiveClient._keep_alive: %s", str(e)) + self._emit( + LiveTranscriptionEvents(LiveTranscriptionEvents.Error), e_error + ) + + # signal exit and close + self._signal_exit() + + self._logger.debug("LiveClient._keep_alive LEAVE") + + if self._config.options.get("termination_exception") == "true": + raise + return + + # pylint: enable=too-many-return-statements + + # pylint: disable=too-many-return-statements def send(self, data: Union[str, bytes]) -> bool: """ Sends data over the WebSocket connection. """ - self.logger.spam("LiveClient.send ENTER") + self._logger.spam("LiveClient.send ENTER") if self._exit_event.is_set(): - self.logger.notice("send exiting gracefully") - self.logger.debug("AsyncLiveClient.send LEAVE") + self._logger.notice("send exiting gracefully") + self._logger.debug("LiveClient.send LEAVE") return False if self._socket is not None: @@ -427,63 +518,71 @@ def send(self, data: Union[str, bytes]) -> bool: try: self._socket.send(data) except websockets.exceptions.ConnectionClosedOK as e: - self.logger.notice(f"send() exiting gracefully: {e.code}") - self.logger.debug("LiveClient._keep_alive LEAVE") - if self.config.options.get("termination_exception_send") == "true": + self._logger.notice(f"send() exiting gracefully: {e.code}") + self._logger.debug("LiveClient._keep_alive LEAVE") + if self._config.options.get("termination_exception_send") == "true": raise return True - except websockets.exceptions.WebSocketException as e: + except websockets.exceptions.ConnectionClosed as e: if e.code == 1000: - self.logger.notice(f"send({e.code}) exiting gracefully") - self.logger.debug("LiveClient.send LEAVE") + self._logger.notice(f"send({e.code}) exiting gracefully") + self._logger.debug("LiveClient.send LEAVE") if ( - self.config.options.get("termination_exception_send") + self._config.options.get("termination_exception_send") == "true" ): raise return True - self.logger.error("send() failed - WebSocketException: %s", str(e)) - self.logger.spam("LiveClient.send LEAVE") - if self.config.options.get("termination_exception_send") == "true": + self._logger.error("send() failed - ConnectionClosed: %s", str(e)) + self._logger.spam("LiveClient.send LEAVE") + if self._config.options.get("termination_exception_send") == "true": + raise + return False + except websockets.exceptions.WebSocketException as e: + self._logger.error("send() failed - WebSocketException: %s", str(e)) + self._logger.spam("LiveClient.send LEAVE") + if self._config.options.get("termination_exception_send") == "true": raise return False - except Exception as e: - self.logger.error("send() failed - Exception: %s", str(e)) - self.logger.spam("LiveClient.send LEAVE") - if self.config.options.get("termination_exception_send") == "true": + except Exception as e: # pylint: disable=broad-except + self._logger.error("send() failed - Exception: %s", str(e)) + self._logger.spam("LiveClient.send LEAVE") + if self._config.options.get("termination_exception_send") == "true": raise return False - self.logger.spam(f"send() succeeded") - self.logger.spam("LiveClient.send LEAVE") + self._logger.spam("send() succeeded") + self._logger.spam("LiveClient.send LEAVE") return True - self.logger.spam("send() failed. socket is None") - self.logger.spam("LiveClient.send LEAVE") + self._logger.spam("send() failed. socket is None") + self._logger.spam("LiveClient.send LEAVE") return False + # pylint: enable=too-many-return-statements + def finalize(self) -> bool: """ Finalizes the Transcript connection by flushing it """ - self.logger.spam("LiveClient.finalize ENTER") + self._logger.spam("LiveClient.finalize ENTER") if self._exit_event.is_set(): - self.logger.notice("finalize exiting gracefully") - self.logger.debug("LiveClient.finalize LEAVE") + self._logger.notice("finalize exiting gracefully") + self._logger.debug("LiveClient.finalize LEAVE") return False if self._socket is not None: - self.logger.notice("sending Finalize...") + self._logger.notice("sending Finalize...") ret = self.send(json.dumps({"type": "Finalize"})) if not ret: - self.logger.error("finalize failed") - self.logger.spam("LiveClient.finalize LEAVE") + self._logger.error("finalize failed") + self._logger.spam("LiveClient.finalize LEAVE") return False - self.logger.notice("finalize succeeded") - self.logger.spam("LiveClient.finalize LEAVE") + self._logger.notice("finalize succeeded") + self._logger.spam("LiveClient.finalize LEAVE") return True @@ -492,51 +591,54 @@ def finish(self) -> bool: """ Closes the WebSocket connection gracefully. """ - self.logger.spam("LiveClient.finish ENTER") + self._logger.spam("LiveClient.finish ENTER") # signal exit self._signal_exit() # stop the threads - self.logger.verbose("cancelling tasks...") - if self._keep_alive_thread is not None: - self._keep_alive_thread.join() - self.logger.notice("processing thread joined") + self._logger.verbose("cancelling tasks...") + if self._config.options.get("keepalive") == "true": + if self._keep_alive_thread is not None: + self._keep_alive_thread.join() + self._keep_alive_thread = None # type: ignore + self._logger.notice("processing thread joined") if self._listen_thread is not None: self._listen_thread.join() - self.logger.notice("listening thread joined") + self._listen_thread = None # type: ignore + self._logger.notice("listening thread joined") - self._socket = None - - self.logger.notice("finish succeeded") - self.logger.spam("LiveClient.finish LEAVE") + self._logger.notice("finish succeeded") + self._logger.spam("LiveClient.finish LEAVE") return True # signals the WebSocket connection to exit def _signal_exit(self) -> None: # closes the WebSocket connection gracefully - self.logger.notice("closing socket...") + self._logger.notice("closing socket...") if self._socket is not None: - self.logger.notice("sending CloseStream...") + self._logger.notice("sending CloseStream...") try: # if the socket connection is closed, the following line might throw an error self._socket.send(json.dumps({"type": "CloseStream"})) except websockets.exceptions.ConnectionClosedOK as e: - self.logger.notice(f"_signal_exit - connection closed: {e.code}") + self._logger.notice("_signal_exit - ConnectionClosedOK: %s", e.code) + except websockets.exceptions.ConnectionClosed as e: + self._logger.error("_signal_exit - ConnectionClosed: %s", e.code) except websockets.exceptions.WebSocketException as e: - self.logger.error(f"_signal_exit - WebSocketException: {str(e)}") - except Exception as e: - self.logger.error(f"_signal_exit - Exception: {str(e)}") + self._logger.error("_signal_exit - WebSocketException: %s", str(e)) + except Exception as e: # pylint: disable=broad-except + self._logger.error("_signal_exit - Exception: %s", str(e)) # push close event try: self._emit( - LiveTranscriptionEvents.Close, - CloseResponse(type=LiveTranscriptionEvents.Close.value), + LiveTranscriptionEvents(LiveTranscriptionEvents.Close), + CloseResponse(type=LiveTranscriptionEvents.Close), ) - except Exception as e: - self.logger.error(f"_signal_exit - Exception: {str(e)}") + except Exception as e: # pylint: disable=broad-except + self._logger.error("_signal_exit - Exception: %s", e) # wait for task to send time.sleep(0.5) @@ -545,11 +647,12 @@ def _signal_exit(self) -> None: self._exit_event.set() # closes the WebSocket connection gracefully - self.logger.verbose("clean up socket...") + self._logger.verbose("clean up socket...") if self._socket is not None: - self.logger.verbose("socket.wait_closed...") + self._logger.verbose("socket.wait_closed...") try: self._socket.close() - self._socket = None except websockets.exceptions.WebSocketException as e: - self.logger.error("socket.wait_closed failed: %s", e) + self._logger.error("socket.wait_closed failed: %s", e) + + self._socket = None # type: ignore diff --git a/deepgram/clients/live/v1/options.py b/deepgram/clients/live/v1/options.py index 3d22b9f9..abebc924 100644 --- a/deepgram/clients/live/v1/options.py +++ b/deepgram/clients/live/v1/options.py @@ -2,15 +2,17 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -from dataclasses import dataclass, field -from dataclasses_json import dataclass_json, config from typing import List, Optional, Union -import logging, verboselogs +import logging + +from dataclasses import dataclass, field +from dataclasses_json import config as dataclass_config, DataClassJsonMixin + +from deepgram.utils import verboselogs -@dataclass_json @dataclass -class LiveOptions: +class LiveOptions(DataClassJsonMixin): # pylint: disable=too-many-instance-attributes """ Live Transcription Options for the Deepgram Platform. @@ -19,91 +21,91 @@ class LiveOptions: """ alternatives: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) callback: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) callback_method: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) channels: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) diarize: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) diarize_version: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) encoding: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) endpointing: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) extra: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) filler_words: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) interim_results: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) keywords: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) language: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) model: Optional[str] = field( - default="nova-2", metadata=config(exclude=lambda f: f is None) + default="nova-2", metadata=dataclass_config(exclude=lambda f: f is None) ) multichannel: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) no_delay: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) numerals: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) punctuate: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) profanity_filter: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) redact: Optional[Union[List[str], bool, str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) replace: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sample_rate: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) search: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) smart_format: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) tag: Optional[List[str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) tier: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) utterance_end_ms: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) vad_events: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) version: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -117,11 +119,13 @@ def __str__(self) -> str: return self.to_json(indent=4) def check(self): - verboselogs.install() - logger = logging.getLogger(__name__) + """ + Check the options for any deprecated or soon-to-be-deprecated options. + """ + logger = verboselogs.VerboseLogger(__name__) logger.addHandler(logging.StreamHandler()) prev = logger.level - logger.setLevel(logging.ERROR) + logger.setLevel(verboselogs.ERROR) if self.tier: logger.error( diff --git a/deepgram/clients/live/v1/response.py b/deepgram/clients/live/v1/response.py index 68e6d4c3..52638b07 100644 --- a/deepgram/clients/live/v1/response.py +++ b/deepgram/clients/live/v1/response.py @@ -2,17 +2,16 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -from dataclasses import dataclass, field -from dataclasses_json import config, dataclass_json -from datetime import datetime from typing import List, Optional, Dict +from dataclasses import dataclass, field +from dataclasses_json import config as dataclass_config, DataClassJsonMixin + # Result Message -@dataclass_json @dataclass -class OpenResponse: +class OpenResponse(DataClassJsonMixin): """ Open Message from the Deepgram Platform """ @@ -30,18 +29,21 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Word: +class Word(DataClassJsonMixin): + """ + Word object + """ + word: str = "" start: float = 0 end: float = 0 confidence: float = 0 punctuated_word: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) speaker: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -55,12 +57,15 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Alternative: +class Alternative(DataClassJsonMixin): + """ + Alternative object + """ + transcript: str = "" confidence: float = 0 - words: List[Word] = None + words: List[Word] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -75,10 +80,13 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Channel: - alternatives: List[Alternative] = None +class Channel(DataClassJsonMixin): + """ + Channel object + """ + + alternatives: List[Alternative] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -96,9 +104,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class ModelInfo: +class ModelInfo(DataClassJsonMixin): + """ + ModelInfo object + """ + name: str = "" version: str = "" arch: str = "" @@ -114,14 +125,17 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Metadata: +class Metadata(DataClassJsonMixin): + """ + Metadata object + """ + + model_info: ModelInfo request_id: str = "" - model_info: ModelInfo = None model_uuid: str = "" extra: Optional[Dict[str, str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -141,24 +155,25 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class LiveResultResponse: +class LiveResultResponse( + DataClassJsonMixin +): # pylint: disable=too-many-instance-attributes """ Result Message from the Deepgram Platform """ + channel: Channel + metadata: Metadata type: str = "" - channel_index: List[int] = None + channel_index: List[int] = field(default_factory=list) duration: float = 0 start: float = 0 is_final: bool = False from_finalize: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) speech_final: bool = False - channel: Channel = None - metadata: Metadata = None def __getitem__(self, key): _dict = self.to_dict() @@ -182,27 +197,10 @@ def __str__(self) -> str: # Metadata Message -@dataclass_json -@dataclass -class ModelInfo: - name: str = "" - version: str = "" - arch: str = "" - - def __getitem__(self, key): - _dict = self.to_dict() - return _dict[key] - - def __setitem__(self, key, val): - self.__dict__[key] = val - - def __str__(self) -> str: - return self.to_json(indent=4) - - -@dataclass_json @dataclass -class MetadataResponse: +class MetadataResponse( + DataClassJsonMixin +): # pylint: disable=too-many-instance-attributes """ Metadata Message from the Deepgram Platform """ @@ -215,13 +213,13 @@ class MetadataResponse: duration: float = 0 channels: int = 0 models: Optional[List[str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) model_info: Optional[Dict[str, ModelInfo]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) extra: Optional[Dict] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -247,15 +245,14 @@ def __str__(self) -> str: # Speech Started Message -@dataclass_json @dataclass -class SpeechStartedResponse: +class SpeechStartedResponse(DataClassJsonMixin): """ SpeechStartedResponse Message from the Deepgram Platform """ type: str = "" - channel: List[int] = None + channel: List[int] = field(default_factory=list) timestamp: float = 0 def __getitem__(self, key): @@ -272,15 +269,14 @@ def __str__(self) -> str: # Utterance End Message -@dataclass_json @dataclass -class UtteranceEndResponse: +class UtteranceEndResponse(DataClassJsonMixin): """ UtteranceEnd Message from the Deepgram Platform """ type: str = "" - channel: List[int] = None + channel: List[int] = field(default_factory=list) last_word_end: float = 0 def __getitem__(self, key): @@ -297,9 +293,8 @@ def __str__(self) -> str: # Close Message -@dataclass_json @dataclass -class CloseResponse: +class CloseResponse(DataClassJsonMixin): """ Close Message from the Deepgram Platform """ @@ -320,9 +315,8 @@ def __str__(self) -> str: # Error Message -@dataclass_json @dataclass -class ErrorResponse: +class ErrorResponse(DataClassJsonMixin): """ Error Message from the Deepgram Platform """ @@ -346,9 +340,8 @@ def __str__(self) -> str: # Unhandled Message -@dataclass_json @dataclass -class UnhandledResponse: +class UnhandledResponse(DataClassJsonMixin): """ Unhandled Message from the Deepgram Platform """ diff --git a/deepgram/clients/manage/__init__.py b/deepgram/clients/manage/__init__.py index 636bf37e..723f7dc5 100644 --- a/deepgram/clients/manage/__init__.py +++ b/deepgram/clients/manage/__init__.py @@ -30,4 +30,4 @@ Balance, BalancesResponse, ) -from ...options import DeepgramClientOptions +from ...options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/manage/client.py b/deepgram/clients/manage/client.py index c40ea91a..aff57e0f 100644 --- a/deepgram/clients/manage/client.py +++ b/deepgram/clients/manage/client.py @@ -36,204 +36,38 @@ ) -""" -The client.py points to the current supported version in the SDK. -Older versions are supported in the SDK for backwards compatibility. -""" +# The client.py points to the current supported version in the SDK. +# Older versions are supported in the SDK for backwards compatibility. # input -class ProjectOptions(ProjectOptionsLatest): - """ - pass through for ProjectOptions based on API version - """ - - pass - - -class KeyOptions(KeyOptionsLatest): - """ - pass through for KeyOptions based on API version - """ - - pass - - -class ScopeOptions(ScopeOptionsLatest): - """ - pass through for ScopeOptions based on API version - """ - - pass - - -class InviteOptions(InviteOptionsLatest): - """ - pass through for InviteOptions based on API version - """ - - pass - - -class UsageRequestOptions(UsageRequestOptionsLatest): - """ - pass through for UsageRequestOptions based on API version - """ - - pass - - -class UsageSummaryOptions(UsageSummaryOptionsLatest): - """ - pass through for UsageSummaryOptions based on API version - """ - - pass - - -class UsageFieldsOptions(UsageFieldsOptionsLatest): - """ - pass through for UsageFieldsOptions based on API version - """ - - pass +ProjectOptions = ProjectOptionsLatest +KeyOptions = KeyOptionsLatest +ScopeOptions = ScopeOptionsLatest +InviteOptions = InviteOptionsLatest +UsageRequestOptions = UsageRequestOptionsLatest +UsageSummaryOptions = UsageSummaryOptionsLatest +UsageFieldsOptions = UsageFieldsOptionsLatest # responses -class Message(MessageLatest): - """ - pass through for Message based on API version - """ - - pass - - -class Project(ProjectLatest): - """ - pass through for Project based on API version - """ - - pass - - -class ProjectsResponse(ProjectsResponseLatest): - """ - pass through for ProjectsResponse based on API version - """ - - pass - - -class MembersResponse(MembersResponseLatest): - """ - pass through for MembersResponse based on API version - """ - - pass - - -class Key(KeyLatest): - """ - pass through for Key based on API version - """ - - pass - - -class KeyResponse(KeyResponseLatest): - """ - pass through for KeyResponse based on API version - """ - - pass - - -class KeysResponse(KeysResponseLatest): - """ - pass through for KeysResponse based on API version - """ - - pass - - -class ScopesResponse(ScopesResponseLatest): - """ - pass through for ScopesResponse based on API version - """ - - pass - - -class InvitesResponse(InvitesResponseLatest): - """ - pass through for InvitesResponse based on API version - """ - - pass - - -class UsageRequest(UsageRequestLatest): - """ - pass through for UsageRequest based on API version - """ - - pass - - -class UsageRequestsResponse(UsageRequestsResponseLatest): - """ - pass through for UsageRequestsResponse based on API version - """ - - pass - - -class UsageSummaryResponse(UsageSummaryResponseLatest): - """ - pass through for UsageSummaryResponse based on API version - """ - - pass - - -class UsageFieldsResponse(UsageFieldsResponseLatest): - """ - pass through for UsageFieldsResponse based on API version - """ - - pass - - -class Balance(BalanceLatest): - """ - pass through for Balance based on API version - """ - - pass - - -class BalancesResponse(BalancesResponseLatest): - """ - pass through for BalancesResponse based on API version - """ - - pass +Message = MessageLatest +Project = ProjectLatest +ProjectsResponse = ProjectsResponseLatest +MembersResponse = MembersResponseLatest +Key = KeyLatest +KeyResponse = KeyResponseLatest +KeysResponse = KeysResponseLatest +ScopesResponse = ScopesResponseLatest +InvitesResponse = InvitesResponseLatest +UsageRequest = UsageRequestLatest +UsageRequestsResponse = UsageRequestsResponseLatest +UsageSummaryResponse = UsageSummaryResponseLatest +UsageFieldsResponse = UsageFieldsResponseLatest +Balance = BalanceLatest +BalancesResponse = BalancesResponseLatest # clients -class ManageClient(ManageClientLatest): - """ - Please see ManageClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) - - -class AsyncManageClient(AsyncManageClientLatest): - """ - Please see AsyncManageClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) +ManageClient = ManageClientLatest +AsyncManageClient = AsyncManageClientLatest diff --git a/deepgram/clients/manage/v1/__init__.py b/deepgram/clients/manage/v1/__init__.py index 372c87c8..057e68e8 100644 --- a/deepgram/clients/manage/v1/__init__.py +++ b/deepgram/clients/manage/v1/__init__.py @@ -30,4 +30,4 @@ Balance, BalancesResponse, ) -from ....options import DeepgramClientOptions +from ....options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/manage/v1/async_client.py b/deepgram/clients/manage/v1/async_client.py index aacb2548..8e7b31b0 100644 --- a/deepgram/clients/manage/v1/async_client.py +++ b/deepgram/clients/manage/v1/async_client.py @@ -2,11 +2,12 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import httpx -import logging, verboselogs +import logging from typing import Dict, Union, Optional -import json +import httpx + +from deepgram.utils import verboselogs from ....options import DeepgramClientOptions from ...abstract_async_client import AbstractAsyncRestClient @@ -38,7 +39,9 @@ ) -class AsyncManageClient(AbstractAsyncRestClient): +class AsyncManageClient( + AbstractAsyncRestClient +): # pylint: disable=too-many-public-methods """ A client for managing Deepgram projects and associated resources via the Deepgram API. @@ -53,12 +56,16 @@ class AsyncManageClient(AbstractAsyncRestClient): config (DeepgramClientOptions): all the options for the client. """ + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + _endpoint: str + def __init__(self, config: DeepgramClientOptions): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - self.endpoint = "v1/projects" + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + self._endpoint = "v1/projects" super().__init__(config) # projects @@ -89,19 +96,19 @@ async def get_projects( Reference: https://developers.deepgram.com/reference/get-projects """ - self.logger.debug("ManageClient.get_projects ENTER") - url = f"{self.config.url}/{self.endpoint}" - self.logger.info("url: %s", url) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_projects ENTER") + url = f"{self._config.url}/{self._endpoint}" + self._logger.info("url: %s", url) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = ProjectsResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_projects succeeded") - self.logger.debug("ManageClient.get_projects LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_projects succeeded") + self._logger.debug("ManageClient.get_projects LEAVE") return res async def get_project( @@ -118,20 +125,20 @@ async def get_project( Reference: https://developers.deepgram.com/reference/get-project """ - self.logger.debug("ManageClient.get_project ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_project ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Project.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_project succeeded") - self.logger.debug("ManageClient.get_project LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_project succeeded") + self._logger.debug("ManageClient.get_project LEAVE") return res async def update_project_option( @@ -149,24 +156,24 @@ async def update_project_option( Reference: https://developers.deepgram.com/reference/update-project """ - self.logger.debug("ManageClient.update_project_option ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.update_project_option ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, ProjectOptions): - self.logger.info("ProjectOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("ProjectOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.patch( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("update_project_option succeeded") - self.logger.debug("ManageClient.update_project_option LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("update_project_option succeeded") + self._logger.debug("ManageClient.update_project_option LEAVE") return res async def update_project( @@ -184,24 +191,24 @@ async def update_project( Reference: https://developers.deepgram.com/reference/update-project """ - self.logger.debug("ManageClient.update_project ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}" - options: ProjectOptions = { + self._logger.debug("ManageClient.update_project ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}" + options = { "name": name, } - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.patch( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("update_project succeeded") - self.logger.debug("ManageClient.update_project LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("update_project succeeded") + self._logger.debug("ManageClient.update_project LEAVE") return res async def delete_project( @@ -218,18 +225,18 @@ async def delete_project( Reference: https://developers.deepgram.com/reference/delete-project """ - self.logger.debug("ManageClient.delete_project ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}" - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.delete_project ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}" + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("delete_project succeeded") - self.logger.debug("ManageClient.delete_project LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("delete_project succeeded") + self._logger.debug("ManageClient.delete_project LEAVE") return res # keys @@ -262,20 +269,20 @@ async def get_keys( Reference: https://developers.deepgram.com/reference/list-keys """ - self.logger.debug("ManageClient.get_keys ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/keys" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_keys ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/keys" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = KeysResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_keys succeeded") - self.logger.debug("ManageClient.get_keys LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_keys succeeded") + self._logger.debug("ManageClient.get_keys LEAVE") return res async def get_key( @@ -293,21 +300,21 @@ async def get_key( Reference: https://developers.deepgram.com/reference/get-key """ - self.logger.debug("ManageClient.get_key ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/keys/{key_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("key_id: %s", key_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_key ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/keys/{key_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("key_id: %s", key_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = KeyResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_key succeeded") - self.logger.debug("ManageClient.get_key LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_key succeeded") + self._logger.debug("ManageClient.get_key LEAVE") return res async def create_key( @@ -325,24 +332,24 @@ async def create_key( Reference: https://developers.deepgram.com/reference/create-key """ - self.logger.debug("ManageClient.create_key ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/keys" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.create_key ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/keys" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, KeyOptions): - self.logger.info("KeyOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("KeyOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Key.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("create_key succeeded") - self.logger.debug("ManageClient.create_key LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("create_key succeeded") + self._logger.debug("ManageClient.create_key LEAVE") return res async def delete_key( @@ -360,21 +367,21 @@ async def delete_key( Reference: https://developers.deepgram.com/reference/delete-key """ - self.logger.debug("ManageClient.delete_key ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/keys/{key_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("key_id: %s", key_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.delete_key ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/keys/{key_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("key_id: %s", key_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("delete_key succeeded") - self.logger.debug("ManageClient.delete_key LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("delete_key succeeded") + self._logger.debug("ManageClient.delete_key LEAVE") return res # members @@ -407,20 +414,20 @@ async def get_members( Reference: https://developers.deepgram.com/reference/get-members """ - self.logger.debug("ManageClient.get_members ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/members" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_members ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/members" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = MembersResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_members succeeded") - self.logger.debug("ManageClient.get_members LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_members succeeded") + self._logger.debug("ManageClient.get_members LEAVE") return res async def remove_member( @@ -438,21 +445,21 @@ async def remove_member( Reference: https://developers.deepgram.com/reference/remove-member """ - self.logger.debug("ManageClient.remove_member ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/members/{member_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("member_id: %s", member_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.remove_member ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/members/{member_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("member_id: %s", member_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("remove_member succeeded") - self.logger.debug("ManageClient.remove_member LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("remove_member succeeded") + self._logger.debug("ManageClient.remove_member LEAVE") return res # scopes @@ -471,23 +478,21 @@ async def get_member_scopes( Reference: https://developers.deepgram.com/reference/get-member-scopes """ - self.logger.debug("ManageClient.get_member_scopes ENTER") - url = ( - f"{self.config.url}/{self.endpoint}/{project_id}/members/{member_id}/scopes" - ) - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("member_id: %s", member_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_member_scopes ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/members/{member_id}/scopes" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("member_id: %s", member_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = ScopesResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_member_scopes succeeded") - self.logger.debug("ManageClient.get_member_scopes LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_member_scopes succeeded") + self._logger.debug("ManageClient.get_member_scopes LEAVE") return res async def update_member_scope( @@ -506,26 +511,24 @@ async def update_member_scope( Reference: https://developers.deepgram.com/reference/update-scope """ - self.logger.debug("ManageClient.update_member_scope ENTER") - url = ( - f"{self.config.url}/{self.endpoint}/{project_id}/members/{member_id}/scopes" - ) - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.update_member_scope ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/members/{member_id}/scopes" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, ScopeOptions): - self.logger.info("ScopeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("ScopeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.put( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("update_member_scope succeeded") - self.logger.debug("ManageClient.update_member_scope LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("update_member_scope succeeded") + self._logger.debug("ManageClient.update_member_scope LEAVE") return res # invites @@ -558,20 +561,20 @@ async def get_invites( Reference: https://developers.deepgram.com/reference/list-invites """ - self.logger.debug("ManageClient.get_invites ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/invites" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_invites ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/invites" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = InvitesResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_invites succeeded") - self.logger.debug("ManageClient.get_invites LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_invites succeeded") + self._logger.debug("ManageClient.get_invites LEAVE") return res async def send_invite_options( @@ -589,24 +592,24 @@ async def send_invite_options( Reference: https://developers.deepgram.com/reference/send-invite """ - self.logger.debug("ManageClient.send_invite_options ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/invites" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.send_invite_options ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/invites" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, InviteOptions): - self.logger.info("InviteOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("InviteOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("send_invite_options succeeded") - self.logger.debug("ManageClient.send_invite_options LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("send_invite_options succeeded") + self._logger.debug("ManageClient.send_invite_options LEAVE") return res async def send_invite( @@ -625,25 +628,25 @@ async def send_invite( Reference: https://developers.deepgram.com/reference/send-invite """ - self.logger.debug("ManageClient.send_invite ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/invites" - options: InviteOptions = { + self._logger.debug("ManageClient.send_invite ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/invites" + options = { "email": email, "scope": scope, } - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("send_invite succeeded") - self.logger.debug("ManageClient.send_invite LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("send_invite succeeded") + self._logger.debug("ManageClient.send_invite LEAVE") return res async def delete_invite( @@ -661,21 +664,21 @@ async def delete_invite( Reference: https://developers.deepgram.com/reference/delete-invite """ - self.logger.debug("ManageClient.delete_invite ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/invites/{email}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("email: %s", email) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.delete_invite ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/invites/{email}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("email: %s", email) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("delete_invite succeeded") - self.logger.debug("ManageClient.delete_invite LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("delete_invite succeeded") + self._logger.debug("ManageClient.delete_invite LEAVE") return res async def leave_project( @@ -692,20 +695,20 @@ async def leave_project( Reference: https://developers.deepgram.com/reference/leave-project """ - self.logger.debug("ManageClient.leave_project ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/leave" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.leave_project ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/leave" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("leave_project succeeded") - self.logger.debug("ManageClient.leave_project LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("leave_project succeeded") + self._logger.debug("ManageClient.leave_project LEAVE") return res # usage @@ -724,16 +727,16 @@ async def get_usage_requests( Reference: https://developers.deepgram.com/reference/get-all-requests """ - self.logger.debug("ManageClient.get_usage_requests ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/requests" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.get_usage_requests ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/requests" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, UsageRequestOptions): - self.logger.info("UsageRequestOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("UsageRequestOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, options=options, @@ -742,11 +745,11 @@ async def get_usage_requests( headers=headers, **kwargs, ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = UsageRequestsResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_usage_requests succeeded") - self.logger.debug("ManageClient.get_usage_requests LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_usage_requests succeeded") + self._logger.debug("ManageClient.get_usage_requests LEAVE") return res async def get_usage_request( @@ -764,21 +767,21 @@ async def get_usage_request( Reference: https://developers.deepgram.com/reference/get-request """ - self.logger.debug("ManageClient.get_usage_request ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/requests/{request_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("request_id: %s", request_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_usage_request ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/requests/{request_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("request_id: %s", request_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = UsageRequest.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_usage_request succeeded") - self.logger.debug("ManageClient.get_usage_request LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_usage_request succeeded") + self._logger.debug("ManageClient.get_usage_request LEAVE") return res async def get_usage_summary( @@ -796,16 +799,16 @@ async def get_usage_summary( Reference: https://developers.deepgram.com/reference/summarize-usage """ - self.logger.debug("ManageClient.get_usage_summary ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/usage" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.get_usage_summary ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/usage" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, UsageSummaryOptions): - self.logger.info("UsageSummaryOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("UsageSummaryOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, options=options, @@ -814,11 +817,11 @@ async def get_usage_summary( headers=headers, **kwargs, ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = UsageSummaryResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_usage_summary succeeded") - self.logger.debug("ManageClient.get_usage_summary LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_usage_summary succeeded") + self._logger.debug("ManageClient.get_usage_summary LEAVE") return res async def get_usage_fields( @@ -836,16 +839,16 @@ async def get_usage_fields( Reference: https://developers.deepgram.com/reference/get-fields """ - self.logger.debug("ManageClient.get_usage_fields ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/usage/fields" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.get_usage_fields ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/usage/fields" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, UsageFieldsOptions): - self.logger.info("UsageFieldsOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("UsageFieldsOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, options=options, @@ -854,11 +857,11 @@ async def get_usage_fields( headers=headers, **kwargs, ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = UsageFieldsResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_usage_fields succeeded") - self.logger.debug("ManageClient.get_usage_fields LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_usage_fields succeeded") + self._logger.debug("ManageClient.get_usage_fields LEAVE") return res # balances @@ -891,20 +894,20 @@ async def get_balances( Reference: https://developers.deepgram.com/reference/get-all-balances """ - self.logger.debug("ManageClient.get_balances ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/balances" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_balances ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/balances" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = BalancesResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_balances succeeded") - self.logger.debug("ManageClient.get_balances LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_balances succeeded") + self._logger.debug("ManageClient.get_balances LEAVE") return res async def get_balance( @@ -922,19 +925,19 @@ async def get_balance( Reference: https://developers.deepgram.com/reference/get-balance """ - self.logger.debug("ManageClient.get_balance ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/balances/{balance_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("balance_id: %s", balance_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_balance ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/balances/{balance_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("balance_id: %s", balance_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("result: %s", result) + self._logger.info("result: %s", result) res = Balance.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_balance succeeded") - self.logger.debug("ManageClient.get_balance LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_balance succeeded") + self._logger.debug("ManageClient.get_balance LEAVE") return res diff --git a/deepgram/clients/manage/v1/client.py b/deepgram/clients/manage/v1/client.py index 46016b24..b3ae4bc1 100644 --- a/deepgram/clients/manage/v1/client.py +++ b/deepgram/clients/manage/v1/client.py @@ -2,11 +2,12 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import httpx -import logging, verboselogs -import json +import logging from typing import Dict, Union, Optional +import httpx + +from deepgram.utils import verboselogs from ....options import DeepgramClientOptions from ...abstract_sync_client import AbstractSyncRestClient @@ -38,7 +39,7 @@ ) -class ManageClient(AbstractSyncRestClient): +class ManageClient(AbstractSyncRestClient): # pylint: disable=too-many-public-methods """ A client for managing Deepgram projects and associated resources via the Deepgram API. @@ -53,13 +54,17 @@ class ManageClient(AbstractSyncRestClient): config (DeepgramClientOptions): all the options for the client. """ + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + _endpoint: str + def __init__(self, config: DeepgramClientOptions): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) - self.config = config - self.endpoint = "v1/projects" + self._config = config + self._endpoint = "v1/projects" super().__init__(config) # projects @@ -90,19 +95,19 @@ def get_projects( Reference: https://developers.deepgram.com/reference/get-projects """ - self.logger.debug("ManageClient.get_projects ENTER") - url = f"{self.config.url}/{self.endpoint}" - self.logger.info("url: %s", url) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_projects ENTER") + url = f"{self._config.url}/{self._endpoint}" + self._logger.info("url: %s", url) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = ProjectsResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_projects succeeded") - self.logger.debug("ManageClient.get_projects LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_projects succeeded") + self._logger.debug("ManageClient.get_projects LEAVE") return res def get_project( @@ -119,20 +124,20 @@ def get_project( Reference: https://developers.deepgram.com/reference/get-project """ - self.logger.debug("ManageClient.get_project ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_project ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Project.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_project succeeded") - self.logger.debug("ManageClient.get_project LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_project succeeded") + self._logger.debug("ManageClient.get_project LEAVE") return res def update_project_option( @@ -150,24 +155,24 @@ def update_project_option( Reference: https://developers.deepgram.com/reference/update-project """ - self.logger.debug("ManageClient.update_project_option ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.update_project_option ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, ProjectOptions): - self.logger.info("ProjectOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("ProjectOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.patch( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("update_project_option succeeded") - self.logger.debug("ManageClient.update_project_option LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("update_project_option succeeded") + self._logger.debug("ManageClient.update_project_option LEAVE") return res def update_project( @@ -185,24 +190,24 @@ def update_project( Reference: https://developers.deepgram.com/reference/update-project """ - self.logger.debug("ManageClient.update_project ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}" - options: ProjectOptions = { + self._logger.debug("ManageClient.update_project ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}" + options = { "name": name, } - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.patch( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("update_project succeeded") - self.logger.debug("ManageClient.update_project LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("update_project succeeded") + self._logger.debug("ManageClient.update_project LEAVE") return res def delete_project( @@ -219,18 +224,18 @@ def delete_project( Reference: https://developers.deepgram.com/reference/delete-project """ - self.logger.debug("ManageClient.delete_project ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}" - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.delete_project ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}" + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("delete_project succeeded") - self.logger.debug("ManageClient.delete_project LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("delete_project succeeded") + self._logger.debug("ManageClient.delete_project LEAVE") return res # keys @@ -263,20 +268,20 @@ def get_keys( Reference: https://developers.deepgram.com/reference/list-keys """ - self.logger.debug("ManageClient.get_keys ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/keys" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_keys ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/keys" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = KeysResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_keys succeeded") - self.logger.debug("ManageClient.get_keys LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_keys succeeded") + self._logger.debug("ManageClient.get_keys LEAVE") return res def get_key( @@ -294,21 +299,21 @@ def get_key( Reference: https://developers.deepgram.com/reference/get-key """ - self.logger.debug("ManageClient.get_key ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/keys/{key_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("key_id: %s", key_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_key ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/keys/{key_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("key_id: %s", key_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = KeyResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_key succeeded") - self.logger.debug("ManageClient.get_key LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_key succeeded") + self._logger.debug("ManageClient.get_key LEAVE") return res def create_key( @@ -326,24 +331,24 @@ def create_key( Reference: https://developers.deepgram.com/reference/create-key """ - self.logger.debug("ManageClient.create_key ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/keys" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.create_key ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/keys" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, KeyOptions): - self.logger.info("KeyOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("KeyOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Key.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("create_key succeeded") - self.logger.debug("ManageClient.create_key LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("create_key succeeded") + self._logger.debug("ManageClient.create_key LEAVE") return res def delete_key( @@ -361,21 +366,21 @@ def delete_key( Reference: https://developers.deepgram.com/reference/delete-key """ - self.logger.debug("ManageClient.delete_key ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/keys/{key_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("key_id: %s", key_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.delete_key ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/keys/{key_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("key_id: %s", key_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("delete_key succeeded") - self.logger.debug("ManageClient.delete_key LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("delete_key succeeded") + self._logger.debug("ManageClient.delete_key LEAVE") return res # members @@ -408,20 +413,20 @@ def get_members( Reference: https://developers.deepgram.com/reference/get-members """ - self.logger.debug("ManageClient.get_members ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/members" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_members ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/members" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = MembersResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_members succeeded") - self.logger.debug("ManageClient.get_members LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_members succeeded") + self._logger.debug("ManageClient.get_members LEAVE") return res def remove_member( @@ -439,21 +444,21 @@ def remove_member( Reference: https://developers.deepgram.com/reference/remove-member """ - self.logger.debug("ManageClient.remove_member ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/members/{member_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("member_id: %s", member_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.remove_member ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/members/{member_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("member_id: %s", member_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("remove_member succeeded") - self.logger.debug("ManageClient.remove_member LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("remove_member succeeded") + self._logger.debug("ManageClient.remove_member LEAVE") return res # scopes @@ -472,23 +477,21 @@ def get_member_scopes( Reference: https://developers.deepgram.com/reference/get-member-scopes """ - self.logger.debug("ManageClient.get_member_scopes ENTER") - url = ( - f"{self.config.url}/{self.endpoint}/{project_id}/members/{member_id}/scopes" - ) - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("member_id: %s", member_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_member_scopes ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/members/{member_id}/scopes" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("member_id: %s", member_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = ScopesResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_member_scopes succeeded") - self.logger.debug("ManageClient.get_member_scopes LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_member_scopes succeeded") + self._logger.debug("ManageClient.get_member_scopes LEAVE") return res def update_member_scope( @@ -507,26 +510,24 @@ def update_member_scope( Reference: https://developers.deepgram.com/reference/update-scope """ - self.logger.debug("ManageClient.update_member_scope ENTER") - url = ( - f"{self.config.url}/{self.endpoint}/{project_id}/members/{member_id}/scopes" - ) - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.update_member_scope ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/members/{member_id}/scopes" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, ScopeOptions): - self.logger.info("ScopeOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("ScopeOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.put( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("update_member_scope succeeded") - self.logger.debug("ManageClient.update_member_scope LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("update_member_scope succeeded") + self._logger.debug("ManageClient.update_member_scope LEAVE") return res # invites @@ -559,20 +560,20 @@ def get_invites( Reference: https://developers.deepgram.com/reference/list-invites """ - self.logger.debug("ManageClient.get_invites ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/invites" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_invites ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/invites" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = InvitesResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_invites succeeded") - self.logger.debug("ManageClient.get_invites LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_invites succeeded") + self._logger.debug("ManageClient.get_invites LEAVE") return res def send_invite_options( @@ -590,24 +591,24 @@ def send_invite_options( Reference: https://developers.deepgram.com/reference/send-invite """ - self.logger.debug("ManageClient.send_invite_options ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/invites" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.send_invite_options ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/invites" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, InviteOptions): - self.logger.info("InviteOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("InviteOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("send_invite_options succeeded") - self.logger.debug("ManageClient.send_invite_options LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("send_invite_options succeeded") + self._logger.debug("ManageClient.send_invite_options LEAVE") return res def send_invite( @@ -626,25 +627,25 @@ def send_invite( Reference: https://developers.deepgram.com/reference/send-invite """ - self.logger.debug("ManageClient.send_invite ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/invites" - options: InviteOptions = { + self._logger.debug("ManageClient.send_invite ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/invites" + options = { "email": email, "scope": scope, } - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, json=options, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("send_invite succeeded") - self.logger.debug("ManageClient.send_invite LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("send_invite succeeded") + self._logger.debug("ManageClient.send_invite LEAVE") return res def delete_invite( @@ -662,21 +663,21 @@ def delete_invite( Reference: https://developers.deepgram.com/reference/delete-invite """ - self.logger.debug("ManageClient.delete_invite ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/invites/{email}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("email: %s", email) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.delete_invite ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/invites/{email}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("email: %s", email) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("delete_invite succeeded") - self.logger.debug("ManageClient.delete_invite LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("delete_invite succeeded") + self._logger.debug("ManageClient.delete_invite LEAVE") return res def leave_project( @@ -693,20 +694,20 @@ def leave_project( Reference: https://developers.deepgram.com/reference/leave-project """ - self.logger.debug("ManageClient.leave_project ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/leave" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.leave_project ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/leave" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.delete( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Message.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("leave_project succeeded") - self.logger.debug("ManageClient.leave_project LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("leave_project succeeded") + self._logger.debug("ManageClient.leave_project LEAVE") return res # usage @@ -725,16 +726,16 @@ def get_usage_requests( Reference: https://developers.deepgram.com/reference/get-all-requests """ - self.logger.debug("ManageClient.get_usage_requests ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/requests" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.get_usage_requests ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/requests" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, UsageRequestOptions): - self.logger.info("UsageRequestOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("UsageRequestOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, options=options, @@ -743,11 +744,11 @@ def get_usage_requests( headers=headers, **kwargs, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = UsageRequestsResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_usage_requests succeeded") - self.logger.debug("ManageClient.get_usage_requests LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_usage_requests succeeded") + self._logger.debug("ManageClient.get_usage_requests LEAVE") return res def get_usage_request( @@ -765,21 +766,21 @@ def get_usage_request( Reference: https://developers.deepgram.com/reference/get-request """ - self.logger.debug("ManageClient.get_usage_request ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/requests/{request_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("request_id: %s", request_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_usage_request ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/requests/{request_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("request_id: %s", request_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = UsageRequest.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_usage_request succeeded") - self.logger.debug("ManageClient.get_usage_request LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_usage_request succeeded") + self._logger.debug("ManageClient.get_usage_request LEAVE") return res def get_usage_summary( @@ -797,16 +798,16 @@ def get_usage_summary( Reference: https://developers.deepgram.com/reference/summarize-usage """ - self.logger.debug("ManageClient.get_usage_summary ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/usage" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.get_usage_summary ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/usage" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, UsageSummaryOptions): - self.logger.info("UsageSummaryOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("UsageSummaryOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, options=options, @@ -815,11 +816,11 @@ def get_usage_summary( headers=headers, **kwargs, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = UsageSummaryResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_usage_summary succeeded") - self.logger.debug("ManageClient.get_usage_summary LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_usage_summary succeeded") + self._logger.debug("ManageClient.get_usage_summary LEAVE") return res def get_usage_fields( @@ -837,16 +838,16 @@ def get_usage_fields( Reference: https://developers.deepgram.com/reference/get-fields """ - self.logger.debug("ManageClient.get_usage_fields ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/usage/fields" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + self._logger.debug("ManageClient.get_usage_fields ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/usage/fields" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) if isinstance(options, UsageFieldsOptions): - self.logger.info("UsageFieldsOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("UsageFieldsOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, options=options, @@ -855,11 +856,11 @@ def get_usage_fields( headers=headers, **kwargs, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = UsageFieldsResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_usage_fields succeeded") - self.logger.debug("ManageClient.get_usage_fields LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_usage_fields succeeded") + self._logger.debug("ManageClient.get_usage_fields LEAVE") return res # balances @@ -892,20 +893,20 @@ def get_balances( Reference: https://developers.deepgram.com/reference/get-all-balances """ - self.logger.debug("ManageClient.get_balances ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/balances" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_balances ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/balances" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = BalancesResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_balances succeeded") - self.logger.debug("ManageClient.get_balances LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_balances succeeded") + self._logger.debug("ManageClient.get_balances LEAVE") return res def get_balance( @@ -923,19 +924,19 @@ def get_balance( Reference: https://developers.deepgram.com/reference/get-balance """ - self.logger.debug("ManageClient.get_balance ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/balances/{balance_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("balance_id: %s", balance_id) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.debug("ManageClient.get_balance ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/balances/{balance_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("balance_id: %s", balance_id) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.get( url, timeout=timeout, addons=addons, headers=headers, **kwargs ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = Balance.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("get_balance succeeded") - self.logger.debug("ManageClient.get_balance LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_balance succeeded") + self._logger.debug("ManageClient.get_balance LEAVE") return res diff --git a/deepgram/clients/manage/v1/options.py b/deepgram/clients/manage/v1/options.py index 89012f62..77f931d4 100644 --- a/deepgram/clients/manage/v1/options.py +++ b/deepgram/clients/manage/v1/options.py @@ -2,17 +2,20 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +from typing import List, Optional + from dataclasses import dataclass, field -from dataclasses_json import dataclass_json, config -from datetime import datetime -from typing import TypedDict, List, Optional +from dataclasses_json import config as dataclass_config, DataClassJsonMixin # Input -@dataclass_json @dataclass -class ProjectOptions: +class ProjectOptions(DataClassJsonMixin): + """ + Project Options + """ + name: str = "" def __getitem__(self, key): @@ -26,22 +29,22 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class KeyOptions: +class KeyOptions(DataClassJsonMixin): + """ + Key Options + """ + comment: Optional[str] = "" expiration_date: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default="", metadata=dataclass_config(exclude=lambda f: f == "") ) time_to_live_in_seconds: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) - ) - expiration_date: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=-1, metadata=dataclass_config(exclude=lambda f: f == -1) ) - scopes: List[str] = None + scopes: List[str] = field(default_factory=list) tags: Optional[List[str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -59,9 +62,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class ScopeOptions: +class ScopeOptions(DataClassJsonMixin): + """ + Scope Options + """ + scope: str = "" def __getitem__(self, key): @@ -75,9 +81,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class InviteOptions: +class InviteOptions(DataClassJsonMixin): + """ + Invite Options + """ + email: str = "" scope: str = "" @@ -92,20 +101,23 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class UsageRequestOptions: +class UsageRequestOptions(DataClassJsonMixin): + """ + Usage Request Options + """ + start: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) end: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) limit: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) status: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -119,71 +131,76 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class UsageSummaryOptions: +class UsageSummaryOptions( + DataClassJsonMixin +): # pylint: disable=too-many-instance-attributes + """ + Usage Summary Options + """ + start: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) end: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) accessor: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) tag: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) method: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) model: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) multichannel: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) interim_results: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) punctuate: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) ner: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) utterances: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) replace: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) profanity_filter: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) keywords: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) detect_topics: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) diarize: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) search: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) redact: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) alternatives: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) numerals: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) smart_format: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -197,9 +214,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class UsageFieldsOptions: +class UsageFieldsOptions(DataClassJsonMixin): + """ + Usage Fields Options + """ + start: Optional[str] = "" end: Optional[str] = "" diff --git a/deepgram/clients/manage/v1/response.py b/deepgram/clients/manage/v1/response.py index 5b5b50a5..e3e1bf03 100644 --- a/deepgram/clients/manage/v1/response.py +++ b/deepgram/clients/manage/v1/response.py @@ -2,18 +2,21 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +from typing import List, Optional + from dataclasses import dataclass, field -from dataclasses_json import dataclass_json, config -from datetime import datetime -from typing import TypedDict, List, Optional +from dataclasses_json import config as dataclass_config, DataClassJsonMixin # Result Message -@dataclass_json @dataclass -class Message: +class Message(DataClassJsonMixin): + """ + Message from the Deepgram Platform + """ + message: str = "" def __getitem__(self, key): @@ -30,9 +33,12 @@ def __str__(self) -> str: # Projects -@dataclass_json @dataclass -class Project: +class Project(DataClassJsonMixin): + """ + Project object + """ + project_id: str = "" name: str = "" @@ -47,10 +53,13 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class ProjectsResponse: - projects: List[Project] = None +class ProjectsResponse(DataClassJsonMixin): + """ + Projects Response object + """ + + projects: List[Project] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -70,9 +79,12 @@ def __str__(self) -> str: # Members -@dataclass_json @dataclass -class Member: +class Member(DataClassJsonMixin): + """ + Member object + """ + email: str = "" first_name: str = "" last_name: str = "" @@ -89,10 +101,13 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class MembersResponse: - members: List[Member] = None +class MembersResponse(DataClassJsonMixin): + """ + Members Response object + """ + + members: List[Member] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -112,16 +127,19 @@ def __str__(self) -> str: # Keys -@dataclass_json @dataclass -class Key: +class Key(DataClassJsonMixin): + """ + Key object + """ + api_key_id: str = "" key: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) comment: Optional[str] = "" created: str = "" - scopes: List[str] = None + scopes: List[str] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -136,11 +154,14 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class KeyResponse: - api_key: Key = None - member: Member = None +class KeyResponse(DataClassJsonMixin): + """ + Key Response object + """ + + api_key: Key + member: Member def __getitem__(self, key): _dict = self.to_dict() @@ -157,10 +178,13 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class KeysResponse: - api_keys: List[KeyResponse] = None +class KeysResponse(DataClassJsonMixin): + """ + Keys Response object + """ + + api_keys: List[KeyResponse] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -180,10 +204,13 @@ def __str__(self) -> str: # Scopes -@dataclass_json @dataclass -class ScopesResponse: - scopes: List[str] = None +class ScopesResponse(DataClassJsonMixin): + """ + Scopes Response object + """ + + scopes: List[str] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -201,9 +228,12 @@ def __str__(self) -> str: # Invites -@dataclass_json @dataclass -class Invite: +class Invite(DataClassJsonMixin): + """ + Invite object + """ + email: str = "" scope: str = "" @@ -218,10 +248,13 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class InvitesResponse: - invites: List[Invite] = None +class InvitesResponse(DataClassJsonMixin): + """ + Invites Response object + """ + + invites: List[Invite] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -241,37 +274,40 @@ def __str__(self) -> str: # Usage -@dataclass_json @dataclass -class Config: +class Config(DataClassJsonMixin): # pylint: disable=too-many-instance-attributes + """ + Config object + """ + language: str = "" model: str = "" punctuate: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) utterances: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) diarize: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) smart_format: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) interim_results: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) topics: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) intents: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) summarize: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -285,22 +321,25 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Details: +class Details(DataClassJsonMixin): # pylint: disable=too-many-instance-attributes + """ + Details object + """ + + config: Config usd: float = 0 duration: float = 0 total_audio: float = 0 channels: int = 0 streams: int = 0 method: str = "" - models: List[str] = None + tier: Optional[str] = "" + models: List[str] = field(default_factory=list) tags: Optional[List[str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) - features: List[str] = None - config: Config = None - tier: Optional[str] = "" + features: List[str] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -321,9 +360,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Callback: +class Callback(DataClassJsonMixin): + """ + Callback object + """ + attempts: int = 0 code: int = 0 completed: str = "" @@ -339,9 +381,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class TokenDetail: +class TokenDetail(DataClassJsonMixin): + """ + Token Detail object + """ + feature: str = "" input: int = 0 model: str = "" @@ -358,9 +403,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class SpeechSegment: +class SpeechSegment(DataClassJsonMixin): + """ + Speech Segment object + """ + characters: int = 0 model: str = "" tier: str = "" @@ -376,12 +424,17 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class TTSDetails: +class TTSDetails(DataClassJsonMixin): + """ + TTS Details object + """ + duration: float = 0 - speech_segments: List[SpeechSegment] = None + speech_segments: List[SpeechSegment] = field(default_factory=list) + # pylint: disable=fixme # TODO: audio_metadata: None + # pylint: enable=fixme def __getitem__(self, key): _dict = self.to_dict() @@ -399,17 +452,20 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Response: +class Response(DataClassJsonMixin): + """ + Response object + """ + + details: Details code: int = 0 completed: str = "" - details: Details = None tts_details: Optional[TTSDetails] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) token_details: List[TokenDetail] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default_factory=list, metadata=dataclass_config(exclude=lambda f: f is list) ) def __getitem__(self, key): @@ -432,20 +488,23 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class UsageRequest: +class UsageRequest(DataClassJsonMixin): # pylint: disable=too-many-instance-attributes + """ + Usage Request object + """ + + response: Response project_uuid: str = "" request_id: str = "" created: str = "" path: str = "" api_key_id: str = "" - response: Response = None callback: Optional[Callback] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) accessor: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -463,12 +522,15 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class UsageRequestsResponse: +class UsageRequestsResponse(DataClassJsonMixin): + """ + Usage Requests Response object + """ + page: int = 0 limit: int = 0 - requests: List[UsageRequest] = None + requests: List[UsageRequest] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -485,9 +547,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Tokens: +class Tokens(DataClassJsonMixin): + """ + Tokens object + """ + tokens_in: int = 0 out: int = 0 @@ -502,9 +567,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class TTS: +class TTS(DataClassJsonMixin): + """ + TTS object + """ + characters: int = 0 requests: int = 0 @@ -519,16 +587,19 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Results: +class Results(DataClassJsonMixin): + """ + Results object + """ + + tokens: Tokens + tts: TTS start: str = "" end: str = "" hours: int = 0 total_hours: int = 0 requests: int = 0 - tokens: Tokens = None - tts: TTS = None def __getitem__(self, key): _dict = self.to_dict() @@ -536,7 +607,7 @@ def __getitem__(self, key): _dict["tokens"] = Tokens.from_dict(_dict["tokens"]) if "tts" in _dict: _dict["tts"] = TTS.from_dict(_dict["tts"]) - return _dict[key] + return _dict[key] def __setitem__(self, key, val): self.__dict__[key] = val @@ -545,9 +616,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Resolution: +class Resolution(DataClassJsonMixin): + """ + Resolution object + """ + units: str = "" amount: int = 0 @@ -562,13 +636,16 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class UsageSummaryResponse: +class UsageSummaryResponse(DataClassJsonMixin): + """ + Usage Summary Response object + """ + + resolution: Resolution start: str = "" end: str = "" - resolution: Resolution = None - results: List[Results] = None + results: List[Results] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -587,9 +664,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class UsageModel: +class UsageModel(DataClassJsonMixin): + """ + Usage Model object + """ + name: str = "" language: str = "" version: str = "" @@ -606,17 +686,20 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class UsageFieldsResponse: +class UsageFieldsResponse(DataClassJsonMixin): + """ + Usage Fields Response object + """ + tags: Optional[List[str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) - models: List[UsageModel] = None - processing_methods: List[str] = None - features: List[str] = None + models: List[UsageModel] = field(default_factory=list) + processing_methods: List[str] = field(default_factory=list) + features: List[str] = field(default_factory=list) languages: Optional[List[str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -648,9 +731,12 @@ def __str__(self) -> str: # Billing -@dataclass_json @dataclass -class Balance: +class Balance(DataClassJsonMixin): + """ + Balance object + """ + balance_id: str = "" amount: str = "" units: str = "" @@ -667,10 +753,13 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class BalancesResponse: - balances: List[Balance] = None +class BalancesResponse(DataClassJsonMixin): + """ + Balances Response object + """ + + balances: List[Balance] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() diff --git a/deepgram/clients/onprem/__init__.py b/deepgram/clients/onprem/__init__.py index 01399768..dbe3194d 100644 --- a/deepgram/clients/onprem/__init__.py +++ b/deepgram/clients/onprem/__init__.py @@ -4,4 +4,4 @@ from .v1.client import OnPremClient from .v1.async_client import AsyncOnPremClient -from ...options import DeepgramClientOptions +from ...options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/onprem/client.py b/deepgram/clients/onprem/client.py index 3d92fbc5..06a931f8 100644 --- a/deepgram/clients/onprem/client.py +++ b/deepgram/clients/onprem/client.py @@ -5,25 +5,9 @@ from .v1.client import OnPremClient as OnPremClientLatest from .v1.async_client import AsyncOnPremClient as AsyncOnPremClientLatest -""" -The client.py points to the current supported version in the SDK. -Older versions are supported in the SDK for backwards compatibility. -""" +# The client.py points to the current supported version in the SDK. +# Older versions are supported in the SDK for backwards compatibility. -class OnPremClient(OnPremClientLatest): - """ - Please see OnPremClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) - - -class AsyncOnPremClient(AsyncOnPremClientLatest): - """ - Please see AsyncOnPremClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) +OnPremClient = OnPremClientLatest +AsyncOnPremClient = AsyncOnPremClientLatest diff --git a/deepgram/clients/onprem/v1/__init__.py b/deepgram/clients/onprem/v1/__init__.py index 882c36fc..ffe012d7 100644 --- a/deepgram/clients/onprem/v1/__init__.py +++ b/deepgram/clients/onprem/v1/__init__.py @@ -4,4 +4,4 @@ from .client import OnPremClient from .async_client import AsyncOnPremClient -from ....options import DeepgramClientOptions +from ....options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/onprem/v1/async_client.py b/deepgram/clients/onprem/v1/async_client.py index a6130f85..a177abc3 100644 --- a/deepgram/clients/onprem/v1/async_client.py +++ b/deepgram/clients/onprem/v1/async_client.py @@ -2,8 +2,13 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import logging, verboselogs +import logging +from typing import Optional +import httpx + +from deepgram.utils import verboselogs +from ....options import DeepgramClientOptions from ...abstract_async_client import AbstractAsyncRestClient @@ -17,61 +22,89 @@ class AsyncOnPremClient(AbstractAsyncRestClient): config (DeepgramClientOptions): all the options for the client. """ - def __init__(self, config): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - self.endpoint = "v1/projects" + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + _endpoint: str + + def __init__(self, config: DeepgramClientOptions): + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + self._endpoint = "v1/projects" super().__init__(config) - async def list_onprem_credentials(self, project_id: str): - self.logger.debug("OnPremClient.list_onprem_credentials ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/onprem/distribution/credentials" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - res = await self.get(url) - self.logger.verbose("result: %s", res) - self.logger.notice("list_onprem_credentials succeeded") - self.logger.debug("OnPremClient.list_onprem_credentials LEAVE") + async def list_onprem_credentials( + self, project_id: str, timeout: Optional[httpx.Timeout] = None + ): + """ + List all on-premises distribution credentials for a project. + """ + self._logger.debug("OnPremClient.list_onprem_credentials ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/onprem/distribution/credentials" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + res = await self.get(url, timeout=timeout) + self._logger.verbose("result: %s", res) + self._logger.notice("list_onprem_credentials succeeded") + self._logger.debug("OnPremClient.list_onprem_credentials LEAVE") return res async def get_onprem_credentials( - self, project_id: str, distribution_credentials_id: str + self, + project_id: str, + distribution_credentials_id: str, + timeout: Optional[httpx.Timeout] = None, ): - self.logger.debug("OnPremClient.get_onprem_credentials ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/onprem/distribution/credentials/{distribution_credentials_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("distribution_credentials_id: %s", distribution_credentials_id) - res = await self.get(url) - self.logger.verbose("result: %s", res) - self.logger.notice("get_onprem_credentials succeeded") - self.logger.debug("OnPremClient.get_onprem_credentials LEAVE") + """ + Get a specific on-premises distribution credential for a project. + """ + self._logger.debug("OnPremClient.get_onprem_credentials ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/onprem/distribution/credentials/{distribution_credentials_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info( + "distribution_credentials_id: %s", distribution_credentials_id + ) + res = await self.get(url, timeout=timeout) + self._logger.verbose("result: %s", res) + self._logger.notice("get_onprem_credentials succeeded") + self._logger.debug("OnPremClient.get_onprem_credentials LEAVE") return res - async def create_onprem_credentials(self, project_id: str, options): - self.logger.debug("OnPremClient.create_onprem_credentials ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/onprem/distribution/credentials/" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("options: %s", options) - res = await self.post(url, json=options) - self.logger.verbose("result: %s", res) - self.logger.notice("create_onprem_credentials succeeded") - self.logger.debug("OnPremClient.create_onprem_credentials LEAVE") + async def create_onprem_credentials( + self, project_id: str, options, timeout: Optional[httpx.Timeout] = None + ): + """ + Create a new on-premises distribution credential for a project. + """ + self._logger.debug("OnPremClient.create_onprem_credentials ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/onprem/distribution/credentials/" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("options: %s", options) + res = await self.post(url, json=options, timeout=timeout) + self._logger.verbose("result: %s", res) + self._logger.notice("create_onprem_credentials succeeded") + self._logger.debug("OnPremClient.create_onprem_credentials LEAVE") return res async def delete_onprem_credentials( - self, project_id: str, distribution_credentials_id: str + self, + project_id: str, + distribution_credentials_id: str, + timeout: Optional[httpx.Timeout] = None, ): - self.logger.debug("OnPremClient.delete_onprem_credentials ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/onprem/distribution/credentials/{distribution_credentials_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("distrbution_credentials_id: %s", distribution_credentials_id) - res = await self.delete(url) - self.logger.verbose("result: %s", res) - self.logger.notice("delete_onprem_credentials succeeded") - self.logger.debug("OnPremClient.delete_onprem_credentials LEAVE") + """ + Delete an on-premises distribution credential for a project. + """ + self._logger.debug("OnPremClient.delete_onprem_credentials ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/onprem/distribution/credentials/{distribution_credentials_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("distrbution_credentials_id: %s", distribution_credentials_id) + res = await self.delete(url, timeout=timeout) + self._logger.verbose("result: %s", res) + self._logger.notice("delete_onprem_credentials succeeded") + self._logger.debug("OnPremClient.delete_onprem_credentials LEAVE") return res diff --git a/deepgram/clients/onprem/v1/client.py b/deepgram/clients/onprem/v1/client.py index 296e5012..c60917f3 100644 --- a/deepgram/clients/onprem/v1/client.py +++ b/deepgram/clients/onprem/v1/client.py @@ -2,9 +2,13 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +import logging +from typing import Optional + import httpx -import logging, verboselogs +from deepgram.utils import verboselogs +from ....options import DeepgramClientOptions from ...abstract_sync_client import AbstractSyncRestClient @@ -18,69 +22,89 @@ class OnPremClient(AbstractSyncRestClient): config (DeepgramClientOptions): all the options for the client. """ - def __init__(self, config): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - self.endpoint = "v1/projects" + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + _endpoint: str + + def __init__(self, config: DeepgramClientOptions): + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + self._endpoint = "v1/projects" super().__init__(config) - def list_onprem_credentials(self, project_id: str, timeout: httpx.Timeout = None): - self.logger.debug("OnPremClient.list_onprem_credentials ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/onprem/distribution/credentials" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) + def list_onprem_credentials( + self, project_id: str, timeout: Optional[httpx.Timeout] = None + ): + """ + List all on-premises distribution credentials for a project. + """ + self._logger.debug("OnPremClient.list_onprem_credentials ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/onprem/distribution/credentials" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) res = self.get(url, timeout=timeout) - self.logger.verbose("result: %s", res) - self.logger.notice("list_onprem_credentials succeeded") - self.logger.debug("OnPremClient.list_onprem_credentials LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("list_onprem_credentials succeeded") + self._logger.debug("OnPremClient.list_onprem_credentials LEAVE") return res def get_onprem_credentials( self, project_id: str, distribution_credentials_id: str, - timeout: httpx.Timeout = None, + timeout: Optional[httpx.Timeout] = None, ): - self.logger.debug("OnPremClient.get_onprem_credentials ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/onprem/distribution/credentials/{distribution_credentials_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("distribution_credentials_id: %s", distribution_credentials_id) + """ + Get a specific on-premises distribution credential for a project. + """ + self._logger.debug("OnPremClient.get_onprem_credentials ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/onprem/distribution/credentials/{distribution_credentials_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info( + "distribution_credentials_id: %s", distribution_credentials_id + ) res = self.get(url, timeout=timeout) - self.logger.verbose("result: %s", res) - self.logger.notice("get_onprem_credentials succeeded") - self.logger.debug("OnPremClient.get_onprem_credentials LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("get_onprem_credentials succeeded") + self._logger.debug("OnPremClient.get_onprem_credentials LEAVE") return res def create_onprem_credentials( - self, project_id: str, options, timeout: httpx.Timeout = None + self, project_id: str, options, timeout: Optional[httpx.Timeout] = None ): - self.logger.debug("OnPremClient.create_onprem_credentials ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/onprem/distribution/credentials/" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("options: %s", options) + """ + Create a new on-premises distribution credential for a project. + """ + self._logger.debug("OnPremClient.create_onprem_credentials ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/onprem/distribution/credentials/" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("options: %s", options) res = self.post(url, json=options, timeout=timeout) - self.logger.verbose("result: %s", res) - self.logger.notice("create_onprem_credentials succeeded") - self.logger.debug("OnPremClient.create_onprem_credentials LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("create_onprem_credentials succeeded") + self._logger.debug("OnPremClient.create_onprem_credentials LEAVE") return res def delete_onprem_credentials( self, project_id: str, distribution_credentials_id: str, - timeout: httpx.Timeout = None, + timeout: Optional[httpx.Timeout] = None, ): - self.logger.debug("OnPremClient.delete_onprem_credentials ENTER") - url = f"{self.config.url}/{self.endpoint}/{project_id}/onprem/distribution/credentials/{distribution_credentials_id}" - self.logger.info("url: %s", url) - self.logger.info("project_id: %s", project_id) - self.logger.info("distrbution_credentials_id: %s", distribution_credentials_id) + """ + Delete an on-premises distribution credential for a project. + """ + self._logger.debug("OnPremClient.delete_onprem_credentials ENTER") + url = f"{self._config.url}/{self._endpoint}/{project_id}/onprem/distribution/credentials/{distribution_credentials_id}" + self._logger.info("url: %s", url) + self._logger.info("project_id: %s", project_id) + self._logger.info("distrbution_credentials_id: %s", distribution_credentials_id) res = self.delete(url, timeout=timeout) - self.logger.verbose("result: %s", res) - self.logger.notice("delete_onprem_credentials succeeded") - self.logger.debug("OnPremClient.delete_onprem_credentials LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("delete_onprem_credentials succeeded") + self._logger.debug("OnPremClient.delete_onprem_credentials LEAVE") return res diff --git a/deepgram/clients/prerecorded/__init__.py b/deepgram/clients/prerecorded/__init__.py index b6d9cc48..cef06476 100644 --- a/deepgram/clients/prerecorded/__init__.py +++ b/deepgram/clients/prerecorded/__init__.py @@ -7,16 +7,14 @@ from .client import PrerecordedOptions from .client import ( UrlSource, - BufferSource, - ReadStreamSource, FileSource, + PreRecordedStreamSource, PrerecordedSource, ) -from .client import Sentiment from .client import ( AsyncPrerecordedResponse, PrerecordedResponse, SyncPrerecordedResponse, ) -from ...options import DeepgramClientOptions +from ...options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/prerecorded/client.py b/deepgram/clients/prerecorded/client.py index 7f2ca45d..4db4474c 100644 --- a/deepgram/clients/prerecorded/client.py +++ b/deepgram/clients/prerecorded/client.py @@ -7,9 +7,8 @@ from .v1.options import ( PrerecordedOptions as PrerecordedOptionsLatest, UrlSource as UrlSourceLatest, - BufferSource as BufferSourceLatest, - ReadStreamSource as ReadStreamSourceLatest, FileSource as FileSourceLatest, + PreRecordedStreamSource as PreRecordedStreamSourceLatest, PrerecordedSource as PrerecordedSourceLatest, ) from .v1.response import ( @@ -17,91 +16,26 @@ PrerecordedResponse as PrerecordedResponseLatest, SyncPrerecordedResponse as SyncPrerecordedResponseLatest, ) -from .enums import Sentiment -""" -The client.py points to the current supported version in the SDK. -Older versions are supported in the SDK for backwards compatibility. -""" - -# input -class PrerecordedOptions(PrerecordedOptionsLatest): - """ - Please see PrerecordedOptionsLatest for details - """ - - pass - - -class UrlSource(UrlSourceLatest): - """ - Please see UrlSourceLatest for details - """ - - pass - - -class BufferSource(BufferSourceLatest): - """ - Please see BufferSourceLatest for details - """ - - pass - - -class ReadStreamSource(ReadStreamSourceLatest): - """ - Please see ReadStreamSourceLatest for details - """ - - pass +# The client.py points to the current supported version in the SDK. +# Older versions are supported in the SDK for backwards compatibility. +# input +PrerecordedOptions = PrerecordedOptionsLatest +PreRecordedStreamSource = PreRecordedStreamSourceLatest +UrlSource = UrlSourceLatest FileSource = FileSourceLatest PrerecordedSource = PrerecordedSourceLatest # output -class AsyncPrerecordedResponse(AsyncPrerecordedResponseLatest): - """ - Please see AsyncPrerecordedResponseLatest for details - """ - - pass - - -class PrerecordedResponse(PrerecordedResponseLatest): - """ - Please see PrerecordedResponseLatest for details - """ - - pass - - -class SyncPrerecordedResponse(PrerecordedResponseLatest): - """ - Please see PrerecordedResponseLatest for details - """ - - pass +AsyncPrerecordedResponse = AsyncPrerecordedResponseLatest +PrerecordedResponse = PrerecordedResponseLatest +SyncPrerecordedResponse = SyncPrerecordedResponseLatest # clients -class PreRecordedClient(PreRecordedClientLatest): - """ - Please see PreRecordedClientLatest for details - """ - - def __init__(self, config): - self.config = config - super().__init__(config) - - -class AsyncPreRecordedClient(AsyncPreRecordedClientLatest): - """ - Please see AsyncPreRecordedClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) +PreRecordedClient = PreRecordedClientLatest +AsyncPreRecordedClient = AsyncPreRecordedClientLatest diff --git a/deepgram/clients/prerecorded/enums.py b/deepgram/clients/prerecorded/enums.py deleted file mode 100644 index e910bc7f..00000000 --- a/deepgram/clients/prerecorded/enums.py +++ /dev/null @@ -1,16 +0,0 @@ -# Copyright 2024 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 - -from enum import Enum - -""" -Constants mapping to events from the Deepgram API -""" - - -class Sentiment(str, Enum): - UNKNOWN: str = "" - NEGATIVE: str = "negative" - NEUTRAL: str = "neutral" - POSITIVE: str = "positive" diff --git a/deepgram/clients/prerecorded/v1/__init__.py b/deepgram/clients/prerecorded/v1/__init__.py index aa967037..65334261 100644 --- a/deepgram/clients/prerecorded/v1/__init__.py +++ b/deepgram/clients/prerecorded/v1/__init__.py @@ -6,17 +6,16 @@ from .async_client import AsyncPreRecordedClient from .options import ( PrerecordedOptions, - UrlSource, - BufferSource, - ReadStreamSource, FileSource, + UrlSource, + PreRecordedStreamSource, PrerecordedSource, ) -from ..enums import Sentiment from .response import ( AsyncPrerecordedResponse, PrerecordedResponse, SyncPrerecordedResponse, + Sentiment, ) -from ....options import DeepgramClientOptions +from ....options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/prerecorded/v1/async_client.py b/deepgram/clients/prerecorded/v1/async_client.py index fdb60f2d..ad422066 100644 --- a/deepgram/clients/prerecorded/v1/async_client.py +++ b/deepgram/clients/prerecorded/v1/async_client.py @@ -2,23 +2,21 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import httpx -import logging, verboselogs -import json +import logging from typing import Dict, Union, Optional +import httpx + +from deepgram.utils import verboselogs +from ....options import DeepgramClientOptions from ...abstract_async_client import AbstractAsyncRestClient from ..errors import DeepgramError, DeepgramTypeError -from .helpers import is_buffer_source, is_readstream_source, is_url_source -from ..enums import Sentiment +from .helpers import is_buffer_source, is_readstream_source, is_url_source from .options import ( PrerecordedOptions, - UrlSource, - BufferSource, - ReadStreamSource, FileSource, - PrerecordedSource, + UrlSource, ) from .response import AsyncPrerecordedResponse, PrerecordedResponse @@ -29,27 +27,15 @@ class AsyncPreRecordedClient(AbstractAsyncRestClient): Provides methods for transcribing audio from URLs and files. """ - def __init__(self, config): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - super().__init__(config) - - """ - Transcribes audio from a URL source. - - Args: - source (UrlSource): The URL source of the audio to transcribe. - options (PrerecordedOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/listen"). - - Returns: - PrerecordedResponse: An object containing the transcription result. + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions - Raises: - DeepgramTypeError: Raised for known API errors. - """ + def __init__(self, config: DeepgramClientOptions): + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + super().__init__(config) async def transcribe_url( self, @@ -59,16 +45,32 @@ async def transcribe_url( headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/listen", - ) -> PrerecordedResponse: - self.logger.debug("PreRecordedClient.transcribe_url ENTER") - - if (options is Dict and "callback" in options is not None) or ( - isinstance(options, PrerecordedOptions) and options.callback is not None - ): - self.logger.debug("PreRecordedClient.transcribe_url LEAVE") + ) -> Union[AsyncPrerecordedResponse, PrerecordedResponse]: + """ + Transcribes audio from a URL source. + + Args: + source (UrlSource): The URL source of the audio to transcribe. + options (PrerecordedOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/listen"). + + Returns: + PrerecordedResponse: An object containing the transcription result. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("PreRecordedClient.transcribe_url ENTER") + + if ( + isinstance(options, dict) + and "callback" in options + and options["callback"] is not None + ) or (isinstance(options, PrerecordedOptions) and options.callback is not None): + self._logger.debug("PreRecordedClient.transcribe_url LEAVE") return await self.transcribe_url_callback( source, - callback=options.callback, + callback=options["callback"], options=options, addons=addons, headers=headers, @@ -76,27 +78,27 @@ async def transcribe_url( endpoint=endpoint, ) - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if is_url_source(source): body = source else: - self.logger.error("Unknown transcription source type") - self.logger.debug("PreRecordedClient.transcribe_url LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("PreRecordedClient.transcribe_url LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, PrerecordedOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("PreRecordedClient.transcribe_url LEAVE") + self._logger.error("options.check failed") + self._logger.debug("PreRecordedClient.transcribe_url LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, PrerecordedOptions): - self.logger.info("PrerecordedOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("PrerecordedOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, options=options, @@ -105,29 +107,13 @@ async def transcribe_url( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = PrerecordedResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("transcribe_url succeeded") - self.logger.debug("PreRecordedClient.transcribe_url LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("transcribe_url succeeded") + self._logger.debug("PreRecordedClient.transcribe_url LEAVE") return res - """ - Transcribes audio from a URL source and sends the result to a callback URL. - - Args: - source (UrlSource): The URL source of the audio to transcribe. - callback (str): The callback URL where the transcription results will be sent. - options (PrerecordedOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/listen"). - - Returns: - AsyncPrerecordedResponse: An object containing the request_id or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - async def transcribe_url_callback( self, source: UrlSource, @@ -138,9 +124,24 @@ async def transcribe_url_callback( timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/listen", ) -> AsyncPrerecordedResponse: - self.logger.debug("PreRecordedClient.transcribe_url_callback ENTER") + """ + Transcribes audio from a URL source and sends the result to a callback URL. + + Args: + source (UrlSource): The URL source of the audio to transcribe. + callback (str): The callback URL where the transcription results will be sent. + options (PrerecordedOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/listen"). + + Returns: + AsyncPrerecordedResponse: An object containing the request_id or an error message. - url = f"{self.config.url}/{endpoint}" + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("PreRecordedClient.transcribe_url_callback ENTER") + + url = f"{self._config.url}/{endpoint}" if options is None: options = {} if isinstance(options, PrerecordedOptions): @@ -150,23 +151,23 @@ async def transcribe_url_callback( if is_url_source(source): body = source else: - self.logger.error("Unknown transcription source type") - self.logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, PrerecordedOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") + self._logger.error("options.check failed") + self._logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, PrerecordedOptions): - self.logger.info("PrerecordedOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("PrerecordedOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, options=options, @@ -175,28 +176,13 @@ async def transcribe_url_callback( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AsyncPrerecordedResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("transcribe_url_callback succeeded") - self.logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("transcribe_url_callback succeeded") + self._logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") return res - """ - Transcribes audio from a local file source. - - Args: - source (FileSource): The local file source of the audio to transcribe. - options (PrerecordedOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/listen"). - - Returns: - PrerecordedResponse: An object containing the transcription result or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - async def transcribe_file( self, source: FileSource, @@ -205,16 +191,32 @@ async def transcribe_file( headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/listen", - ) -> PrerecordedResponse: - self.logger.debug("PreRecordedClient.transcribe_file ENTER") - - if (options is Dict and "callback" in options is not None) or ( - isinstance(options, PrerecordedOptions) and options.callback is not None - ): - self.logger.debug("PreRecordedClient.transcribe_file LEAVE") + ) -> Union[AsyncPrerecordedResponse, PrerecordedResponse]: + """ + Transcribes audio from a local file source. + + Args: + source (FileSource): The local file source of the audio to transcribe. + options (PrerecordedOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/listen"). + + Returns: + PrerecordedResponse: An object containing the transcription result or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("PreRecordedClient.transcribe_file ENTER") + + if ( + isinstance(options, dict) + and "callback" in options + and options["callback"] is not None + ) or (isinstance(options, PrerecordedOptions) and options.callback is not None): + self._logger.debug("PreRecordedClient.transcribe_file LEAVE") return await self.transcribe_file_callback( source, - callback=options.callback, + callback=options["callback"], options=options, addons=addons, headers=headers, @@ -222,28 +224,28 @@ async def transcribe_file( endpoint=endpoint, ) - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if is_buffer_source(source): - body = source["buffer"] + body = source["buffer"] # type: ignore elif is_readstream_source(source): - body = source["stream"] + body = source["stream"] # type: ignore else: - self.logger.error("Unknown transcription source type") - self.logger.debug("PreRecordedClient.transcribe_file LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("PreRecordedClient.transcribe_file LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, PrerecordedOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("PreRecordedClient.transcribe_file LEAVE") + self._logger.error("options.check failed") + self._logger.debug("PreRecordedClient.transcribe_file LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) + self._logger.info("url: %s", url) if isinstance(options, PrerecordedOptions): - self.logger.info("PrerecordedOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("PrerecordedOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, options=options, @@ -252,29 +254,13 @@ async def transcribe_file( content=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = PrerecordedResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("transcribe_file succeeded") - self.logger.debug("PreRecordedClient.transcribe_file LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("transcribe_file succeeded") + self._logger.debug("PreRecordedClient.transcribe_file LEAVE") return res - """ - Transcribes audio from a local file source and sends the result to a callback URL. - - Args: - source (FileSource): The local file source of the audio to transcribe. - callback (str): The callback URL where the transcription results will be sent. - options (PrerecordedOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/listen"). - - Returns: - AsyncPrerecordedResponse: An object containing the request_id or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - async def transcribe_file_callback( self, source: FileSource, @@ -285,9 +271,24 @@ async def transcribe_file_callback( timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/listen", ) -> AsyncPrerecordedResponse: - self.logger.debug("PreRecordedClient.transcribe_file_callback ENTER") + """ + Transcribes audio from a local file source and sends the result to a callback URL. + + Args: + source (FileSource): The local file source of the audio to transcribe. + callback (str): The callback URL where the transcription results will be sent. + options (PrerecordedOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/listen"). + + Returns: + AsyncPrerecordedResponse: An object containing the request_id or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("PreRecordedClient.transcribe_file_callback ENTER") - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if options is None: options = {} if isinstance(options, PrerecordedOptions): @@ -295,26 +296,26 @@ async def transcribe_file_callback( else: options["callback"] = callback if is_buffer_source(source): - body = source["buffer"] + body = source["buffer"] # type: ignore elif is_readstream_source(source): - body = source["stream"] + body = source["stream"] # type: ignore else: - self.logger.error("Unknown transcription source type") - self.logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, PrerecordedOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") + self._logger.error("options.check failed") + self._logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) + self._logger.info("url: %s", url) if isinstance(options, PrerecordedOptions): - self.logger.info("PrerecordedOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("PrerecordedOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = await self.post( url, options=options, @@ -323,9 +324,9 @@ async def transcribe_file_callback( content=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AsyncPrerecordedResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("transcribe_file_callback succeeded") - self.logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("transcribe_file_callback succeeded") + self._logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") return res diff --git a/deepgram/clients/prerecorded/v1/client.py b/deepgram/clients/prerecorded/v1/client.py index 9d3c5461..3c0d7959 100644 --- a/deepgram/clients/prerecorded/v1/client.py +++ b/deepgram/clients/prerecorded/v1/client.py @@ -2,23 +2,21 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import httpx -import logging, verboselogs -import json +import logging from typing import Dict, Union, Optional +import httpx + +from deepgram.utils import verboselogs +from ....options import DeepgramClientOptions from ...abstract_sync_client import AbstractSyncRestClient from ..errors import DeepgramError, DeepgramTypeError -from .helpers import is_buffer_source, is_readstream_source, is_url_source -from ..enums import Sentiment +from .helpers import is_buffer_source, is_readstream_source, is_url_source from .options import ( PrerecordedOptions, - UrlSource, - BufferSource, - ReadStreamSource, FileSource, - PrerecordedSource, + UrlSource, ) from .response import AsyncPrerecordedResponse, PrerecordedResponse @@ -29,27 +27,15 @@ class PreRecordedClient(AbstractSyncRestClient): Provides methods for transcribing audio from URLs and files. """ - def __init__(self, config): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - super().__init__(config) - - """ - Transcribes audio from a URL source. - - Args: - source (UrlSource): The URL source of the audio to transcribe. - options (PrerecordedOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/listen"). + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions - Returns: - PrerecordedResponse: An object containing the transcription result. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ + def __init__(self, config: DeepgramClientOptions): + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + super().__init__(config) def transcribe_url( self, @@ -59,16 +45,32 @@ def transcribe_url( headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/listen", - ) -> PrerecordedResponse: - self.logger.debug("PreRecordedClient.transcribe_url ENTER") - - if (options is Dict and "callback" in options is not None) or ( - isinstance(options, PrerecordedOptions) and options.callback is not None - ): - self.logger.debug("PreRecordedClient.transcribe_url LEAVE") + ) -> Union[AsyncPrerecordedResponse, PrerecordedResponse]: + """ + Transcribes audio from a URL source. + + Args: + source (UrlSource): The URL source of the audio to transcribe. + options (PrerecordedOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/listen"). + + Returns: + PrerecordedResponse: An object containing the transcription result. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("PreRecordedClient.transcribe_url ENTER") + + if ( + isinstance(options, dict) + and "callback" in options + and options["callback"] is not None + ) or (isinstance(options, PrerecordedOptions) and options.callback is not None): + self._logger.debug("PreRecordedClient.transcribe_url LEAVE") return self.transcribe_url_callback( source, - callback=options.callback, + callback=options["callback"], options=options, addons=addons, headers=headers, @@ -76,27 +78,27 @@ def transcribe_url( endpoint=endpoint, ) - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if is_url_source(source): body = source else: - self.logger.error("Unknown transcription source type") - self.logger.debug("PreRecordedClient.transcribe_url LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("PreRecordedClient.transcribe_url LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, PrerecordedOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("PreRecordedClient.transcribe_url LEAVE") + self._logger.error("options.check failed") + self._logger.debug("PreRecordedClient.transcribe_url LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, PrerecordedOptions): - self.logger.info("PrerecordedOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("PrerecordedOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, options=options, @@ -105,29 +107,13 @@ def transcribe_url( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = PrerecordedResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("transcribe_url succeeded") - self.logger.debug("PreRecordedClient.transcribe_url LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("transcribe_url succeeded") + self._logger.debug("PreRecordedClient.transcribe_url LEAVE") return res - """ - Transcribes audio from a URL source and sends the result to a callback URL. - - Args: - source (UrlSource): The URL source of the audio to transcribe. - callback (str): The callback URL where the transcription results will be sent. - options (PrerecordedOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/listen"). - - Returns: - AsyncPrerecordedResponse: An object containing the request_id or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - def transcribe_url_callback( self, source: UrlSource, @@ -138,9 +124,24 @@ def transcribe_url_callback( timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/listen", ) -> AsyncPrerecordedResponse: - self.logger.debug("PreRecordedClient.transcribe_url_callback ENTER") + """ + Transcribes audio from a URL source and sends the result to a callback URL. - url = f"{self.config.url}/{endpoint}" + Args: + source (UrlSource): The URL source of the audio to transcribe. + callback (str): The callback URL where the transcription results will be sent. + options (PrerecordedOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/listen"). + + Returns: + AsyncPrerecordedResponse: An object containing the request_id or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("PreRecordedClient.transcribe_url_callback ENTER") + + url = f"{self._config.url}/{endpoint}" if options is None: options = {} if isinstance(options, PrerecordedOptions): @@ -150,23 +151,23 @@ def transcribe_url_callback( if is_url_source(source): body = source else: - self.logger.error("Unknown transcription source type") - self.logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, PrerecordedOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") + self._logger.error("options.check failed") + self._logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, PrerecordedOptions): - self.logger.info("PrerecordedOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("PrerecordedOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, options=options, @@ -175,28 +176,13 @@ def transcribe_url_callback( json=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AsyncPrerecordedResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("transcribe_url_callback succeeded") - self.logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("transcribe_url_callback succeeded") + self._logger.debug("PreRecordedClient.transcribe_url_callback LEAVE") return res - """ - Transcribes audio from a local file source. - - Args: - source (FileSource): The local file source of the audio to transcribe. - options (PrerecordedOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/listen"). - - Returns: - PrerecordedResponse: An object containing the transcription result or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - def transcribe_file( self, source: FileSource, @@ -205,16 +191,32 @@ def transcribe_file( headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/listen", - ) -> PrerecordedResponse: - self.logger.debug("PreRecordedClient.transcribe_file ENTER") - - if (options is Dict and "callback" in options is not None) or ( - isinstance(options, PrerecordedOptions) and options.callback is not None - ): - self.logger.debug("PreRecordedClient.transcribe_file LEAVE") + ) -> Union[AsyncPrerecordedResponse, PrerecordedResponse]: + """ + Transcribes audio from a local file source. + + Args: + source (FileSource): The local file source of the audio to transcribe. + options (PrerecordedOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/listen"). + + Returns: + PrerecordedResponse: An object containing the transcription result or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("PreRecordedClient.transcribe_file ENTER") + + if ( + isinstance(options, dict) + and "callback" in options + and options["callback"] is not None + ) or (isinstance(options, PrerecordedOptions) and options.callback is not None): + self._logger.debug("PreRecordedClient.transcribe_file LEAVE") return self.transcribe_file_callback( source, - callback=options.callback, + callback=options["callback"], options=options, addons=addons, headers=headers, @@ -222,28 +224,29 @@ def transcribe_file( endpoint=endpoint, ) - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" + if is_buffer_source(source): - body = source["buffer"] + body = source["buffer"] # type: ignore elif is_readstream_source(source): - body = source["stream"] + body = source["stream"] # type: ignore else: - self.logger.error("Unknown transcription source type") - self.logger.debug("PreRecordedClient.transcribe_file LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("PreRecordedClient.transcribe_file LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, PrerecordedOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("PreRecordedClient.transcribe_file LEAVE") + self._logger.error("options.check failed") + self._logger.debug("PreRecordedClient.transcribe_file LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) + self._logger.info("url: %s", url) if isinstance(options, PrerecordedOptions): - self.logger.info("PrerecordedOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("PrerecordedOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, options=options, @@ -252,29 +255,13 @@ def transcribe_file( content=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = PrerecordedResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("transcribe_file succeeded") - self.logger.debug("PreRecordedClient.transcribe_file LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("transcribe_file succeeded") + self._logger.debug("PreRecordedClient.transcribe_file LEAVE") return res - """ - Transcribes audio from a local file source and sends the result to a callback URL. - - Args: - source (FileSource): The local file source of the audio to transcribe. - callback (str): The callback URL where the transcription results will be sent. - options (PrerecordedOptions): Additional options for the transcription (default is None). - endpoint (str): The API endpoint for the transcription (default is "v1/listen"). - - Returns: - AsyncPrerecordedResponse: An object containing the request_id or an error message. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ - def transcribe_file_callback( self, source: FileSource, @@ -285,9 +272,24 @@ def transcribe_file_callback( timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/listen", ) -> AsyncPrerecordedResponse: - self.logger.debug("PreRecordedClient.transcribe_file_callback ENTER") + """ + Transcribes audio from a local file source and sends the result to a callback URL. + + Args: + source (FileSource): The local file source of the audio to transcribe. + callback (str): The callback URL where the transcription results will be sent. + options (PrerecordedOptions): Additional options for the transcription (default is None). + endpoint (str): The API endpoint for the transcription (default is "v1/listen"). + + Returns: + AsyncPrerecordedResponse: An object containing the request_id or an error message. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("PreRecordedClient.transcribe_file_callback ENTER") - url = f"{self.config.url}/{endpoint}" + url = f"{self._config.url}/{endpoint}" if options is None: options = {} if isinstance(options, PrerecordedOptions): @@ -295,26 +297,26 @@ def transcribe_file_callback( else: options["callback"] = callback if is_buffer_source(source): - body = source["buffer"] + body = source["buffer"] # type: ignore elif is_readstream_source(source): - body = source["stream"] + body = source["stream"] # type: ignore else: - self.logger.error("Unknown transcription source type") - self.logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") + self._logger.error("Unknown transcription source type") + self._logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") raise DeepgramTypeError("Unknown transcription source type") if isinstance(options, PrerecordedOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") + self._logger.error("options.check failed") + self._logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") raise DeepgramError("Fatal transcription options error") - self.logger.info("url: %s", url) + self._logger.info("url: %s", url) if isinstance(options, PrerecordedOptions): - self.logger.info("PrerecordedOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("PrerecordedOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) result = self.post( url, options=options, @@ -323,9 +325,9 @@ def transcribe_file_callback( content=body, timeout=timeout, ) - self.logger.info("json: %s", result) + self._logger.info("json: %s", result) res = AsyncPrerecordedResponse.from_json(result) - self.logger.verbose("result: %s", res) - self.logger.notice("transcribe_file_callback succeeded") - self.logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") + self._logger.verbose("result: %s", res) + self._logger.notice("transcribe_file_callback succeeded") + self._logger.debug("PreRecordedClient.transcribe_file_callback LEAVE") return res diff --git a/deepgram/clients/prerecorded/v1/helpers.py b/deepgram/clients/prerecorded/v1/helpers.py index fe8d4d85..2def898f 100644 --- a/deepgram/clients/prerecorded/v1/helpers.py +++ b/deepgram/clients/prerecorded/v1/helpers.py @@ -6,12 +6,21 @@ def is_buffer_source(provided_source: PrerecordedSource) -> bool: + """ + Check if the provided source is a buffer source. + """ return "buffer" in provided_source def is_readstream_source(provided_source: PrerecordedSource) -> bool: + """ + Check if the provided source is a readstream source. + """ return "stream" in provided_source def is_url_source(provided_source: PrerecordedSource) -> bool: + """ + Check if the provided source is a URL source. + """ return "url" in provided_source diff --git a/deepgram/clients/prerecorded/v1/options.py b/deepgram/clients/prerecorded/v1/options.py index 85244289..29a1284f 100644 --- a/deepgram/clients/prerecorded/v1/options.py +++ b/deepgram/clients/prerecorded/v1/options.py @@ -2,18 +2,20 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +from typing import Union, List, Optional +import logging + from dataclasses import dataclass, field -from dataclasses_json import dataclass_json, config +from dataclasses_json import config as dataclass_config, DataClassJsonMixin -from io import BufferedReader -from typing import Union, List, Optional -from typing_extensions import TypedDict -import logging, verboselogs +from deepgram.utils import verboselogs +from ...common import StreamSource, UrlSource, FileSource -@dataclass_json @dataclass -class PrerecordedOptions: +class PrerecordedOptions( + DataClassJsonMixin +): # pylint: disable=too-many-instance-attributes """ Contains all the options for the PrerecordedClient. @@ -22,124 +24,124 @@ class PrerecordedOptions: """ alternatives: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) channels: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) callback: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) callback_method: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) custom_intent: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) custom_intent_mode: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) custom_topics: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) custom_topic_mode: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) detect_entities: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) detect_language: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) detect_topics: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) diarize: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) diarize_version: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) dictation: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) encoding: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) extra: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) filler_words: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) intents: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) keywords: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) language: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) measurements: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) model: Optional[str] = field( - default="nova-2", metadata=config(exclude=lambda f: f is None) + default="nova-2", metadata=dataclass_config(exclude=lambda f: f is None) ) multichannel: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) numerals: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) paragraphs: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) profanity_filter: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) punctuate: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) redact: Optional[Union[List[str], bool, str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) replace: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sample_rate: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) search: Optional[Union[List[str], str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) smart_format: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) summarize: Optional[Union[bool, str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) tag: Optional[List[str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) tier: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) topics: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) utt_split: Optional[float] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) utterances: Optional[bool] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) version: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -153,11 +155,13 @@ def __str__(self) -> str: return self.to_json(indent=4) def check(self): - verboselogs.install() - logger = logging.getLogger(__name__) + """ + Check the options for any deprecated fields or values. + """ + logger = verboselogs.VerboseLogger(__name__) logger.addHandler(logging.StreamHandler()) prev = logger.level - logger.setLevel(logging.ERROR) + logger.setLevel(verboselogs.ERROR) if self.tier: logger.error( @@ -169,47 +173,9 @@ def check(self): return True -class ReadStreamSource(TypedDict): - """ - Represents a data source for reading binary data from a stream-like source. - - This class is used to specify a source of binary data that can be read from - a stream, such as an audio file in .wav format. - - Attributes: - stream (BufferedReader): A BufferedReader object for reading binary data. - """ - - stream: BufferedReader - - -class UrlSource(TypedDict): - """ - Represents a data source for specifying the location of a file via a URL. - - This class is used to specify a hosted file URL, typically pointing to an - externally hosted file, such as an audio file hosted on a server or the internet. - - Attributes: - url (str): The URL pointing to the hosted file. - """ - - url: str - - -class BufferSource(TypedDict): - """ - Represents a data source for handling raw binary data. - - This class is used to specify raw binary data, such as audio data in its - binary form, which can be captured from a microphone or generated synthetically. - - Attributes: - buffer (bytes): The binary data. - """ - - buffer: bytes - +# PrerecordedSource for backwards compatibility +PreRecordedStreamSource = StreamSource -PrerecordedSource = Union[UrlSource, BufferSource, ReadStreamSource] -FileSource = Union[BufferSource, ReadStreamSource] +# UrlSource +# FileSource +PrerecordedSource = Union[UrlSource, FileSource] diff --git a/deepgram/clients/prerecorded/v1/response.py b/deepgram/clients/prerecorded/v1/response.py index c6422036..31571c49 100644 --- a/deepgram/clients/prerecorded/v1/response.py +++ b/deepgram/clients/prerecorded/v1/response.py @@ -2,17 +2,22 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +from typing import List, Optional, Dict + from dataclasses import dataclass, field -from dataclasses_json import config, dataclass_json -from typing import List, Optional, TypedDict, Dict -from ..enums import Sentiment +from dataclasses_json import config as dataclass_config, DataClassJsonMixin + +from ...common import Sentiment # Async Prerecorded Response Types: -@dataclass_json @dataclass -class AsyncPrerecordedResponse: +class AsyncPrerecordedResponse(DataClassJsonMixin): + """ + The response object for the async prerecorded API. + """ + request_id: str = "" def __getitem__(self, key): @@ -29,9 +34,12 @@ def __str__(self) -> str: # Prerecorded Response Types: -@dataclass_json @dataclass -class SummaryInfo: +class SummaryInfo(DataClassJsonMixin): + """ + The summary information for the response. + """ + input_tokens: int = 0 output_tokens: int = 0 model_uuid: str = "" @@ -47,9 +55,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class ModelInfo: +class ModelInfo(DataClassJsonMixin): + """ + The model information for the response. + """ + name: str = "" version: str = "" arch: str = "" @@ -65,9 +76,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class IntentsInfo: +class IntentsInfo(DataClassJsonMixin): + """ + The intents information for the response. + """ + model_uuid: str = "" input_tokens: int = 0 output_tokens: int = 0 @@ -83,9 +97,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class SentimentInfo: +class SentimentInfo(DataClassJsonMixin): + """ + The sentiment information for the response. + """ + model_uuid: str = "" input_tokens: int = 0 output_tokens: int = 0 @@ -101,9 +118,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class TopicsInfo: +class TopicsInfo(DataClassJsonMixin): + """ + The topics information for the response. + """ + model_uuid: str = "" input_tokens: int = 0 output_tokens: int = 0 @@ -119,9 +139,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Metadata: +class Metadata(DataClassJsonMixin): # pylint: disable=too-many-instance-attributes + """ + The metadata for the response. + """ + transaction_key: str = "" request_id: str = "" sha256: str = "" @@ -129,28 +152,30 @@ class Metadata: duration: float = 0 channels: int = 0 models: Optional[List[str]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) + # pylint: disable=used-before-assignment warnings: Optional[List[Warning]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) + # pylint: enable=used-before-assignment model_info: Optional[Dict[str, ModelInfo]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) summary_info: Optional[SummaryInfo] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) intents_info: Optional[IntentsInfo] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment_info: Optional[SentimentInfo] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) topics_info: Optional[TopicsInfo] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) extra: Optional[Dict] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -185,9 +210,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class SummaryV1: +class SummaryV1(DataClassJsonMixin): + """ + The summary information for the response. + """ + summary: str = "" start_word: float = 0 end_word: float = 0 @@ -203,15 +231,15 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json -@dataclass -class Summaries(SummaryV1): # internal reference to old name - pass +Summaries = SummaryV1 -@dataclass_json @dataclass -class SummaryV2: +class SummaryV2(DataClassJsonMixin): + """ + The summary information for the response. + """ + result: str = "" short: str = "" @@ -226,15 +254,15 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json -@dataclass -class Summary(SummaryV2): # internal reference to old name - pass +Summary = SummaryV2 -@dataclass_json @dataclass -class Hit: +class Hit(DataClassJsonMixin): + """ + The hit information for the response. + """ + confidence: float = 0 start: float = 0 end: float = 0 @@ -251,27 +279,30 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Word: +class Word(DataClassJsonMixin): # pylint: disable=too-many-instance-attributes + """ + The word information for the response. + """ + word: str = "" start: float = 0 end: float = 0 confidence: float = 0 punctuated_word: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) speaker: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) speaker_confidence: Optional[float] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment: Optional[Sentiment] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment_score: Optional[float] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -287,17 +318,20 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Sentence: +class Sentence(DataClassJsonMixin): + """ + The sentence information for the response. + """ + text: str = "" start: float = 0 end: float = 0 sentiment: Optional[Sentiment] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment_score: Optional[float] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -313,21 +347,24 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Paragraph: - sentences: List[Sentence] = None +class Paragraph(DataClassJsonMixin): + """ + The paragraph information for the response. + """ + + sentences: List[Sentence] = field(default_factory=list) start: float = 0 end: float = 0 num_words: int = 0 speaker: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment: Optional[Sentiment] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment_score: Optional[float] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -347,12 +384,15 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Paragraphs: +class Paragraphs(DataClassJsonMixin): + """ + The paragraphs information for the response. + """ + transcript: Optional[str] = "" paragraphs: Optional[List[Paragraph]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -370,9 +410,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Translation: +class Translation(DataClassJsonMixin): + """ + The translation information for the response. + """ + language: Optional[str] = "" translation: Optional[str] = "" @@ -387,9 +430,14 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Warning: +class Warning( + DataClassJsonMixin +): # pylint: disable=used-before-assignment,redefined-builtin + """ + The warning information for the response. + """ + parameter: str = "" type: str = "" message: str = "" @@ -405,11 +453,14 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Search: +class Search(DataClassJsonMixin): + """ + The search information for the response. + """ + query: str = "" - hits: List[Hit] = None + hits: List[Hit] = field(default_factory=list) def __getitem__(self, key): _dict = self.to_dict() @@ -424,23 +475,26 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Utterance: +class Utterance(DataClassJsonMixin): # pylint: disable=too-many-instance-attributes + """ + The utterance information for the response. + """ + start: float = 0 end: float = 0 confidence: float = 0 channel: int = 0 transcript: str = "" - words: List[Word] = None + words: List[Word] = field(default_factory=list) speaker: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment: Optional[Sentiment] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment_score: Optional[float] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) id: str = "" @@ -459,9 +513,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Entity: +class Entity(DataClassJsonMixin): + """ + The entity information for the response. + """ + label: str = "" value: str = "" confidence: float = 0 @@ -479,23 +536,26 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Alternative: +class Alternative(DataClassJsonMixin): + """ + The alternative information for the response. + """ + transcript: str = "" confidence: float = 0 - words: List[Word] = None + words: List[Word] = field(default_factory=list) summaries: Optional[List[SummaryV1]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) paragraphs: Optional[Paragraphs] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) entities: Optional[List[Entity]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) translations: Optional[List[Translation]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -526,18 +586,21 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Channel: +class Channel(DataClassJsonMixin): + """ + The channel information for the response. + """ + search: Optional[List[Search]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) - alternatives: List[Alternative] = None + alternatives: List[Alternative] = field(default_factory=list) detected_language: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) language_confidence: Optional[float] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -558,9 +621,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Intent: +class Intent(DataClassJsonMixin): + """ + The intent information for the response. + """ + intent: str = "" confidence_score: float = 0 @@ -575,11 +641,14 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Average: - sentiment: Sentiment = None - sentiment_score: float = None +class Average(DataClassJsonMixin): + """ + The average information for the response. + """ + + sentiment: Sentiment + sentiment_score: float def __getitem__(self, key): _dict = self.to_dict() @@ -594,9 +663,12 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Topic: +class Topic(DataClassJsonMixin): + """ + The topic information for the response. + """ + topic: str = "" confidence_score: float = 0 @@ -611,23 +683,26 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Segment: +class Segment(DataClassJsonMixin): + """ + The segment information for the response. + """ + text: str = "" start_word: int = 0 end_word: int = 0 sentiment: Optional[Sentiment] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiment_score: Optional[float] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) intents: Optional[List[Intent]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) topics: Optional[List[Topic]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -649,14 +724,17 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Sentiments: +class Sentiments(DataClassJsonMixin): + """ + The sentiments information for the response. + """ + segments: Optional[List[Segment]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) average: Optional[Average] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -676,11 +754,14 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Topics: +class Topics(DataClassJsonMixin): + """ + The topics information for the response. + """ + segments: Optional[List[Segment]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -698,11 +779,14 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Intents: +class Intents(DataClassJsonMixin): + """ + The intents information for the response. + """ + segments: Optional[List[Segment]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -720,26 +804,29 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json @dataclass -class Results: +class Results(DataClassJsonMixin): + """ + The results information for the response. + """ + channels: Optional[List[Channel]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) utterances: Optional[List[Utterance]] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) summary: Optional[SummaryV2] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sentiments: Optional[Sentiments] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) topics: Optional[Topics] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) intents: Optional[Intents] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -772,14 +859,17 @@ def __str__(self) -> str: # Prerecorded Response Result: -@dataclass_json @dataclass -class PrerecordedResponse: +class PrerecordedResponse(DataClassJsonMixin): + """ + The response object for the prerecorded API. + """ + metadata: Optional[Metadata] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) results: Optional[Results] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -797,7 +887,4 @@ def __str__(self) -> str: return self.to_json(indent=4) -@dataclass_json -@dataclass -class SyncPrerecordedResponse(PrerecordedResponse): - pass +SyncPrerecordedResponse = PrerecordedResponse diff --git a/deepgram/clients/read.py b/deepgram/clients/read.py index 8c51dc15..28e01091 100644 --- a/deepgram/clients/read.py +++ b/deepgram/clients/read.py @@ -3,27 +3,12 @@ # SPDX-License-Identifier: MIT from importlib import import_module -import logging, verboselogs -from typing import Union +import logging +from deepgram.utils import verboselogs from ..options import DeepgramClientOptions from .errors import DeepgramModuleError -# live client -# classes and input -from .analyze import ( - AnalyzeClient, - AsyncAnalyzeClient, - AnalyzeOptions, -) - -# responses -from .analyze import ( - AsyncAnalyzeResponse, - AnalyzeResponse, - SyncAnalyzeResponse, -) - class Read: """ @@ -43,84 +28,104 @@ class Read: asyncread: Returns an (Async) AnalyzeClient instance for interacting with Deepgram's read transcription services. """ + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + def __init__(self, config: DeepgramClientOptions): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config @property def analyze(self): - return self.Version(self.config, "analyze") + """ + Returns an AnalyzeClient instance for interacting with Deepgram's read transcription services. + """ + return self.Version(self._config, "analyze") @property def asyncanalyze(self): - return self.Version(self.config, "asyncanalyze") + """ + Returns an AsyncAnalyzeClient instance for interacting with Deepgram's read transcription services. + """ + return self.Version(self._config, "asyncanalyze") # INTERNAL CLASSES class Version: + """ + Represents a version of the Deepgram API. + """ + + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions + _parent: str + def __init__(self, config, parent: str): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - self.parent = parent + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + self._parent = parent # FUTURE VERSIONING: # When v2 or v1.1beta1 or etc. This allows easy access to the latest version of the API. # @property # def latest(self): - # match self.parent: + # match self._parent: # case "analyze": - # return AnalyzeClient(self.config) + # return AnalyzeClient(self._config) # case _: # raise DeepgramModuleError("Invalid parent") def v(self, version: str = ""): - self.logger.debug("Version.v ENTER") - self.logger.info("version: %s", version) + """ + Returns a specific version of the Deepgram API. + """ + self._logger.debug("Version.v ENTER") + self._logger.info("version: %s", version) if len(version) == 0: - self.logger.error("version is empty") - self.logger.debug("Version.v LEAVE") + self._logger.error("version is empty") + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Invalid module version") parent = "" - fileName = "" - className = "" - match self.parent: + file_name = "" + class_name = "" + match self._parent: case "analyze": parent = "analyze" - fileName = "client" - className = "AnalyzeClient" + file_name = "client" + class_name = "AnalyzeClient" case "asyncanalyze": parent = "analyze" - fileName = "async_client" - className = "AsyncAnalyzeClient" + file_name = "async_client" + class_name = "AsyncAnalyzeClient" case _: - self.logger.error("parent unknown: %s", self.parent) - self.logger.debug("Version.v LEAVE") + self._logger.error("parent unknown: %s", self._parent) + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Invalid parent type") # create class path - path = f"deepgram.clients.{parent}.v{version}.{fileName}" - self.logger.info("path: %s", path) - self.logger.info("className: %s", className) + path = f"deepgram.clients.{parent}.v{version}.{file_name}" + self._logger.info("path: %s", path) + self._logger.info("class_name: %s", class_name) # import class mod = import_module(path) if mod is None: - self.logger.error("module path is None") - self.logger.debug("Version.v LEAVE") + self._logger.error("module path is None") + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Unable to find package") - my_class = getattr(mod, className) + my_class = getattr(mod, class_name) if my_class is None: - self.logger.error("my_class is None") - self.logger.debug("Version.v LEAVE") + self._logger.error("my_class is None") + self._logger.debug("Version.v LEAVE") raise DeepgramModuleError("Unable to find class") # instantiate class - myClass = my_class(self.config) - self.logger.notice("Version.v succeeded") - self.logger.debug("Version.v LEAVE") - return myClass + my_class = my_class(self._config) + self._logger.notice("Version.v succeeded") + self._logger.debug("Version.v LEAVE") + return my_class diff --git a/deepgram/clients/speak/__init__.py b/deepgram/clients/speak/__init__.py index cf3c6502..522dbefe 100644 --- a/deepgram/clients/speak/__init__.py +++ b/deepgram/clients/speak/__init__.py @@ -7,9 +7,9 @@ from .client import SpeakOptions from .client import SpeakResponse from .client import ( - SpeakSource, - TextSource, + FileSource, SpeakStreamSource, + SpeakSource, ) -from ...options import DeepgramClientOptions +from ...options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/speak/client.py b/deepgram/clients/speak/client.py index 3af64d1b..e0dda23c 100644 --- a/deepgram/clients/speak/client.py +++ b/deepgram/clients/speak/client.py @@ -6,70 +6,26 @@ from .v1.async_client import AsyncSpeakClient as AsyncSpeakClientLatest from .v1.options import ( SpeakOptions as SpeakOptionsLatest, - SpeakSource as SpeakSourceLatest, - TextSource as TextSourceLatest, + FileSource as FileSourceLatest, SpeakStreamSource as SpeakStreamSourceLatest, + SpeakSource as SpeakSourceLatest, ) from .v1.response import SpeakResponse as SpeakResponseLatest -""" -The client.py points to the current supported version in the SDK. -Older versions are supported in the SDK for backwards compatibility. -""" +# The client.py points to the current supported version in the SDK. +# Older versions are supported in the SDK for backwards compatibility. # input -class SpeakOptions(SpeakOptionsLatest): - """ - Please see SpeakOptionsLatest for details - """ - - pass - - -class TextSource(TextSourceLatest): - """ - Please see TextSourceLatest for details - """ - - pass - - -class SpeakStreamSource(SpeakStreamSourceLatest): - """ - Please see SpeakStreamSourceLatest for details - """ - - pass +SpeakOptions = SpeakOptionsLatest +SpeakStreamSource = SpeakStreamSourceLatest +FileSource = FileSourceLatest SpeakSource = SpeakSourceLatest # output -class SpeakResponse(SpeakResponseLatest): - """ - Please see SpeakResponseLatest for details - """ - - pass - - -# clients -class SpeakClient(SpeakClientLatest): - """ - Please see SpeakClientLatest for details - """ - - def __init__(self, config): - self.config = config - super().__init__(config) - - -class AsyncSpeakClient(AsyncSpeakClientLatest): - """ - Please see AsyncSpeakClientLatest for details - """ - - def __init__(self, config): - super().__init__(config) +SpeakResponse = SpeakResponseLatest +SpeakClient = SpeakClientLatest +AsyncSpeakClient = AsyncSpeakClientLatest diff --git a/deepgram/clients/speak/v1/__init__.py b/deepgram/clients/speak/v1/__init__.py index 7b077800..42090b8e 100644 --- a/deepgram/clients/speak/v1/__init__.py +++ b/deepgram/clients/speak/v1/__init__.py @@ -4,6 +4,7 @@ from .client import SpeakClient from .async_client import AsyncSpeakClient -from .options import SpeakOptions, SpeakSource, TextSource, SpeakStreamSource +from .options import SpeakOptions, FileSource, SpeakStreamSource, SpeakSource from .response import SpeakResponse -from ....options import DeepgramClientOptions + +from ....options import DeepgramClientOptions, ClientOptionsFromEnv diff --git a/deepgram/clients/speak/v1/async_client.py b/deepgram/clients/speak/v1/async_client.py index abfd2281..6ebbb292 100644 --- a/deepgram/clients/speak/v1/async_client.py +++ b/deepgram/clients/speak/v1/async_client.py @@ -2,17 +2,20 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import httpx -import logging, verboselogs -import json -from typing import Dict, Union, Optional +import logging +from typing import Dict, Union, Optional, cast +import io import aiofiles +import httpx + +from deepgram.utils import verboselogs +from ....options import DeepgramClientOptions from ...abstract_async_client import AbstractAsyncRestClient from ..errors import DeepgramError, DeepgramTypeError -from .helpers import is_text_source -from .options import SpeakOptions, SpeakSource, TextSource, SpeakStreamSource +from .helpers import is_text_source +from .options import SpeakOptions, FileSource from .response import SpeakResponse @@ -22,62 +25,67 @@ class AsyncSpeakClient(AbstractAsyncRestClient): Provides methods for speaking from text. """ - def __init__(self, config): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - super().__init__(config) - - """ - Speak from a text source. - - Args: - source (TextSource): The text source to speak. - options (SpeakOptions): Additional options for the ingest (default is None). - endpoint (str): The API endpoint for the ingest (default is "v1/speak"). + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions - Returns: - SpeakResponse: The response from the speak request. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ + def __init__(self, config: DeepgramClientOptions): + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + super().__init__(config) async def stream( self, - source: SpeakSource, + source: FileSource, options: Optional[Union[Dict, SpeakOptions]] = None, addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/speak", ) -> SpeakResponse: - self.logger.debug("AsyncSpeakClient.stream ENTER") - - url = f"{self.config.url}/{endpoint}" + """ + Speak from a text source and store in memory. + + Args: + source (TextSource): The text source to speak. + options (SpeakOptions): Additional options for the ingest (default is None). + addons (Dict): Additional options for the request (default is None). + headers (Dict): Additional headers for the request (default is None). + timeout (httpx.Timeout): The timeout for the request (default is None). + endpoint (str): The endpoint to use for the request (default is "v1/speak"). + + Returns: + SpeakResponse: The response from the speak request. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AsyncSpeakClient.stream ENTER") + + url = f"{self._config.url}/{endpoint}" if is_text_source(source): body = source else: - self.logger.error("Unknown speak source type") - self.logger.debug("AsyncSpeakClient.stream LEAVE") + self._logger.error("Unknown speak source type") + self._logger.debug("AsyncSpeakClient.stream LEAVE") raise DeepgramTypeError("Unknown speak source type") if isinstance(options, SpeakOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("AsyncSpeakClient.stream LEAVE") + self._logger.error("options.check failed") + self._logger.debug("AsyncSpeakClient.stream LEAVE") raise DeepgramError("Fatal speak options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, SpeakOptions): - self.logger.info("SpeakOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("SpeakOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) - returnVals = [ + return_vals = [ "content-type", "request-id", "model-uuid", @@ -93,46 +101,73 @@ async def stream( headers=headers, json=body, timeout=timeout, - file_result=returnVals, + file_result=return_vals, ) - self.logger.info("result: %s", result) - sResp = SpeakResponse( - content_type=result["content-type"], - request_id=result["request-id"], - model_uuid=result["model-uuid"], - model_name=result["model-name"], - characters=result["char-count"], - transfer_encoding=result["transfer-encoding"], - date=result["date"], - stream=result["stream"], + self._logger.info("result: %s", result) + resp = SpeakResponse( + content_type=str(result["content-type"]), + request_id=str(result["request-id"]), + model_uuid=str(result["model-uuid"]), + model_name=str(result["model-name"]), + characters=int(str(result["char-count"])), + transfer_encoding=str(result["transfer-encoding"]), + date=str(result["date"]), + stream=cast(io.BytesIO, result["stream"]), ) - self.logger.verbose("result: %s", sResp) - self.logger.notice("speak succeeded") - self.logger.debug("AsyncSpeakClient.stream LEAVE") - return sResp + self._logger.verbose("result: %s", resp) + self._logger.notice("speak succeeded") + self._logger.debug("AsyncSpeakClient.stream LEAVE") + return resp async def file( self, filename: str, - source: SpeakSource, + source: FileSource, options: Optional[Union[Dict, SpeakOptions]] = None, addons: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/speak", ) -> SpeakResponse: - return await self.save(filename, source, options, addons, timeout, endpoint) + """ + Speak from a text source and save to a file. + """ + return await self.save( + filename, + source, + options=options, + addons=addons, + timeout=timeout, + endpoint=endpoint, + ) async def save( self, filename: str, - source: SpeakSource, + source: FileSource, options: Optional[Union[Dict, SpeakOptions]] = None, addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/speak", ) -> SpeakResponse: - self.logger.debug("AsyncSpeakClient.save ENTER") + """ + Speak from a text source and save to a file. + + Args: + source (TextSource): The text source to speak. + options (SpeakOptions): Additional options for the ingest (default is None). + addons (Dict): Additional options for the request (default is None). + headers (Dict): Additional headers for the request (default is None). + timeout (httpx.Timeout): The timeout for the request (default is None). + endpoint (str): The endpoint to use for the request (default is "v1/speak"). + + Returns: + SpeakResponse: The response from the speak request. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("AsyncSpeakClient.save ENTER") res = await self.stream( source, @@ -143,6 +178,11 @@ async def save( endpoint=endpoint, ) + if res.stream is None: + self._logger.error("stream is None") + self._logger.debug("AsyncSpeakClient.save LEAVE") + raise DeepgramError("BytesIO stream is None") + # save to file async with aiofiles.open(filename, "wb") as out: await out.write(res.stream.getbuffer()) @@ -152,5 +192,5 @@ async def save( res.stream = None res.filename = filename - self.logger.debug("AsyncSpeakClient.save LEAVE") + self._logger.debug("AsyncSpeakClient.save LEAVE") return res diff --git a/deepgram/clients/speak/v1/client.py b/deepgram/clients/speak/v1/client.py index e2442980..4ad5113d 100644 --- a/deepgram/clients/speak/v1/client.py +++ b/deepgram/clients/speak/v1/client.py @@ -2,16 +2,19 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +import logging +from typing import Dict, Union, Optional, cast +import io + import httpx -import logging, verboselogs -import json -from typing import Dict, Union, Optional +from deepgram.utils import verboselogs +from ....options import DeepgramClientOptions from ...abstract_sync_client import AbstractSyncRestClient from ..errors import DeepgramError, DeepgramTypeError from .helpers import is_text_source -from .options import SpeakOptions, SpeakSource, TextSource, SpeakStreamSource +from .options import SpeakOptions, FileSource from .response import SpeakResponse @@ -21,62 +24,67 @@ class SpeakClient(AbstractSyncRestClient): Provides methods for speaking from text. """ - def __init__(self, config): - self.logger = logging.getLogger(__name__) - self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(config.verbose) - self.config = config - super().__init__(config) - - """ - Speak from a text source. - - Args: - source (TextSource): The text source to speak. - options (SpeakOptions): Additional options for the ingest (default is None). - endpoint (str): The API endpoint for the ingest (default is "v1/speak"). + _logger: verboselogs.VerboseLogger + _config: DeepgramClientOptions - Returns: - SpeakResponse: The response from the speak request. - - Raises: - DeepgramTypeError: Raised for known API errors. - """ + def __init__(self, config: DeepgramClientOptions): + self._logger = verboselogs.VerboseLogger(__name__) + self._logger.addHandler(logging.StreamHandler()) + self._logger.setLevel(config.verbose) + self._config = config + super().__init__(config) def stream( self, - source: SpeakSource, + source: FileSource, options: Optional[Union[Dict, SpeakOptions]] = None, addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/speak", ) -> SpeakResponse: - self.logger.debug("SpeakClient.stream ENTER") - - url = f"{self.config.url}/{endpoint}" + """ + Speak from a text source and store in memory. + + Args: + source (TextSource): The text source to speak. + options (SpeakOptions): Additional options for the ingest (default is None). + addons (Dict): Additional options for the request (default is None). + headers (Dict): Additional headers for the request (default is None). + timeout (httpx.Timeout): The timeout for the request (default is None). + endpoint (str): The endpoint to use for the request (default is "v1/speak"). + + Returns: + SpeakResponse: The response from the speak request. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("SpeakClient.stream ENTER") + + url = f"{self._config.url}/{endpoint}" if is_text_source(source): body = source else: - self.logger.error("Unknown speak source type") - self.logger.debug("SpeakClient.stream LEAVE") + self._logger.error("Unknown speak source type") + self._logger.debug("SpeakClient.stream LEAVE") raise DeepgramTypeError("Unknown speak source type") if isinstance(options, SpeakOptions) and not options.check(): - self.logger.error("options.check failed") - self.logger.debug("SpeakClient.stream LEAVE") + self._logger.error("options.check failed") + self._logger.debug("SpeakClient.stream LEAVE") raise DeepgramError("Fatal speak options error") - self.logger.info("url: %s", url) - self.logger.info("source: %s", source) + self._logger.info("url: %s", url) + self._logger.info("source: %s", source) if isinstance(options, SpeakOptions): - self.logger.info("SpeakOptions switching class -> json") - options = json.loads(options.to_json()) - self.logger.info("options: %s", options) - self.logger.info("addons: %s", addons) - self.logger.info("headers: %s", headers) + self._logger.info("SpeakOptions switching class -> dict") + options = options.to_dict() + self._logger.info("options: %s", options) + self._logger.info("addons: %s", addons) + self._logger.info("headers: %s", headers) - returnVals = [ + return_vals = [ "content-type", "request-id", "model-uuid", @@ -92,46 +100,74 @@ def stream( headers=headers, json=body, timeout=timeout, - file_result=returnVals, + file_result=return_vals, ) - self.logger.info("result: %s", result) - sResp = SpeakResponse( - content_type=result["content-type"], - request_id=result["request-id"], - model_uuid=result["model-uuid"], - model_name=result["model-name"], - characters=result["char-count"], - transfer_encoding=result["transfer-encoding"], - date=result["date"], - stream=result["stream"], + + self._logger.info("result: %s", result) + resp = SpeakResponse( + content_type=str(result["content-type"]), + request_id=str(result["request-id"]), + model_uuid=str(result["model-uuid"]), + model_name=str(result["model-name"]), + characters=int(str(result["char-count"])), + transfer_encoding=str(result["transfer-encoding"]), + date=str(result["date"]), + stream=cast(io.BytesIO, result["stream"]), ) - self.logger.verbose("result: %s", sResp) - self.logger.notice("speak succeeded") - self.logger.debug("SpeakClient.stream LEAVE") - return sResp + self._logger.verbose("result: %s", resp) + self._logger.notice("speak succeeded") + self._logger.debug("SpeakClient.stream LEAVE") + return resp async def file( self, filename: str, - source: SpeakSource, + source: FileSource, options: Optional[Union[Dict, SpeakOptions]] = None, addons: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/speak", ) -> SpeakResponse: - return self.save(filename, source, options, addons, timeout, endpoint) + """ + Speak from a text source and save to a file. + """ + return self.save( + filename, + source, + options=options, + addons=addons, + timeout=timeout, + endpoint=endpoint, + ) def save( self, filename: str, - source: SpeakSource, + source: FileSource, options: Optional[Union[Dict, SpeakOptions]] = None, addons: Optional[Dict] = None, headers: Optional[Dict] = None, timeout: Optional[httpx.Timeout] = None, endpoint: str = "v1/speak", ) -> SpeakResponse: - self.logger.debug("SpeakClient.save ENTER") + """ + Speak from a text source and save to a file. + + Args: + source (TextSource): The text source to speak. + options (SpeakOptions): Additional options for the ingest (default is None). + addons (Dict): Additional options for the request (default is None). + headers (Dict): Additional headers for the request (default is None). + timeout (httpx.Timeout): The timeout for the request (default is None). + endpoint (str): The endpoint to use for the request (default is "v1/speak"). + + Returns: + SpeakResponse: The response from the speak request. + + Raises: + DeepgramTypeError: Raised for known API errors. + """ + self._logger.debug("SpeakClient.save ENTER") res = self.stream( source, @@ -142,15 +178,19 @@ def save( endpoint=endpoint, ) + if res.stream is None: + self._logger.error("stream is None") + self._logger.debug("SpeakClient.save LEAVE") + raise DeepgramError("BytesIO stream is None") + # save to file - file = open(filename, "wb+") - file.write(res.stream.getbuffer()) - file.flush() - file.close() + with open(filename, "wb+") as file: + file.write(res.stream.getbuffer()) + file.flush() # add filename to response res.stream = None res.filename = filename - self.logger.debug("SpeakClient.save LEAVE") + self._logger.debug("SpeakClient.save LEAVE") return res diff --git a/deepgram/clients/speak/v1/helpers.py b/deepgram/clients/speak/v1/helpers.py index 3073965b..3232c1e7 100644 --- a/deepgram/clients/speak/v1/helpers.py +++ b/deepgram/clients/speak/v1/helpers.py @@ -6,8 +6,14 @@ def is_text_source(provided_source: SpeakSource) -> bool: + """ + Check if the provided source is a text source. + """ return "text" in provided_source def is_readstream_source(provided_source: SpeakSource) -> bool: + """ + Check if the provided source is a readstream source. + """ return "stream" in provided_source diff --git a/deepgram/clients/speak/v1/options.py b/deepgram/clients/speak/v1/options.py index 479a22ea..f46e0252 100644 --- a/deepgram/clients/speak/v1/options.py +++ b/deepgram/clients/speak/v1/options.py @@ -2,17 +2,19 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT +from io import BufferedReader +from typing import Union, Optional +import logging + from dataclasses import dataclass, field -from dataclasses_json import dataclass_json, config +from dataclasses_json import config as dataclass_config, DataClassJsonMixin -from io import BufferedReader -from typing import Union, List, TypedDict, Optional -import logging, verboselogs +from deepgram.utils import verboselogs +from ...common import FileSource -@dataclass_json @dataclass -class SpeakOptions: +class SpeakOptions(DataClassJsonMixin): """ Contains all the options for the SpeakOptions. @@ -21,19 +23,20 @@ class SpeakOptions: """ model: Optional[str] = field( - default="aura-asteria-en", metadata=config(exclude=lambda f: f is None) + default="aura-asteria-en", + metadata=dataclass_config(exclude=lambda f: f is None), ) encoding: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) container: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) sample_rate: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) bit_rate: Optional[int] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -47,11 +50,13 @@ def __str__(self) -> str: return self.to_json(indent=4) def check(self): - verboselogs.install() - logger = logging.getLogger(__name__) + """ + Check the SpeakOptions for any missing or invalid values. + """ + logger = verboselogs.VerboseLogger(__name__) logger.addHandler(logging.StreamHandler()) prev = logger.level - logger.setLevel(logging.ERROR) + logger.setLevel(verboselogs.ERROR) # no op at the moment @@ -60,31 +65,5 @@ def check(self): return True -class SpeakStreamSource(TypedDict): - """ - Represents a data source for reading binary data from a stream-like source. - - This class is used to specify a source of binary data that can be read from - a stream, such as an audio file in .wav format. - - Attributes: - stream (BufferedReader): A BufferedReader object for reading binary data. - """ - - stream: BufferedReader - - -class TextSource(TypedDict): - """ - Represents a data source for reading binary data from a text-like source. - - This class is used to specify a source of text data that can be read from. - - Attributes: - text (str): A string for reading text data. - """ - - text: str - - -SpeakSource = Union[TextSource, SpeakStreamSource] +SpeakStreamSource = BufferedReader +SpeakSource = Union[FileSource, SpeakStreamSource] diff --git a/deepgram/clients/speak/v1/response.py b/deepgram/clients/speak/v1/response.py index a11d4a74..182f51a5 100644 --- a/deepgram/clients/speak/v1/response.py +++ b/deepgram/clients/speak/v1/response.py @@ -2,30 +2,34 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -from dataclasses import dataclass, field -from dataclasses_json import config, dataclass_json -from typing import List, Optional, TypedDict, Dict +from typing import Optional import io +from dataclasses import dataclass, field +from dataclasses_json import config as dataclass_config, DataClassJsonMixin + # Speak Response Types: -@dataclass_json @dataclass -class SpeakResponse: +class SpeakResponse(DataClassJsonMixin): # pylint: disable=too-many-instance-attributes + """ + A class for representing a response from the speak endpoint. + """ + content_type: str = "" request_id: str = "" model_uuid: str = "" model_name: str = "" - characters: str = 0 + characters: int = 0 transfer_encoding: str = "" date: str = "" stream: Optional[io.BytesIO] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) filename: Optional[str] = field( - default=None, metadata=config(exclude=lambda f: f is None) + default=None, metadata=dataclass_config(exclude=lambda f: f is None) ) def __getitem__(self, key): @@ -38,5 +42,5 @@ def __setitem__(self, key, val): # this is a hack to make the response look like a dict because of the io.BytesIO object # otherwise it will throw an exception on printing def __str__(self) -> str: - myDict = self.to_dict() - return myDict.__str__() + my_dict = self.to_dict() + return my_dict.__str__() diff --git a/deepgram/options.py b/deepgram/options.py index 8278f5d9..03a8b569 100644 --- a/deepgram/options.py +++ b/deepgram/options.py @@ -2,13 +2,15 @@ # Use of this source code is governed by a MIT license that can be found in the LICENSE file. # SPDX-License-Identifier: MIT -import logging, verboselogs -from typing import Dict, Optional -from .errors import DeepgramApiKeyError -from deepgram import __version__ import sys import re import os +from typing import Dict, Optional +import logging + +from deepgram import __version__ +from deepgram.utils import verboselogs +from .errors import DeepgramApiKeyError class DeepgramClientOptions: @@ -19,8 +21,8 @@ class DeepgramClientOptions: Attributes: api_key: (Optional) A Deepgram API key used for authentication. Default uses the `DEEPGRAM_API_KEY` environment variable. - verbose: (Optional) The logging level for the client. Defaults to `logging.WARNING`. url: (Optional) The URL used to interact with production, On-prem, and other Deepgram environments. Defaults to `api.deepgram.com`. + verbose: (Optional) The logging level for the client. Defaults to `verboselogs.WARNING`. headers: (Optional) Headers for initializing the client. options: (Optional) Additional options for initializing the client. """ @@ -29,14 +31,16 @@ def __init__( self, api_key: str = "", url: str = "", - verbose: int = logging.WARNING, + verbose: int = verboselogs.WARNING, headers: Optional[Dict] = None, options: Optional[Dict] = None, ): - verboselogs.install() - self.logger = logging.getLogger(__name__) + self.logger = verboselogs.VerboseLogger(__name__) self.logger.addHandler(logging.StreamHandler()) + if api_key is None: + api_key = "" + self.verbose = verbose self.api_key = api_key self._update_headers(headers=headers) @@ -46,10 +50,16 @@ def __init__( self.url = self._get_url(url) if options is None: - options = dict() + options = {} self.options = options def set_apikey(self, api_key: str): + """ + set_apikey: Sets the API key for the client. + + Args: + api_key: The Deepgram API key used for authentication. + """ self.api_key = api_key self._update_headers() @@ -59,8 +69,7 @@ def _get_url(self, url): return url.strip("/") def _update_headers(self, headers: Optional[Dict] = None): - if not hasattr(self, "headers") or self.headers is None: - self.headers = {} + self.headers = {} self.headers["Accept"] = "application/json" if self.api_key: self.headers["Authorization"] = f"Token {self.api_key}" @@ -74,19 +83,27 @@ def _update_headers(self, headers: Optional[Dict] = None): self.headers.update(headers) -class ClientOptionsFromEnv(DeepgramClientOptions): +class ClientOptionsFromEnv( + DeepgramClientOptions +): # pylint: disable=too-many-branches, too-many-statements + """ + This class extends DeepgramClientOptions and will attempt to use environment variables first before defaults. + """ + def __init__( self, api_key: str = "", url: str = "", - verbose: int = logging.WARNING, + verbose: int = verboselogs.WARNING, headers: Optional[Dict] = None, options: Optional[Dict] = None, ): - verboselogs.install() - self.logger = logging.getLogger(__name__) + self.logger = verboselogs.VerboseLogger(__name__) self.logger.addHandler(logging.StreamHandler()) - self.logger.setLevel(logging.WARNING) # temporary set for setup + self.logger.setLevel(verboselogs.WARNING) # temporary set for setup + + if api_key is None: + api_key = "" if api_key == "": api_key = os.getenv("DEEPGRAM_API_KEY", "") @@ -98,52 +115,54 @@ def __init__( url = os.getenv("DEEPGRAM_HOST", "api.deepgram.com") self.logger.notice(f"Deepgram host is set to {url}") - if verbose == logging.WARNING: + if verbose == verboselogs.WARNING: _loglevel = os.getenv("DEEPGRAM_LOGGING", "") if _loglevel != "": verbose = int(_loglevel) - if type(verbose) != int: + if isinstance(verbose, str): match verbose: case "NOTSET": self.logger.notice("Logging level is set to NOTSET") - verbose = logging.NOTSET + verbose = verboselogs.NOTSET case "SPAM": self.logger.notice("Logging level is set to SPAM") - verbose = logging.SPAM + verbose = verboselogs.SPAM case "DEBUG": self.logger.notice("Logging level is set to DEBUG") - verbose = logging.DEBUG + verbose = verboselogs.DEBUG case "VERBOSE": self.logger.notice("Logging level is set to VERBOSE") - verbose = logging.VERBOSE + verbose = verboselogs.VERBOSE case "NOTICE": self.logger.notice("Logging level is set to NOTICE") - verbose = logging.NOTICE + verbose = verboselogs.NOTICE case "WARNING": self.logger.notice("Logging level is set to WARNING") - verbose = logging.WARNING + verbose = verboselogs.WARNING case "SUCCESS": self.logger.notice("Logging level is set to SUCCESS") - verbose = logging.SUCCESS + verbose = verboselogs.SUCCESS case "ERROR": self.logger.notice("Logging level is set to ERROR") - verbose = logging.ERROR + verbose = verboselogs.ERROR case "CRITICAL": self.logger.notice("Logging level is set to CRITICAL") - verbose = logging.CRITICAL + verbose = verboselogs.CRITICAL case _: self.logger.notice("Logging level is set to WARNING") - verbose = logging.WARNING + verbose = verboselogs.WARNING self.logger.notice(f"Logging level is set to {verbose}") if headers is None: - headers = dict() + headers = {} for x in range(0, 20): header = os.getenv(f"DEEPGRAM_HEADER_{x}", None) if header is not None: headers[header] = os.getenv(f"DEEPGRAM_HEADER_VALUE_{x}", None) self.logger.debug( - f"Deepgram header {header} is set with value {headers[header]}" + "Deepgram header %s is set with value %s", + header, + headers[header], ) else: break @@ -152,13 +171,13 @@ def __init__( headers = None if options is None: - options = dict() + options = {} for x in range(0, 20): param = os.getenv(f"DEEPGRAM_PARAM_{x}", None) if param is not None: options[param] = os.getenv(f"DEEPGRAM_PARAM_VALUE_{x}", None) self.logger.debug( - f"Deepgram option {param} is set with value {options[param]}" + "Deepgram option %s is set with value %s", param, options[param] ) else: break diff --git a/deepgram/utils/verboselogs/__init__.py b/deepgram/utils/verboselogs/__init__.py new file mode 100644 index 00000000..c7d39326 --- /dev/null +++ b/deepgram/utils/verboselogs/__init__.py @@ -0,0 +1,168 @@ +# Copyright 2024 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 + +""" +Custom log levels for Python's :mod:`logging` module. + +The :mod:`verboselogs` module defines the :data:`NOTICE`, :data:`SPAM`, +:data:`SUCCESS` and :data:`VERBOSE` constants, the :class:`VerboseLogger` class +and the :func:`add_log_level()` and :func:`install()` functions. + +At import time :func:`add_log_level()` is used to register the custom log +levels :data:`NOTICE`, :data:`SPAM`, :data:`SUCCESS` and :data:`VERBOSE` with +Python's :mod:`logging` module. +""" + +import logging + +__version__ = "1.8" +"""Semi-standard module versioning.""" + +NOTICE = 25 +""" +The numeric value of the 'notice' log level (a number). + +The value of :data:`NOTICE` positions the notice log level between the +:data:`~verboselogs.WARNING` and :data:`~verboselogs.INFO` levels. + +:see also: The :func:`~VerboseLogger.notice()` method of the + :class:`VerboseLogger` class. +""" + +SPAM = 5 +""" +The numeric value of the 'spam' log level (a number). + +The value of :data:`SPAM` positions the spam log level between the +:data:`~verboselogs.DEBUG` and :data:`~verboselogs.NOTSET` levels. + +:see also: The :func:`~VerboseLogger.spam()` method of the + :class:`VerboseLogger` class. +""" + +SUCCESS = 35 +""" +The numeric value of the 'success' log level (a number). + +The value of :data:`SUCCESS` positions the success log level between the +:data:`~verboselogs.WARNING` and :data:`~verboselogs.ERROR` levels. + +:see also: The :func:`~VerboseLogger.success()` method of the + :class:`VerboseLogger` class. +""" + +VERBOSE = 15 +""" +The numeric value of the 'verbose' log level (a number). + +The value of :data:`VERBOSE` positions the verbose log level between the +:data:`~verboselogs.INFO` and :data:`~verboselogs.DEBUG` levels. + +:see also: The :func:`~VerboseLogger.verbose()` method of the + :class:`VerboseLogger` class. +""" + + +def install(): + """ + Make :class:`VerboseLogger` the default logger class. + + The :func:`install()` function uses :func:`~logging.setLoggerClass()` to + configure :class:`VerboseLogger` as the default class for all loggers + created by :func:`logging.getLogger()` after :func:`install()` has been + called. Here's how it works: + + .. code-block:: python + + import logging + import verboselogs + + verboselogs.install() + logger = logging.getLogger(__name__) # will be a VerboseLogger instance + """ + logging.setLoggerClass(VerboseLogger) + + +def add_log_level(value, name): + """ + Add a new log level to the :mod:`logging` module. + + :param value: The log level's number (an integer). + :param name: The name for the log level (a string). + """ + logging.addLevelName(value, name) + setattr(logging, name, value) + + +WARNING = logging.WARNING +ERROR = logging.ERROR +FATAL = logging.FATAL +CRITICAL = logging.CRITICAL +INFO = logging.INFO +DEBUG = logging.DEBUG +NOTSET = logging.NOTSET + +# Define the NOTICE log level. +add_log_level(NOTICE, "NOTICE") + +# Define the SPAM log level. +add_log_level(SPAM, "SPAM") + +# Define the SUCCESS log level. +add_log_level(SUCCESS, "SUCCESS") + +# Define the VERBOSE log level. +add_log_level(VERBOSE, "VERBOSE") + + +class VerboseLogger(logging.Logger): + """ + Custom logger class to support the additional logging levels. + + This subclass of :class:`verboselogs.Logger` adds support for the additional + logging methods :func:`notice()`, :func:`spam()`, :func:`success()` and + :func:`verbose()`. + + You can use :func:`verboselogs.install()` to make :class:`VerboseLogger` + the default logger class. + """ + + def __init__(self, *args, **kw): + """ + Initialize a :class:`VerboseLogger` object. + + :param args: Refer to the superclass (:class:`verboselogs.Logger`). + :param kw: Refer to the superclass (:class:`verboselogs.Logger`). + + This method first initializes the superclass and then it sets the root + logger as the parent of this logger. + + The function :func:`logging.getLogger()` is normally responsible for + defining the hierarchy of logger objects however because verbose + loggers can be created by calling the :class:`VerboseLogger` + constructor, we're responsible for defining the parent relationship + ourselves. + """ + logging.Logger.__init__(self, *args, **kw) + self.parent = logging.getLogger() + + def notice(self, msg, *args, **kw): + """Log a message with level :data:`NOTICE`. The arguments are interpreted as for :func:`logging.debug()`.""" + if self.isEnabledFor(NOTICE): + self._log(NOTICE, msg, args, **kw) + + def spam(self, msg, *args, **kw): + """Log a message with level :data:`SPAM`. The arguments are interpreted as for :func:`logging.debug()`.""" + if self.isEnabledFor(SPAM): + self._log(SPAM, msg, args, **kw) + + def success(self, msg, *args, **kw): + """Log a message with level :data:`SUCCESS`. The arguments are interpreted as for :func:`logging.debug()`.""" + if self.isEnabledFor(SUCCESS): + self._log(SUCCESS, msg, args, **kw) + + def verbose(self, msg, *args, **kw): + """Log a message with level :data:`VERBOSE`. The arguments are interpreted as for :func:`logging.debug()`.""" + if self.isEnabledFor(VERBOSE): + self._log(VERBOSE, msg, args, **kw) diff --git a/examples/advanced/prerecorded/direct_invocation/main.py b/examples/advanced/prerecorded/direct_invocation/main.py index 90bde6ed..d6b88b4a 100644 --- a/examples/advanced/prerecorded/direct_invocation/main.py +++ b/examples/advanced/prerecorded/direct_invocation/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs import traceback from deepgram import ClientOptionsFromEnv, PrerecordedOptions, PreRecordedClient @@ -12,7 +13,7 @@ load_dotenv() AUDIO_URL = { - "url": "https://static.deepgram.com/examples/Bueller-Life-moves-pretty-fast.wav" + "url": "https://static.deepgram.com/examples-tmp/Bueller-Life-moves-pretty-fast.wav" } @@ -37,7 +38,8 @@ def main(): except Exception as e: print(f"Exception: {e}") - traceback.print_exc() + # enable the following line to print the stack trace + # traceback.print_exc() if __name__ == "__main__": diff --git a/examples/advanced/streaming/direct_invocation/main.py b/examples/advanced/streaming/direct_invocation/main.py index b90cc899..3cb6c947 100644 --- a/examples/advanced/streaming/direct_invocation/main.py +++ b/examples/advanced/streaming/direct_invocation/main.py @@ -4,7 +4,8 @@ import httpx from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs import threading from deepgram import ( @@ -24,7 +25,7 @@ def main(): try: # STEP 1 Create a Deepgram LiveClient using a specific config # config: ClientOptionsFromEnv = ClientOptionsFromEnv( - # verbose=logging.DEBUG, options={"keepalive": "true"} + # verbose=verboselogs.DEBUG, options={"keepalive": "true"} # ) # liveClient: LiveClient = LiveClient("", config) # OR just use the default config diff --git a/examples/advanced/streaming/microphone_inheritance/main.py b/examples/advanced/streaming/microphone_inheritance/main.py index 40649fb4..bc05e397 100644 --- a/examples/advanced/streaming/microphone_inheritance/main.py +++ b/examples/advanced/streaming/microphone_inheritance/main.py @@ -3,7 +3,8 @@ # SPDX-License-Identifier: MIT from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from time import sleep from deepgram import ( @@ -70,7 +71,7 @@ def main(): try: # example of setting up a client config. logging values: WARNING, VERBOSE, DEBUG, SPAM # config: ClientOptionsFromEnv = ClientOptionsFromEnv( - # verbose=logging.DEBUG, + # verbose=verboselogs.DEBUG, # options={"keepalive": "true"} # ) # liveClient: MyLiveClient = MyLiveClient(config) diff --git a/examples/advanced/streaming/mute-microphone/main.py b/examples/advanced/streaming/mute-microphone/main.py index e03ab457..86e9a2b6 100644 --- a/examples/advanced/streaming/mute-microphone/main.py +++ b/examples/advanced/streaming/mute-microphone/main.py @@ -3,7 +3,8 @@ # SPDX-License-Identifier: MIT from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from time import sleep from deepgram import ( @@ -21,7 +22,7 @@ def main(): try: # example of setting up a client config. logging values: WARNING, VERBOSE, DEBUG, SPAM # config = DeepgramClientOptions( - # verbose=logging.DEBUG, options={"keepalive": "true"} + # verbose=verboselogs.DEBUG, options={"keepalive": "true"} # ) # deepgram: DeepgramClient = DeepgramClient("", config) # otherwise, use default config @@ -39,9 +40,7 @@ def on_message(self, result, **kwargs): sentence = result.channel.alternatives[0].transcript if len(sentence) == 0: return - microphone.mute() print(f"speaker: {sentence}") - microphone.unmute() def on_metadata(self, metadata, **kwargs): print(f"\n\n{metadata}\n\n") diff --git a/examples/analyze/intent/main.py b/examples/analyze/intent/main.py index 2cb8c267..e3b691e4 100644 --- a/examples/analyze/intent/main.py +++ b/examples/analyze/intent/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables # config: DeepgramClientOptions = DeepgramClientOptions( - # verbose=logging.SPAM, + # verbose=verboselogs.SPAM, # ) # deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/analyze/legacy_dict_intent/main.py b/examples/analyze/legacy_dict_intent/main.py index 98b0e9f4..d0487d1f 100644 --- a/examples/analyze/legacy_dict_intent/main.py +++ b/examples/analyze/legacy_dict_intent/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables # config = DeepgramClientOptions( - # verbose=logging.SPAM, + # verbose=verboselogs.SPAM, # ) # deepgram = DeepgramClient("", config) # OR use defaults diff --git a/examples/analyze/sentiment/main.py b/examples/analyze/sentiment/main.py index 87fa4d09..0375a8f4 100644 --- a/examples/analyze/sentiment/main.py +++ b/examples/analyze/sentiment/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables # config: DeepgramClientOptions = DeepgramClientOptions( - # verbose=logging.SPAM, + # verbose=verboselogs.SPAM, # ) # deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/analyze/stream_intent/main.py b/examples/analyze/stream_intent/main.py index 9477611b..6e9316d5 100644 --- a/examples/analyze/stream_intent/main.py +++ b/examples/analyze/stream_intent/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from io import BufferedReader from deepgram import DeepgramClientOptions @@ -26,7 +27,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables config = DeepgramClientOptions( - verbose=logging.SPAM, + verbose=verboselogs.SPAM, ) deepgram = DeepgramClient("", config) diff --git a/examples/analyze/summary/main.py b/examples/analyze/summary/main.py index 1a362c97..e93e57fc 100644 --- a/examples/analyze/summary/main.py +++ b/examples/analyze/summary/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables # config: DeepgramClientOptions = DeepgramClientOptions( - # verbose=logging.SPAM, + # verbose=verboselogs.SPAM, # ) # deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/analyze/topic/main.py b/examples/analyze/topic/main.py index 10dc9daa..77b7c114 100644 --- a/examples/analyze/topic/main.py +++ b/examples/analyze/topic/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables # config: DeepgramClientOptions = DeepgramClientOptions( - # verbose=logging.SPAM, + # verbose=verboselogs.SPAM, # ) # deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/manage/balances/main.py b/examples/manage/balances/main.py index 45b1d8ad..d29f4c0b 100644 --- a/examples/manage/balances/main.py +++ b/examples/manage/balances/main.py @@ -5,7 +5,8 @@ import os import sys from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import DeepgramClient, DeepgramClientOptions @@ -16,7 +17,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables config: DeepgramClientOptions = DeepgramClientOptions( - verbose=logging.SPAM, + verbose=verboselogs.SPAM, ) deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/manage/keys/main.py b/examples/manage/keys/main.py index 916b29d6..81468226 100644 --- a/examples/manage/keys/main.py +++ b/examples/manage/keys/main.py @@ -4,7 +4,8 @@ import os import sys -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from dotenv import load_dotenv from deepgram import DeepgramClient, DeepgramClientOptions, KeyOptions @@ -16,7 +17,7 @@ def main(): try: # example of setting up a client config. logging values: WARNING, VERBOSE, DEBUG, SPAM config = DeepgramClientOptions( - verbose=logging.DEBUG, + verbose=verboselogs.DEBUG, ) deepgram: DeepgramClient = DeepgramClient("", config) # otherwise, use default config diff --git a/examples/manage/usage/main.py b/examples/manage/usage/main.py index cb6eb35a..f439e9bd 100644 --- a/examples/manage/usage/main.py +++ b/examples/manage/usage/main.py @@ -5,7 +5,8 @@ import os import sys from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import ( DeepgramClient, @@ -21,7 +22,7 @@ def main(): try: # Create a Deepgram client using the API key - # config: DeepgramClientOptions = DeepgramClientOptions(verbose=logging.SPAM) + # config: DeepgramClientOptions = DeepgramClientOptions(verbose=verboselogs.SPAM) config: DeepgramClientOptions = DeepgramClientOptions() deepgram: DeepgramClient = DeepgramClient("", config) diff --git a/examples/prerecorded/callback/callback/main.py b/examples/prerecorded/callback/callback/main.py index 246aea11..31fdd2f7 100644 --- a/examples/prerecorded/callback/callback/main.py +++ b/examples/prerecorded/callback/callback/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -27,7 +28,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables config: DeepgramClientOptions = DeepgramClientOptions( - verbose=logging.SPAM, + verbose=verboselogs.SPAM, ) deepgram: DeepgramClient = DeepgramClient("", config) diff --git a/examples/prerecorded/file/main.py b/examples/prerecorded/file/main.py index 32ac818b..7191e2d2 100644 --- a/examples/prerecorded/file/main.py +++ b/examples/prerecorded/file/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime import httpx @@ -24,7 +25,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables config: DeepgramClientOptions = DeepgramClientOptions( - verbose=logging.SPAM, + verbose=verboselogs.SPAM, ) deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/prerecorded/intent/main.py b/examples/prerecorded/intent/main.py index 379ca14e..940c19ba 100644 --- a/examples/prerecorded/intent/main.py +++ b/examples/prerecorded/intent/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables # config: DeepgramClientOptions = DeepgramClientOptions( - # verbose=logging.SPAM, + # verbose=verboselogs.SPAM, # ) # deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/prerecorded/legacy_dict_url/main.py b/examples/prerecorded/legacy_dict_url/main.py index c2d261ee..76b471f7 100644 --- a/examples/prerecorded/legacy_dict_url/main.py +++ b/examples/prerecorded/legacy_dict_url/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import ( DeepgramClient, diff --git a/examples/prerecorded/sentiment/main.py b/examples/prerecorded/sentiment/main.py index 667224e9..74e7e256 100644 --- a/examples/prerecorded/sentiment/main.py +++ b/examples/prerecorded/sentiment/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables # config: DeepgramClientOptions = DeepgramClientOptions( - # verbose=logging.SPAM, + # verbose=verboselogs.SPAM, # ) # deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/prerecorded/stream_file/main.py b/examples/prerecorded/stream_file/main.py index ec6497dd..92e31496 100644 --- a/examples/prerecorded/stream_file/main.py +++ b/examples/prerecorded/stream_file/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from io import BufferedReader from deepgram import DeepgramClientOptions @@ -13,7 +14,7 @@ from deepgram import ( DeepgramClient, DeepgramClientOptions, - ReadStreamSource, + StreamSource, PrerecordedOptions, ) @@ -26,7 +27,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables config = DeepgramClientOptions( - verbose=logging.SPAM, + verbose=verboselogs.SPAM, ) deepgram = DeepgramClient("", config) # OR use defaults @@ -35,7 +36,7 @@ def main(): # STEP 2 Call the transcribe_file method on the prerecorded class stream = open(AUDIO_FILE, "rb") - payload: ReadStreamSource = { + payload: StreamSource = { "stream": stream, } diff --git a/examples/prerecorded/summary/main.py b/examples/prerecorded/summary/main.py index 220a2a34..b3c379af 100644 --- a/examples/prerecorded/summary/main.py +++ b/examples/prerecorded/summary/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables # config: DeepgramClientOptions = DeepgramClientOptions( - # verbose=logging.SPAM, + # verbose=verboselogs.SPAM, # ) # deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/prerecorded/topic/main.py b/examples/prerecorded/topic/main.py index 4f4c687f..0b7e4295 100644 --- a/examples/prerecorded/topic/main.py +++ b/examples/prerecorded/topic/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from datetime import datetime, timedelta from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key in the environment variables # config: DeepgramClientOptions = DeepgramClientOptions( - # verbose=logging.SPAM, + # verbose=verboselogs.SPAM, # ) # deepgram: DeepgramClient = DeepgramClient("", config) # OR use defaults diff --git a/examples/prerecorded/url/main.py b/examples/prerecorded/url/main.py index 60f73adf..dccc1420 100644 --- a/examples/prerecorded/url/main.py +++ b/examples/prerecorded/url/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import ( DeepgramClient, diff --git a/examples/requirements-examples.txt b/examples/requirements-examples.txt index 1794c8bd..ee220abd 100644 --- a/examples/requirements-examples.txt +++ b/examples/requirements-examples.txt @@ -4,4 +4,4 @@ python-dotenv # streaming libs -pyaudio +pyaudio \ No newline at end of file diff --git a/examples/speak/file/async_hello_world/main.py b/examples/speak/file/async_hello_world/main.py index 8b759cf9..e24023f7 100644 --- a/examples/speak/file/async_hello_world/main.py +++ b/examples/speak/file/async_hello_world/main.py @@ -4,7 +4,8 @@ import asyncio from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import ( DeepgramClient, diff --git a/examples/speak/file/hello_world/main.py b/examples/speak/file/hello_world/main.py index b8b1123c..c5b29557 100644 --- a/examples/speak/file/hello_world/main.py +++ b/examples/speak/file/hello_world/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import ( DeepgramClient, @@ -21,7 +22,9 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key from environment variables - deepgram = DeepgramClient(api_key="", config=ClientOptionsFromEnv()) + deepgram = DeepgramClient( + api_key="", config=ClientOptionsFromEnv(verbose=verboselogs.SPAM) + ) # STEP 2 Call the save method on the speak property options = SpeakOptions( diff --git a/examples/speak/file/legacy_dict_hello_world/main.py b/examples/speak/file/legacy_dict_hello_world/main.py index 163c96ca..74a2a8e0 100644 --- a/examples/speak/file/legacy_dict_hello_world/main.py +++ b/examples/speak/file/legacy_dict_hello_world/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import ( DeepgramClient, diff --git a/examples/speak/file/woodchuck/main.py b/examples/speak/file/woodchuck/main.py index 6eb2b189..5358de0c 100644 --- a/examples/speak/file/woodchuck/main.py +++ b/examples/speak/file/woodchuck/main.py @@ -4,7 +4,8 @@ import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import ( DeepgramClient, @@ -23,9 +24,7 @@ def main(): try: # STEP 1 Create a Deepgram client using the API key from environment variables - deepgram = DeepgramClient( - api_key="", config=ClientOptionsFromEnv(verbose=logging.SPAM) - ) + deepgram = DeepgramClient(api_key="", config=ClientOptionsFromEnv()) # STEP 2 Call the save method on the speak property options = SpeakOptions( diff --git a/examples/streaming/async_http/main.py b/examples/streaming/async_http/main.py index 0c7d6605..a3c33059 100644 --- a/examples/streaming/async_http/main.py +++ b/examples/streaming/async_http/main.py @@ -7,7 +7,8 @@ import aiohttp import os from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import ( DeepgramClient, @@ -27,7 +28,7 @@ async def main(): # example of setting up a client config. logging values: WARNING, VERBOSE, DEBUG, SPAM # config: DeepgramClientOptions = DeepgramClientOptions( - # verbose=logging.DEBUG, options={"keepalive": "true"} + # verbose=verboselogs.DEBUG, options={"keepalive": "true"} # ) # deepgram: DeepgramClient = DeepgramClient(API_KEY, config) # otherwise, use default config @@ -63,13 +64,13 @@ async def on_speech_started(self, speech_started, **kwargs): async def on_utterance_end(self, utterance_end, **kwargs): print(f"\n\n{utterance_end}\n\n") - def on_close(self, close, **kwargs): + async def on_close(self, close, **kwargs): print(f"\n\n{close}\n\n") - def on_error(self, error, **kwargs): + async def on_error(self, error, **kwargs): print(f"\n\n{error}\n\n") - def on_unhandled(self, unhandled, **kwargs): + async def on_unhandled(self, unhandled, **kwargs): print(f"\n\n{unhandled}\n\n") dg_connection.on(LiveTranscriptionEvents.Open, on_open) diff --git a/examples/streaming/async_microphone/main.py b/examples/streaming/async_microphone/main.py index 0e79fd67..ce0d2b19 100644 --- a/examples/streaming/async_microphone/main.py +++ b/examples/streaming/async_microphone/main.py @@ -5,7 +5,8 @@ from signal import SIGINT, SIGTERM import asyncio from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from time import sleep from deepgram import ( diff --git a/examples/streaming/http/main.py b/examples/streaming/http/main.py index 417cac52..1a3f5f4b 100644 --- a/examples/streaming/http/main.py +++ b/examples/streaming/http/main.py @@ -4,7 +4,8 @@ import httpx from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs import threading from deepgram import ( @@ -23,7 +24,7 @@ def main(): try: # example of setting up a client config. logging values: WARNING, VERBOSE, DEBUG, SPAM - # config: DeepgramClientOptions = DeepgramClientOptions(verbose=logging.DEBUG) + # config: DeepgramClientOptions = DeepgramClientOptions(verbose=verboselogs.DEBUG) # deepgram: DeepgramClient = DeepgramClient("", config) # otherwise, use default config deepgram: DeepgramClient = DeepgramClient() diff --git a/examples/streaming/legacy_dict_microphone/main.py b/examples/streaming/legacy_dict_microphone/main.py index 746c665b..2c95f931 100644 --- a/examples/streaming/legacy_dict_microphone/main.py +++ b/examples/streaming/legacy_dict_microphone/main.py @@ -3,7 +3,8 @@ # SPDX-License-Identifier: MIT from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from time import sleep from deepgram import ( @@ -21,7 +22,7 @@ def main(): try: # example of setting up a client config. logging values: WARNING, VERBOSE, DEBUG, SPAM # config = DeepgramClientOptions( - # verbose=logging.DEBUG, + # verbose=verboselogs.DEBUG, # options={"keepalive": "true"} # ) # deepgram: DeepgramClient = DeepgramClient("", config) diff --git a/examples/streaming/microphone/main.py b/examples/streaming/microphone/main.py index 8d50c083..083aaf42 100644 --- a/examples/streaming/microphone/main.py +++ b/examples/streaming/microphone/main.py @@ -3,7 +3,8 @@ # SPDX-License-Identifier: MIT from dotenv import load_dotenv -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from time import sleep from deepgram import ( @@ -24,7 +25,7 @@ def main(): try: # example of setting up a client config. logging values: WARNING, VERBOSE, DEBUG, SPAM # config = DeepgramClientOptions( - # verbose=logging.DEBUG, options={"keepalive": "true"} + # verbose=verboselogs.DEBUG, options={"keepalive": "true"} # ) # deepgram: DeepgramClient = DeepgramClient("", config) # otherwise, use default config diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..fb624ca2 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,26 @@ +# MyPy config file +# File reference here - http://mypy.readthedocs.io/en/latest/config_file.html#config-file + +[mypy] +warn_redundant_casts = True +warn_unused_ignores = True + +# Needed because of bug in MyPy +disallow_subclassing_any = False + +mypy_path = stubs + +[mypy-*] +disallow_untyped_calls = True +disallow_untyped_defs = True +check_untyped_defs = True +warn_return_any = True +no_implicit_optional = True +strict_optional = True +ignore_missing_imports = True + +[mypy-setuptools] +ignore_missing_imports = True + +[mypy-aenum] +ignore_missing_imports = True \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index d0923f29..8f62bacf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,13 @@ typing-extensions = "^4.9.0" dataclasses-json = "^0.6.3" aiohttp = "^3.9.1" aiofiles = "^23.2.1" -verboselogs = "^1.7" +aenum = "^3.1.0" +# needed only if you are looking to develop/work-on the SDK +# black = "^24.0" +# pylint = "^3.0" +# mypy = "^1.0" +# types-pyaudio = "^0.2.16" +# types-aiofiles = "^23.2.0" # needed only if you are looking to use samples in the "examples" folder # pyaudio = "^0.2.14" # python-dotenv = "^1.0.0" diff --git a/requirements-dev.txt b/requirements-dev.txt index 7bb24000..d05169c3 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,14 @@ # pip install -r requirements.txt +# lint, static, etc +black==24.* +pylint==3.* +mypy==1.* + +# static check types +types-pyaudio +types-aiofiles + # Testing pytest pytest-asyncio diff --git a/requirements.txt b/requirements.txt index bf815b3a..7b5d360a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ httpx dataclasses-json dataclasses typing_extensions -verboselogs +aenum # Async functionality, likely to be already installed aiohttp diff --git a/setup.py b/setup.py index 550e0463..ffa7e436 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ "typing_extensions>=4.9.0", "aiohttp>=3.9.1", "aiofiles>=23.2.1", - "verboselogs>=1.7", + "aenum>=3.1.0", ], keywords=["deepgram", "deepgram speech-to-text"], classifiers=[ diff --git a/tests/edge_cases/keepalive/async/main.py b/tests/edge_cases/keepalive/async/main.py index 622f9bc2..043e8a37 100644 --- a/tests/edge_cases/keepalive/async/main.py +++ b/tests/edge_cases/keepalive/async/main.py @@ -4,7 +4,8 @@ import asyncio import time -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import DeepgramClient, DeepgramClientOptions, LiveOptions @@ -12,7 +13,7 @@ async def main(): # for debugging config: DeepgramClientOptions = DeepgramClientOptions( - verbose=logging.DEBUG, options={"keepalive": "true"} + verbose=verboselogs.DEBUG, options={"keepalive": "true"} ) deepgram: DeepgramClient = DeepgramClient("", config) diff --git a/tests/edge_cases/keepalive/sync/main.py b/tests/edge_cases/keepalive/sync/main.py index f0d466f6..4b35b59d 100644 --- a/tests/edge_cases/keepalive/sync/main.py +++ b/tests/edge_cases/keepalive/sync/main.py @@ -3,7 +3,8 @@ # SPDX-License-Identifier: MIT import time -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import DeepgramClient, DeepgramClientOptions, LiveOptions @@ -11,7 +12,7 @@ def main(): # for debugging config: DeepgramClientOptions = DeepgramClientOptions( - verbose=logging.DEBUG, options={"keepalive": "true"} + verbose=verboselogs.DEBUG, options={"keepalive": "true"} ) deepgram: DeepgramClient = DeepgramClient("", config) # OR diff --git a/tests/edge_cases/reconnect_same_object/async/main.py b/tests/edge_cases/reconnect_same_object/async/main.py index 28bf62d7..81824d85 100644 --- a/tests/edge_cases/reconnect_same_object/async/main.py +++ b/tests/edge_cases/reconnect_same_object/async/main.py @@ -1,6 +1,7 @@ import asyncio import time -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from signal import SIGINT, SIGTERM from deepgram import DeepgramClient, DeepgramClientOptions, LiveOptions, Microphone @@ -19,7 +20,7 @@ async def main(): ) config: DeepgramClientOptions = DeepgramClientOptions( - options={"keepalive": "true"}, verbose=logging.SPAM + options={"keepalive": "true"}, verbose=verboselogs.SPAM ) # config: DeepgramClientOptions = DeepgramClientOptions() deepgram: DeepgramClient = DeepgramClient("", config) diff --git a/tests/edge_cases/reconnect_same_object/sync/main.py b/tests/edge_cases/reconnect_same_object/sync/main.py index a7ec51ad..ca797c3b 100644 --- a/tests/edge_cases/reconnect_same_object/sync/main.py +++ b/tests/edge_cases/reconnect_same_object/sync/main.py @@ -1,11 +1,12 @@ import time -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import DeepgramClient, DeepgramClientOptions, LiveOptions, Microphone def main(): - config: DeepgramClientOptions = DeepgramClientOptions(verbose=logging.DEBUG) + config: DeepgramClientOptions = DeepgramClientOptions(verbose=verboselogs.DEBUG) # config: DeepgramClientOptions = DeepgramClientOptions() deepgram: DeepgramClient = DeepgramClient("", config) options: LiveOptions = LiveOptions( diff --git a/tests/expected_failures/exercise_timeout/async/main.py b/tests/expected_failures/exercise_timeout/async/main.py index 0c81800a..f07ca14b 100644 --- a/tests/expected_failures/exercise_timeout/async/main.py +++ b/tests/expected_failures/exercise_timeout/async/main.py @@ -4,14 +4,15 @@ import asyncio import time -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import DeepgramClient, DeepgramClientOptions, LiveOptions async def main(): # for debugging - config: DeepgramClientOptions = DeepgramClientOptions(verbose=logging.DEBUG) + config: DeepgramClientOptions = DeepgramClientOptions(verbose=verboselogs.DEBUG) deepgram: DeepgramClient = DeepgramClient("", config) # OR # deepgram: DeepgramClient = DeepgramClient() diff --git a/tests/expected_failures/exercise_timeout/sync/main.py b/tests/expected_failures/exercise_timeout/sync/main.py index 32905f0b..34bc843b 100644 --- a/tests/expected_failures/exercise_timeout/sync/main.py +++ b/tests/expected_failures/exercise_timeout/sync/main.py @@ -3,14 +3,15 @@ # SPDX-License-Identifier: MIT import time -import logging, verboselogs +import logging +from deepgram.utils import verboselogs from deepgram import DeepgramClient, DeepgramClientOptions, LiveOptions def main(): # for debugging - config: DeepgramClientOptions = DeepgramClientOptions(verbose=logging.SPAM) + config: DeepgramClientOptions = DeepgramClientOptions(verbose=verboselogs.SPAM) deepgram: DeepgramClient = DeepgramClient("", config) # OR # deepgram: DeepgramClient = DeepgramClient()