addisai 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - run: pip install -e ".[dev]"
21
+ - run: pytest -q
@@ -0,0 +1,24 @@
1
+ name: publish
2
+
3
+ # Publishes `addisai` to PyPI using the PYPI_API_TOKEN repository secret.
4
+ # Create a GitHub Release (tag e.g. v0.1.0) to trigger, or run manually.
5
+ # (Consider migrating to PyPI Trusted Publishing/OIDC later — then drop the token.)
6
+ on:
7
+ release:
8
+ types: [published]
9
+ workflow_dispatch:
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.11"
19
+ - run: pip install -e ".[dev]" build
20
+ - run: pytest -q
21
+ - run: python -m build
22
+ - uses: pypa/gh-action-pypi-publish@release/v1
23
+ with:
24
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,22 @@
1
+ name: smoke
2
+
3
+ # Live smoke test against the real API. NOT a PR gate — manual + scheduled only.
4
+ # Requires repo secret ADDIS_SMOKE_API_KEY (a low-balance sandbox key).
5
+ on:
6
+ workflow_dispatch:
7
+ schedule:
8
+ - cron: "0 6 * * *"
9
+
10
+ jobs:
11
+ smoke:
12
+ runs-on: ubuntu-latest
13
+ if: ${{ github.repository_owner == 'Addis-AI-Org' }}
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: "3.11"
19
+ - run: pip install -e ".[dev]"
20
+ - run: python scripts/smoke.py
21
+ env:
22
+ ADDIS_API_KEY: ${{ secrets.ADDIS_SMOKE_API_KEY }}
@@ -0,0 +1,8 @@
1
+ .venv
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
8
+ .mypy_cache/
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — Developer preview
4
+
5
+ Initial release of the official Addis AI SDK for Python.
6
+
7
+ - **Voice (TTS):** `voice.generate`, `voices.list/preview`, `voice.estimate`,
8
+ `voice.usage`, `voice.clips.*`, `VoiceClip` helpers.
9
+ - **Chat / LLM:** OpenAI-compatible `chat.completions.create` with `system`,
10
+ `persona`, tools/function calling, attachments, audio input; `chat.run_tools`
11
+ agent loop; beta SSE streaming via `ChatStream`.
12
+ - **Speech-to-text:** `speech.transcribe`. **Translation:** `translate.create`.
13
+ - **Reliability/security:** automatic retries with backoff, idempotent paid
14
+ calls, normalized exception hierarchy, secret redaction, Cloudflare-only
15
+ transport. Built on `httpx`. Typed (`py.typed`).
16
+
17
+ > Persona, system prompts, and function calling depend on a backend rollout; they
18
+ > are verified and ready in the SDK and activate as the server side ships.
19
+ >
20
+ > An async client (`AsyncAddisAI`) is planned for a follow-up release.
addisai-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Addis AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
addisai-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,200 @@
1
+ Metadata-Version: 2.4
2
+ Name: addisai
3
+ Version: 0.1.0
4
+ Summary: Official Addis AI SDK for Python — voice (TTS), chat/LLM with system prompts, personas and function calling, speech-to-text, and translation for Amharic and Afan Oromo.
5
+ Project-URL: Homepage, https://addisai.com
6
+ Project-URL: Repository, https://github.com/Addis-AI-Org/addisai-py
7
+ Project-URL: Issues, https://github.com/Addis-AI-Org/addisai-py/issues
8
+ Author: Addis AI
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: addis,addisai,afan-oromo,ai,amharic,llm,speech-to-text,text-to-speech,translation,tts,voice
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Typing :: Typed
16
+ Requires-Python: >=3.8
17
+ Requires-Dist: httpx<1,>=0.24
18
+ Provides-Extra: dev
19
+ Requires-Dist: mypy>=1.6; extra == 'dev'
20
+ Requires-Dist: pytest>=7; extra == 'dev'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # addisai (Python)
24
+
25
+ The official [Addis AI](https://addisai.com) SDK for Python — voice (text‑to‑speech), chat/LLM with system prompts, personas and function calling, speech‑to‑text, and translation for **Amharic (`am`)** and **Afan Oromo (`om`)**.
26
+
27
+ Mirrors the [Node SDK](../node) surface, Pythonic and `snake_case`.
28
+
29
+ ```bash
30
+ pip install addisai
31
+ ```
32
+
33
+ Requires Python 3.8+.
34
+
35
+ ## Quickstart
36
+
37
+ ```python
38
+ from addisai import AddisAI
39
+
40
+ addis = AddisAI() # reads ADDIS_API_KEY from the environment
41
+
42
+ # Text-to-speech
43
+ clip = addis.voice.generate(voice_id="am-hiwot", text="ሰላም ለዓለም።", language="am")
44
+ clip.to_file("welcome.mp3")
45
+
46
+ # Chat
47
+ res = addis.chat.completions.create(
48
+ language="am",
49
+ system="Answer in concise bullet points.",
50
+ messages=[{"role": "user", "content": "ስለ አዲስ አበባ ንገረኝ"}],
51
+ )
52
+ print(res["choices"][0]["message"]["content"])
53
+ ```
54
+
55
+ ## Configuration
56
+
57
+ ```python
58
+ addis = AddisAI(
59
+ api_key="...", # or set ADDIS_API_KEY
60
+ timeout=60.0, # seconds (voice.generate raises this to >= 95s)
61
+ max_retries=2, # backoff on 408/409/425/429/5xx
62
+ )
63
+ ```
64
+
65
+ The key is read from `api_key=` or `ADDIS_API_KEY`, never logged, and redacted in `repr()`. The SDK only talks to the Cloudflare‑wrapped API and refuses raw `*.supabase.co` hosts.
66
+
67
+ ## Voice (text‑to‑speech)
68
+
69
+ ```python
70
+ clip = addis.voice.generate(
71
+ voice_id="am-hiwot",
72
+ text="ሰላም።",
73
+ language="am",
74
+ output_format="mp3_44100", # mp3_44100 | wav_44100 | pcm_16000
75
+ voice_settings={"speed": 50, "stability": 50, "similarity": 50, "style": 0},
76
+ )
77
+ clip.audio_url
78
+ clip.usage # {"credits_used": ..., "currency": "ETB", ...}
79
+ data = clip.content() # bytes
80
+ clip.to_file("out.mp3")
81
+ ```
82
+
83
+ Idempotency keys are generated automatically and reused across retries so a retry is never double‑billed. Pass `client_request_id=` for full control.
84
+
85
+ ```python
86
+ addis.voices.list(language="am", gender="female")
87
+ addis.voice.estimate(voice_id="am-hiwot", text="ሰላም", language="am")
88
+ addis.voice.usage()
89
+ for clip in addis.voice.clips.list(language="am"): # auto-paginates
90
+ print(clip.id)
91
+ addis.voice.clips.delete("clip_123")
92
+ ```
93
+
94
+ ## Chat / LLM
95
+
96
+ ```python
97
+ res = addis.chat.completions.create(
98
+ language="am",
99
+ system="Be concise.",
100
+ persona="You are RecipeBot by AcmeCorp.",
101
+ messages=[{"role": "user", "content": "የእንጀራ አሰራር አስተምረኝ"}],
102
+ temperature=0.7,
103
+ max_tokens=1200,
104
+ )
105
+ ```
106
+
107
+ > The model is selected by Addis AI; responses report the id `addis-1-alef`.
108
+
109
+ ### Function calling
110
+
111
+ ```python
112
+ def get_order_status(args):
113
+ return {"status": "shipped", "eta": "tomorrow"}
114
+
115
+ final = addis.chat.run_tools(
116
+ language="am",
117
+ messages=[{"role": "user", "content": "Check order 123 and summarize it."}],
118
+ tools=[{
119
+ "type": "function",
120
+ "function": {
121
+ "name": "get_order_status",
122
+ "description": "Fetch order status by order ID.",
123
+ "parameters": {"type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"]},
124
+ "callable": get_order_status,
125
+ },
126
+ }],
127
+ )
128
+ print(final["choices"][0]["message"]["content"])
129
+ ```
130
+
131
+ ### Streaming (beta)
132
+
133
+ `stream=True` returns a `ChatStream` — iterate for OpenAI‑style chunk dicts, or use the accumulators:
134
+
135
+ ```python
136
+ stream = addis.chat.completions.create(
137
+ language="am", messages=[{"role": "user", "content": "Capital?"}], stream=True
138
+ )
139
+ for chunk in stream:
140
+ print(chunk["choices"][0]["delta"].get("content", ""), end="", flush=True)
141
+
142
+ # or, instead of iterating:
143
+ text = addis.chat.completions.create(language="am", messages=[...], stream=True).final_text()
144
+ completion = addis.chat.completions.create(language="am", messages=[...], stream=True).final_completion()
145
+ ```
146
+
147
+ Streaming is beta and not available with tools.
148
+
149
+ ## Speech‑to‑text & translation
150
+
151
+ ```python
152
+ with open("call.wav", "rb") as f:
153
+ t = addis.speech.transcribe(audio=f, language="am") # am|om|en|ha|sw
154
+ print(t["text"])
155
+
156
+ out = addis.translate.create(text="Hello", source="en", target="am") # am|om|en
157
+ print(out["text"])
158
+ ```
159
+
160
+ ## Legacy audio (deprecated)
161
+
162
+ ```python
163
+ # ⚠️ deprecated — use addis.voice.generate
164
+ out = addis.legacy.audio.generate(text="ሰላም", language="am")
165
+ out.to_file("legacy.wav")
166
+
167
+ # streaming bridge — yields audio bytes, handles both legacy encodings
168
+ audio = addis.legacy.audio.stream(text="ሰላም", language="am")
169
+ audio.to_file("legacy.wav") # or: data = audio.read()
170
+ ```
171
+
172
+ ## Errors
173
+
174
+ ```python
175
+ from addisai import InsufficientCreditsError, RateLimitError, APIError
176
+
177
+ try:
178
+ addis.voice.generate(voice_id="am-hiwot", text="ሰላም", language="am")
179
+ except InsufficientCreditsError as e:
180
+ show_top_up(e.available_balance)
181
+ except RateLimitError as e:
182
+ sleep(e.retry_after or 1)
183
+ except APIError as e:
184
+ print(e.status, e.code, e.details)
185
+ ```
186
+
187
+ ## Not yet in the Python SDK
188
+
189
+ An async client (`AsyncAddisAI`) is the one planned follow‑up. Everything else — including streaming — is at parity with the Node SDK.
190
+
191
+ ## Live smoke test
192
+
193
+ `scripts/smoke.py` exercises the real API end-to-end. It is **not** a PR gate —
194
+ the `smoke` workflow runs on manual dispatch and a daily schedule, only where the
195
+ repo secret `ADDIS_SMOKE_API_KEY` (a low-balance sandbox key) is set. Run locally
196
+ with `ADDIS_API_KEY=<key> python scripts/smoke.py`.
197
+
198
+ ## License
199
+
200
+ MIT
@@ -0,0 +1,178 @@
1
+ # addisai (Python)
2
+
3
+ The official [Addis AI](https://addisai.com) SDK for Python — voice (text‑to‑speech), chat/LLM with system prompts, personas and function calling, speech‑to‑text, and translation for **Amharic (`am`)** and **Afan Oromo (`om`)**.
4
+
5
+ Mirrors the [Node SDK](../node) surface, Pythonic and `snake_case`.
6
+
7
+ ```bash
8
+ pip install addisai
9
+ ```
10
+
11
+ Requires Python 3.8+.
12
+
13
+ ## Quickstart
14
+
15
+ ```python
16
+ from addisai import AddisAI
17
+
18
+ addis = AddisAI() # reads ADDIS_API_KEY from the environment
19
+
20
+ # Text-to-speech
21
+ clip = addis.voice.generate(voice_id="am-hiwot", text="ሰላም ለዓለም።", language="am")
22
+ clip.to_file("welcome.mp3")
23
+
24
+ # Chat
25
+ res = addis.chat.completions.create(
26
+ language="am",
27
+ system="Answer in concise bullet points.",
28
+ messages=[{"role": "user", "content": "ስለ አዲስ አበባ ንገረኝ"}],
29
+ )
30
+ print(res["choices"][0]["message"]["content"])
31
+ ```
32
+
33
+ ## Configuration
34
+
35
+ ```python
36
+ addis = AddisAI(
37
+ api_key="...", # or set ADDIS_API_KEY
38
+ timeout=60.0, # seconds (voice.generate raises this to >= 95s)
39
+ max_retries=2, # backoff on 408/409/425/429/5xx
40
+ )
41
+ ```
42
+
43
+ The key is read from `api_key=` or `ADDIS_API_KEY`, never logged, and redacted in `repr()`. The SDK only talks to the Cloudflare‑wrapped API and refuses raw `*.supabase.co` hosts.
44
+
45
+ ## Voice (text‑to‑speech)
46
+
47
+ ```python
48
+ clip = addis.voice.generate(
49
+ voice_id="am-hiwot",
50
+ text="ሰላም።",
51
+ language="am",
52
+ output_format="mp3_44100", # mp3_44100 | wav_44100 | pcm_16000
53
+ voice_settings={"speed": 50, "stability": 50, "similarity": 50, "style": 0},
54
+ )
55
+ clip.audio_url
56
+ clip.usage # {"credits_used": ..., "currency": "ETB", ...}
57
+ data = clip.content() # bytes
58
+ clip.to_file("out.mp3")
59
+ ```
60
+
61
+ Idempotency keys are generated automatically and reused across retries so a retry is never double‑billed. Pass `client_request_id=` for full control.
62
+
63
+ ```python
64
+ addis.voices.list(language="am", gender="female")
65
+ addis.voice.estimate(voice_id="am-hiwot", text="ሰላም", language="am")
66
+ addis.voice.usage()
67
+ for clip in addis.voice.clips.list(language="am"): # auto-paginates
68
+ print(clip.id)
69
+ addis.voice.clips.delete("clip_123")
70
+ ```
71
+
72
+ ## Chat / LLM
73
+
74
+ ```python
75
+ res = addis.chat.completions.create(
76
+ language="am",
77
+ system="Be concise.",
78
+ persona="You are RecipeBot by AcmeCorp.",
79
+ messages=[{"role": "user", "content": "የእንጀራ አሰራር አስተምረኝ"}],
80
+ temperature=0.7,
81
+ max_tokens=1200,
82
+ )
83
+ ```
84
+
85
+ > The model is selected by Addis AI; responses report the id `addis-1-alef`.
86
+
87
+ ### Function calling
88
+
89
+ ```python
90
+ def get_order_status(args):
91
+ return {"status": "shipped", "eta": "tomorrow"}
92
+
93
+ final = addis.chat.run_tools(
94
+ language="am",
95
+ messages=[{"role": "user", "content": "Check order 123 and summarize it."}],
96
+ tools=[{
97
+ "type": "function",
98
+ "function": {
99
+ "name": "get_order_status",
100
+ "description": "Fetch order status by order ID.",
101
+ "parameters": {"type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"]},
102
+ "callable": get_order_status,
103
+ },
104
+ }],
105
+ )
106
+ print(final["choices"][0]["message"]["content"])
107
+ ```
108
+
109
+ ### Streaming (beta)
110
+
111
+ `stream=True` returns a `ChatStream` — iterate for OpenAI‑style chunk dicts, or use the accumulators:
112
+
113
+ ```python
114
+ stream = addis.chat.completions.create(
115
+ language="am", messages=[{"role": "user", "content": "Capital?"}], stream=True
116
+ )
117
+ for chunk in stream:
118
+ print(chunk["choices"][0]["delta"].get("content", ""), end="", flush=True)
119
+
120
+ # or, instead of iterating:
121
+ text = addis.chat.completions.create(language="am", messages=[...], stream=True).final_text()
122
+ completion = addis.chat.completions.create(language="am", messages=[...], stream=True).final_completion()
123
+ ```
124
+
125
+ Streaming is beta and not available with tools.
126
+
127
+ ## Speech‑to‑text & translation
128
+
129
+ ```python
130
+ with open("call.wav", "rb") as f:
131
+ t = addis.speech.transcribe(audio=f, language="am") # am|om|en|ha|sw
132
+ print(t["text"])
133
+
134
+ out = addis.translate.create(text="Hello", source="en", target="am") # am|om|en
135
+ print(out["text"])
136
+ ```
137
+
138
+ ## Legacy audio (deprecated)
139
+
140
+ ```python
141
+ # ⚠️ deprecated — use addis.voice.generate
142
+ out = addis.legacy.audio.generate(text="ሰላም", language="am")
143
+ out.to_file("legacy.wav")
144
+
145
+ # streaming bridge — yields audio bytes, handles both legacy encodings
146
+ audio = addis.legacy.audio.stream(text="ሰላም", language="am")
147
+ audio.to_file("legacy.wav") # or: data = audio.read()
148
+ ```
149
+
150
+ ## Errors
151
+
152
+ ```python
153
+ from addisai import InsufficientCreditsError, RateLimitError, APIError
154
+
155
+ try:
156
+ addis.voice.generate(voice_id="am-hiwot", text="ሰላም", language="am")
157
+ except InsufficientCreditsError as e:
158
+ show_top_up(e.available_balance)
159
+ except RateLimitError as e:
160
+ sleep(e.retry_after or 1)
161
+ except APIError as e:
162
+ print(e.status, e.code, e.details)
163
+ ```
164
+
165
+ ## Not yet in the Python SDK
166
+
167
+ An async client (`AsyncAddisAI`) is the one planned follow‑up. Everything else — including streaming — is at parity with the Node SDK.
168
+
169
+ ## Live smoke test
170
+
171
+ `scripts/smoke.py` exercises the real API end-to-end. It is **not** a PR gate —
172
+ the `smoke` workflow runs on manual dispatch and a daily schedule, only where the
173
+ repo secret `ADDIS_SMOKE_API_KEY` (a low-balance sandbox key) is set. Run locally
174
+ with `ADDIS_API_KEY=<key> python scripts/smoke.py`.
175
+
176
+ ## License
177
+
178
+ MIT
@@ -0,0 +1,51 @@
1
+ """Official Addis AI SDK for Python."""
2
+ from ._client import AddisAI
3
+ from ._clip import VoiceClip
4
+ from ._idempotency import ulid
5
+ from ._streaming import AudioStream, ChatStream
6
+ from ._version import __version__
7
+ from .resources import ADDIS_CHAT_MODEL
8
+ from ._exceptions import (
9
+ AddisAIError,
10
+ APIConnectionError,
11
+ APIConnectionTimeoutError,
12
+ APIError,
13
+ AuthenticationError,
14
+ BadRequestError,
15
+ ConflictError,
16
+ GenerationInProgressError,
17
+ IdempotencyConflictError,
18
+ InsufficientCreditsError,
19
+ InternalServerError,
20
+ NotFoundError,
21
+ NotSupportedError,
22
+ PermissionDeniedError,
23
+ RateLimitError,
24
+ UnprocessableEntityError,
25
+ )
26
+
27
+ __all__ = [
28
+ "AddisAI",
29
+ "VoiceClip",
30
+ "ChatStream",
31
+ "AudioStream",
32
+ "ulid",
33
+ "ADDIS_CHAT_MODEL",
34
+ "__version__",
35
+ "AddisAIError",
36
+ "APIError",
37
+ "APIConnectionError",
38
+ "APIConnectionTimeoutError",
39
+ "AuthenticationError",
40
+ "BadRequestError",
41
+ "ConflictError",
42
+ "GenerationInProgressError",
43
+ "IdempotencyConflictError",
44
+ "InsufficientCreditsError",
45
+ "InternalServerError",
46
+ "NotFoundError",
47
+ "NotSupportedError",
48
+ "PermissionDeniedError",
49
+ "RateLimitError",
50
+ "UnprocessableEntityError",
51
+ ]
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from typing import Any, Dict, Optional
5
+
6
+ import httpx
7
+
8
+ from ._env import API_KEY_ENV_VAR, redact_api_key, resolve_base_url
9
+ from ._exceptions import AddisAIError
10
+ from ._transport import Transport
11
+ from .resources import Chat, Legacy, Speech, Translate, Voice, Voices
12
+
13
+
14
+ class AddisAI:
15
+ """Client for the Addis AI API — voice, chat/LLM, speech-to-text, translation."""
16
+
17
+ def __init__(
18
+ self,
19
+ *,
20
+ api_key: Optional[str] = None,
21
+ base_url: Optional[str] = None,
22
+ timeout: float = 60.0,
23
+ max_retries: int = 3,
24
+ default_headers: Optional[Dict[str, str]] = None,
25
+ default_query: Optional[Dict[str, str]] = None,
26
+ http_client: Optional[httpx.Client] = None,
27
+ ) -> None:
28
+ key = api_key or os.environ.get(API_KEY_ENV_VAR)
29
+ if not key:
30
+ raise AddisAIError(
31
+ f"Missing API key. Pass api_key= or set the {API_KEY_ENV_VAR} environment variable."
32
+ )
33
+ self._api_key = key
34
+ self._transport = Transport(
35
+ api_key=key,
36
+ base_url=resolve_base_url(base_url),
37
+ timeout=timeout,
38
+ max_retries=max_retries,
39
+ default_headers=default_headers or {},
40
+ default_query=default_query or {},
41
+ http_client=http_client,
42
+ )
43
+
44
+ self.chat = Chat(self._transport)
45
+ self.voice = Voice(self._transport)
46
+ self.voices = Voices(self._transport)
47
+ self.speech = Speech(self._transport)
48
+ self.translate = Translate(self._transport)
49
+ #: Deprecated. Use ``voice``.
50
+ self.legacy = Legacy(self._transport)
51
+
52
+ def close(self) -> None:
53
+ self._transport.close()
54
+
55
+ def __enter__(self) -> "AddisAI":
56
+ return self
57
+
58
+ def __exit__(self, *exc: Any) -> None:
59
+ self.close()
60
+
61
+ def __repr__(self) -> str:
62
+ return f'AddisAI(api_key="{redact_api_key(self._api_key)}")'