typecast-python 0.1.7__tar.gz → 0.1.9__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.
- {typecast_python-0.1.7 → typecast_python-0.1.9}/PKG-INFO +4 -1
- {typecast_python-0.1.7 → typecast_python-0.1.9}/README.md +1 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/pyproject.toml +33 -3
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/async_client.py +4 -5
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/client.py +4 -5
- typecast_python-0.1.9/src/typecast/sse.py +58 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/websocket.py +7 -6
- typecast_python-0.1.7/src/typecast/sse.py +0 -35
- {typecast_python-0.1.7 → typecast_python-0.1.9}/.gitignore +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/LICENSE +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/__init__.py +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/conf.py +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/exceptions.py +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/models/__init__.py +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/models/error.py +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/models/tts.py +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/models/tts_wss.py +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/models/voices.py +0 -0
- {typecast_python-0.1.7 → typecast_python-0.1.9}/src/typecast/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: typecast-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Official Typecast Python SDK - Convert text to lifelike speech using AI-powered voices
|
|
5
5
|
Project-URL: Homepage, https://typecast.ai
|
|
6
6
|
Project-URL: Documentation, https://typecast.ai/docs/overview
|
|
@@ -230,11 +230,13 @@ Requires-Dist: sseclient-py>=1.7.2
|
|
|
230
230
|
Requires-Dist: typing-extensions>=4.0.0
|
|
231
231
|
Requires-Dist: websockets>=10.0
|
|
232
232
|
Provides-Extra: dev
|
|
233
|
+
Requires-Dist: aioresponses>=0.7.6; extra == 'dev'
|
|
233
234
|
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
234
235
|
Requires-Dist: flake8>=6.0.0; extra == 'dev'
|
|
235
236
|
Requires-Dist: isort>=5.0.0; extra == 'dev'
|
|
236
237
|
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
237
238
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
239
|
+
Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
|
|
238
240
|
Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
|
|
239
241
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
240
242
|
Description-Content-Type: text/markdown
|
|
@@ -248,6 +250,7 @@ Description-Content-Type: text/markdown
|
|
|
248
250
|
Convert text to lifelike speech using AI-powered voices
|
|
249
251
|
|
|
250
252
|
[](https://pypi.org/project/typecast-python/)
|
|
253
|
+
[](../docs/coverage-policy.md)
|
|
251
254
|
[](LICENSE)
|
|
252
255
|
[](https://www.python.org/)
|
|
253
256
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
Convert text to lifelike speech using AI-powered voices
|
|
8
8
|
|
|
9
9
|
[](https://pypi.org/project/typecast-python/)
|
|
10
|
+
[](../docs/coverage-policy.md)
|
|
10
11
|
[](LICENSE)
|
|
11
12
|
[](https://www.python.org/)
|
|
12
13
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "typecast-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.9"
|
|
8
8
|
description = "Official Typecast Python SDK - Convert text to lifelike speech using AI-powered voices"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Neosapience", email = "help@typecast.ai"}
|
|
@@ -40,12 +40,14 @@ dependencies = [
|
|
|
40
40
|
[project.optional-dependencies]
|
|
41
41
|
dev = [
|
|
42
42
|
"pytest>=7.0.0",
|
|
43
|
+
"pytest-cov>=7.0.0",
|
|
44
|
+
"pytest-mock>=3.14.0",
|
|
45
|
+
"pytest-asyncio>=0.21.0",
|
|
46
|
+
"aioresponses>=0.7.6",
|
|
43
47
|
"black>=23.0.0",
|
|
44
48
|
"flake8>=6.0.0",
|
|
45
49
|
"mypy>=1.0.0",
|
|
46
50
|
"isort>=5.0.0",
|
|
47
|
-
"pytest-mock>=3.14.0",
|
|
48
|
-
"pytest-asyncio>=0.21.0",
|
|
49
51
|
]
|
|
50
52
|
|
|
51
53
|
[project.urls]
|
|
@@ -90,6 +92,34 @@ dev = [
|
|
|
90
92
|
"pytest-cov>=7.0.0",
|
|
91
93
|
"pytest-mock>=3.14.0",
|
|
92
94
|
"pytest-asyncio>=0.21.0",
|
|
95
|
+
"aioresponses>=0.7.6",
|
|
93
96
|
"python-dotenv>=1.1.1",
|
|
94
97
|
"ruff>=0.14.0",
|
|
95
98
|
]
|
|
99
|
+
|
|
100
|
+
[tool.pytest.ini_options]
|
|
101
|
+
asyncio_mode = "auto"
|
|
102
|
+
testpaths = ["tests"]
|
|
103
|
+
filterwarnings = [
|
|
104
|
+
"ignore::DeprecationWarning",
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
[tool.coverage.run]
|
|
108
|
+
branch = true
|
|
109
|
+
source = ["src/typecast"]
|
|
110
|
+
omit = [
|
|
111
|
+
"*/tests/*",
|
|
112
|
+
"*/examples/*",
|
|
113
|
+
"*/__pycache__/*",
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
[tool.coverage.report]
|
|
117
|
+
show_missing = true
|
|
118
|
+
skip_covered = false
|
|
119
|
+
fail_under = 100
|
|
120
|
+
exclude_lines = [
|
|
121
|
+
"pragma: no cover",
|
|
122
|
+
"raise NotImplementedError",
|
|
123
|
+
"if TYPE_CHECKING:",
|
|
124
|
+
"\\.\\.\\.",
|
|
125
|
+
]
|
|
@@ -196,12 +196,11 @@ class AsyncTypecast:
|
|
|
196
196
|
params = {}
|
|
197
197
|
if filter:
|
|
198
198
|
filter_dict = filter.model_dump(exclude_none=True)
|
|
199
|
-
# Convert enum values to
|
|
199
|
+
# Convert enum values to their underlying str representation.
|
|
200
|
+
# Every VoicesV2Filter field is an Optional[Enum], so getattr
|
|
201
|
+
# falls back only if a future non-enum field is added.
|
|
200
202
|
for key, value in filter_dict.items():
|
|
201
|
-
|
|
202
|
-
params[key] = value.value
|
|
203
|
-
else:
|
|
204
|
-
params[key] = value
|
|
203
|
+
params[key] = getattr(value, "value", value)
|
|
205
204
|
|
|
206
205
|
async with self.session.get(
|
|
207
206
|
f"{self.host}{endpoint}", params=params
|
|
@@ -173,12 +173,11 @@ class Typecast:
|
|
|
173
173
|
params = {}
|
|
174
174
|
if filter:
|
|
175
175
|
filter_dict = filter.model_dump(exclude_none=True)
|
|
176
|
-
# Convert enum values to
|
|
176
|
+
# Convert enum values to their underlying str representation.
|
|
177
|
+
# Every VoicesV2Filter field is an Optional[Enum], so getattr
|
|
178
|
+
# falls back only if a future non-enum field is added.
|
|
177
179
|
for key, value in filter_dict.items():
|
|
178
|
-
|
|
179
|
-
params[key] = value.value
|
|
180
|
-
else:
|
|
181
|
-
params[key] = value
|
|
180
|
+
params[key] = getattr(value, "value", value)
|
|
182
181
|
|
|
183
182
|
response = self.session.get(f"{self.host}{endpoint}", params=params)
|
|
184
183
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from typing import AsyncIterator, Optional
|
|
2
|
+
|
|
3
|
+
import aiohttp
|
|
4
|
+
|
|
5
|
+
from . import conf
|
|
6
|
+
from .exceptions import TypecastError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TypecastSSE:
|
|
10
|
+
"""Server-Sent Events client for Typecast streaming endpoints."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
api_key: Optional[str] = None,
|
|
15
|
+
host: Optional[str] = None,
|
|
16
|
+
sse_url: Optional[str] = None,
|
|
17
|
+
):
|
|
18
|
+
"""Initialize the SSE client.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
api_key: API key. Defaults to TYPECAST_API_KEY env var.
|
|
22
|
+
host: API host. Defaults to TYPECAST_API_HOST env var or
|
|
23
|
+
'https://api.typecast.ai'. Used to derive sse_url when
|
|
24
|
+
sse_url is not provided.
|
|
25
|
+
sse_url: Full SSE base URL override (escape hatch for tests).
|
|
26
|
+
When provided, takes precedence over host.
|
|
27
|
+
"""
|
|
28
|
+
self.api_key = conf.get_api_key(api_key)
|
|
29
|
+
self.host = conf.get_host(host)
|
|
30
|
+
self._sse_url_override = sse_url
|
|
31
|
+
self.session: Optional[aiohttp.ClientSession] = None
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def sse_url(self) -> str:
|
|
35
|
+
if self._sse_url_override is not None:
|
|
36
|
+
return self._sse_url_override
|
|
37
|
+
return f"{self.host}/v1/text-to-speech/sse"
|
|
38
|
+
|
|
39
|
+
async def connect(self, endpoint: str) -> AsyncIterator[str]:
|
|
40
|
+
if self.session:
|
|
41
|
+
await self.session.close()
|
|
42
|
+
|
|
43
|
+
self.session = aiohttp.ClientSession(
|
|
44
|
+
headers={"X-API-KEY": self.api_key, "Accept": "text/event-stream"}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
async with self.session.get(f"{self.sse_url}/{endpoint}") as response:
|
|
48
|
+
if response.status != 200:
|
|
49
|
+
raise TypecastError(f"SSE connection failed: {response.status}")
|
|
50
|
+
|
|
51
|
+
async for line in response.content:
|
|
52
|
+
decoded_line = line.decode("utf-8").strip()
|
|
53
|
+
if decoded_line.startswith("data: "):
|
|
54
|
+
yield decoded_line[6:]
|
|
55
|
+
|
|
56
|
+
async def close(self):
|
|
57
|
+
if self.session:
|
|
58
|
+
await self.session.close()
|
|
@@ -9,17 +9,18 @@ from .models import WebSocketMessage
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class TypecastWebSocket:
|
|
12
|
-
|
|
12
|
+
"""WebSocket client for Typecast streaming TTS."""
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
DEFAULT_WS_URL = "wss://api.typecast.ai/v1/ws"
|
|
15
|
+
|
|
16
|
+
def __init__(self, api_key: str, ws_url: Optional[str] = None):
|
|
15
17
|
self.api_key = api_key
|
|
18
|
+
self.ws_url = ws_url or self.DEFAULT_WS_URL
|
|
16
19
|
self.ws: Optional[websockets.WebSocketClientProtocol] = None
|
|
17
20
|
self.callbacks: dict[str, Callable] = {}
|
|
18
21
|
|
|
19
22
|
async def connect(self):
|
|
20
|
-
self.ws = await websockets.connect(f"{self.
|
|
21
|
-
|
|
22
|
-
# Start message handler
|
|
23
|
+
self.ws = await websockets.connect(f"{self.ws_url}?token={self.api_key}")
|
|
23
24
|
asyncio.create_task(self._message_handler())
|
|
24
25
|
|
|
25
26
|
async def _message_handler(self):
|
|
@@ -34,7 +35,7 @@ class TypecastWebSocket:
|
|
|
34
35
|
await self.callbacks[msg.type](msg.payload)
|
|
35
36
|
|
|
36
37
|
def on(self, event_type: str, callback: Callable):
|
|
37
|
-
"""Register event callback"""
|
|
38
|
+
"""Register event callback."""
|
|
38
39
|
self.callbacks[event_type] = callback
|
|
39
40
|
|
|
40
41
|
async def send(self, message: WebSocketMessage):
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
from typing import AsyncIterator, Optional
|
|
2
|
-
|
|
3
|
-
import aiohttp
|
|
4
|
-
|
|
5
|
-
from . import conf
|
|
6
|
-
from .exceptions import TypecastError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TypecastSSE:
|
|
10
|
-
SSE_URL = f"{conf.get_host()}/v1/text-to-speech/sse"
|
|
11
|
-
|
|
12
|
-
def __init__(self, api_key: str):
|
|
13
|
-
self.api_key = conf.get_api_key(api_key)
|
|
14
|
-
self.session: Optional[aiohttp.ClientSession] = None
|
|
15
|
-
|
|
16
|
-
async def connect(self, endpoint: str) -> AsyncIterator[str]:
|
|
17
|
-
if self.session:
|
|
18
|
-
await self.session.close()
|
|
19
|
-
|
|
20
|
-
self.session = aiohttp.ClientSession(
|
|
21
|
-
headers={"X-API-KEY": self.api_key, "Accept": "text/event-stream"}
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
async with self.session.get(f"{self.SSE_URL}/{endpoint}") as response:
|
|
25
|
-
if response.status != 200:
|
|
26
|
-
raise TypecastError(f"SSE connection failed: {response.status}")
|
|
27
|
-
|
|
28
|
-
async for line in response.content:
|
|
29
|
-
decoded_line = line.decode("utf-8").strip()
|
|
30
|
-
if decoded_line.startswith("data: "):
|
|
31
|
-
yield decoded_line[6:]
|
|
32
|
-
|
|
33
|
-
async def close(self):
|
|
34
|
-
if self.session:
|
|
35
|
-
await self.session.close()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|