typecast-python 0.3.15__py3-none-any.whl → 0.4.1__py3-none-any.whl

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/_user_agent.py CHANGED
@@ -108,19 +108,3 @@ def aiohttp_user_agent(
108
108
  generated_by=generated_by,
109
109
  )
110
110
 
111
-
112
- def httpx_user_agent(
113
- host: str,
114
- mode: str,
115
- transport: str = "rest",
116
- source: Optional[str] = None,
117
- generated_by: Optional[str] = None,
118
- ) -> str:
119
- return build_user_agent(
120
- mode=mode,
121
- http_library=f"httpx/{_package_version('httpx')}",
122
- host=host,
123
- transport=transport,
124
- source=source,
125
- generated_by=generated_by,
126
- )
typecast/async_client.py CHANGED
@@ -1,18 +1,14 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
- import sys
5
4
  from pathlib import Path
6
- from typing import TYPE_CHECKING, Any, AsyncIterator, BinaryIO, Optional, Union
5
+ from typing import Any, AsyncIterator, BinaryIO, Optional, Union
7
6
  from urllib.parse import quote
8
7
 
9
- if sys.version_info >= (3, 10): # pragma: no cover - version-specific import
10
- import aiohttp
11
- else: # pragma: no cover
12
- aiohttp = None # type: ignore[assignment]
8
+ import aiohttp
13
9
 
14
10
  from . import conf
15
- from ._user_agent import aiohttp_user_agent, attribution_suffix, httpx_user_agent
11
+ from ._user_agent import aiohttp_user_agent, attribution_suffix
16
12
  from ._voice_clone import (
17
13
  normalize_clone_model,
18
14
  validate_clone_inputs,
@@ -20,8 +16,6 @@ from ._voice_clone import (
20
16
  validate_voice_id,
21
17
  )
22
18
 
23
- if TYPE_CHECKING or sys.version_info < (3, 10): # pragma: no cover
24
- from ._httpx_compat import AiohttpCompatSession, ClientTimeout, FormData
25
19
  from .client import (
26
20
  _guess_audio_mime,
27
21
  _output_with_inferred_format,
@@ -120,21 +114,12 @@ class AsyncTypecast:
120
114
  aiohttp_user_agent(
121
115
  self.host, source=self.source, generated_by=self.generated_by
122
116
  )
123
- if aiohttp
124
- else httpx_user_agent(
125
- self.host,
126
- "async",
127
- source=self.source,
128
- generated_by=self.generated_by,
129
- )
130
117
  )
131
118
  }
132
119
  if self.api_key:
133
120
  headers["X-API-KEY"] = self.api_key
134
121
  self.session = (
135
122
  aiohttp.ClientSession(headers=headers)
136
- if aiohttp
137
- else AiohttpCompatSession(headers=headers)
138
123
  )
139
124
  return self
140
125
 
@@ -288,8 +273,6 @@ class AsyncTypecast:
288
273
  endpoint = "/v1/text-to-speech/stream"
289
274
  stream_timeout = (
290
275
  aiohttp.ClientTimeout(sock_connect=10, sock_read=300)
291
- if aiohttp
292
- else ClientTimeout(sock_connect=10, sock_read=300)
293
276
  )
294
277
  async with self.session.post(
295
278
  f"{self.host}{endpoint}",
@@ -377,7 +360,7 @@ class AsyncTypecast:
377
360
  audio_bytes, filename = validate_clone_inputs(audio, name)
378
361
  model_str = normalize_clone_model(model)
379
362
 
380
- form: Any = aiohttp.FormData() if aiohttp else FormData()
363
+ form: Any = aiohttp.FormData()
381
364
  form.add_field("name", name)
382
365
  form.add_field("model", model_str)
383
366
  form.add_field(
@@ -388,8 +371,6 @@ class AsyncTypecast:
388
371
  )
389
372
  timeout = (
390
373
  aiohttp.ClientTimeout(total=300, connect=10)
391
- if aiohttp
392
- else ClientTimeout(total=300, connect=10)
393
374
  )
394
375
  async with self.session.post(
395
376
  f"{self.host}/v1/custom-voices/instant-clone",
@@ -418,8 +399,6 @@ class AsyncTypecast:
418
399
  validate_custom_voice_id(voice_id)
419
400
  timeout = (
420
401
  aiohttp.ClientTimeout(total=60, connect=10)
421
- if aiohttp
422
- else ClientTimeout(total=60, connect=10)
423
402
  )
424
403
  async with self.session.delete(
425
404
  f"{self.host}/v1/custom-voices/{quote(voice_id, safe='')}",
@@ -441,12 +420,12 @@ class AsyncTypecast:
441
420
  if self.session is None:
442
421
  raise TypecastError("Client session not initialized; use 'async with'.")
443
422
  audio_bytes, filename = validate_clone_inputs(audio, name)
444
- form: Any = aiohttp.FormData() if aiohttp else FormData()
423
+ form: Any = aiohttp.FormData()
445
424
  form.add_field("name", name)
446
425
  form.add_field("language", str(language.value if hasattr(language, "value") else language))
447
426
  form.add_field("model", normalize_clone_model(model))
448
427
  form.add_field("files", audio_bytes, filename=filename, content_type=_guess_audio_mime(filename))
449
- timeout = aiohttp.ClientTimeout(total=300, connect=10) if aiohttp else ClientTimeout(total=300, connect=10)
428
+ timeout = aiohttp.ClientTimeout(total=300, connect=10)
450
429
  async with self.session.post(
451
430
  f"{self.host}/v1/custom-voices/professional-clone",
452
431
  data=form, timeout=timeout, headers=self._request_headers(),
typecast/client.py CHANGED
@@ -1,17 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- import sys
4
3
  from pathlib import Path
5
- from typing import TYPE_CHECKING, Any, BinaryIO, Iterator, Optional, Union
4
+ from typing import Any, BinaryIO, Iterator, Optional, Union
6
5
  from urllib.parse import quote
7
6
 
8
- if sys.version_info >= (3, 10): # pragma: no cover - version-specific import
9
- import requests
10
- else: # pragma: no cover
11
- requests = None # type: ignore[assignment]
7
+ import requests
12
8
 
13
9
  from . import conf
14
- from ._user_agent import attribution_suffix, httpx_user_agent, requests_user_agent
10
+ from ._user_agent import attribution_suffix, requests_user_agent
15
11
  from ._voice_clone import (
16
12
  normalize_clone_model,
17
13
  validate_clone_inputs,
@@ -19,8 +15,6 @@ from ._voice_clone import (
19
15
  validate_voice_id,
20
16
  )
21
17
 
22
- if TYPE_CHECKING or sys.version_info < (3, 10): # pragma: no cover
23
- from ._httpx_compat import RequestsCompatSession
24
18
  from .composer import SpeechComposer
25
19
  from .exceptions import (
26
20
  BadRequestError,
@@ -144,13 +138,11 @@ class Typecast:
144
138
  if session is not None:
145
139
  self.session = session
146
140
  else:
147
- self.session = requests.Session() if requests else RequestsCompatSession()
141
+ self.session = requests.Session()
148
142
  headers = {
149
143
  "Content-Type": "application/json",
150
144
  "User-Agent": (
151
145
  requests_user_agent(self.host, source=source, generated_by=generated_by)
152
- if requests
153
- else httpx_user_agent(self.host, "sync", source=source, generated_by=generated_by)
154
146
  ),
155
147
  }
156
148
  if self.api_key:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: typecast-python
3
- Version: 0.3.15
3
+ Version: 0.4.1
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
@@ -217,8 +217,6 @@ Classifier: Intended Audience :: Developers
217
217
  Classifier: License :: OSI Approved :: Apache Software License
218
218
  Classifier: Operating System :: OS Independent
219
219
  Classifier: Programming Language :: Python :: 3
220
- Classifier: Programming Language :: Python :: 3.8
221
- Classifier: Programming Language :: Python :: 3.9
222
220
  Classifier: Programming Language :: Python :: 3.10
223
221
  Classifier: Programming Language :: Python :: 3.11
224
222
  Classifier: Programming Language :: Python :: 3.12
@@ -226,11 +224,10 @@ Classifier: Programming Language :: Python :: 3.13
226
224
  Classifier: Programming Language :: Python :: 3.14
227
225
  Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
228
226
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
229
- Requires-Python: <3.15,>=3.8
230
- Requires-Dist: aiohttp>=3.14.1; python_version >= '3.10'
231
- Requires-Dist: httpx<1,>=0.28.1; python_version < '3.10'
227
+ Requires-Python: <3.15,>=3.10
228
+ Requires-Dist: aiohttp>=3.14.1
232
229
  Requires-Dist: pydantic>=2.0.0
233
- Requires-Dist: requests>=2.33.0; python_version >= '3.10'
230
+ Requires-Dist: requests>=2.33.0
234
231
  Requires-Dist: typing-extensions>=4.0.0
235
232
  Provides-Extra: dev
236
233
  Requires-Dist: aioresponses>=0.7.6; extra == 'dev'
@@ -256,7 +253,7 @@ Convert text to lifelike speech using AI-powered voices
256
253
  [![PyPI version](https://img.shields.io/pypi/v/typecast-python.svg?style=flat-square)](https://pypi.org/project/typecast-python/)
257
254
  [![coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square)](../docs/coverage-policy.md)
258
255
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg?style=flat-square)](LICENSE)
259
- [![Python](https://img.shields.io/badge/Python-3.8--3.14-3776ab.svg?style=flat-square&logo=python&logoColor=white)](https://www.python.org/)
256
+ [![Python](https://img.shields.io/badge/Python-3.10--3.14-3776ab.svg?style=flat-square&logo=python&logoColor=white)](https://www.python.org/)
260
257
 
261
258
  [Documentation](https://typecast.ai/docs) | [API Reference](https://typecast.ai/docs/api-reference) | [Get API Key](https://typecast.ai/developers/api/api-key)
262
259
 
@@ -284,6 +281,8 @@ Convert text to lifelike speech using AI-powered voices
284
281
 
285
282
  ## Installation
286
283
 
284
+ Python 3.10 through 3.14 is required. Python 3.8 and 3.9 are no longer supported because they are end-of-life. Version 0.3.15 is the last compatible published release. Upgrade Python and recreate your virtual environment before installing 0.4.0. A temporary `typecast-python==0.3.15` pin does not restore security support for an EOL runtime.
285
+
287
286
  ```bash
288
287
  pip install typecast-python
289
288
  ```
@@ -1,9 +1,8 @@
1
1
  typecast/__init__.py,sha256=L6m0Z1jn1UgGjPp9AFiTwhHk9DVV_B4WrLfQDEfbcZI,1093
2
- typecast/_httpx_compat.py,sha256=Xw4AvybiiXyaA2WATBUYHwST8clE8R7R0flLMpdvBC4,6170
3
- typecast/_user_agent.py,sha256=dCiHkvCuTQOz5qYxk4vmxEzL7qZUo9ceKmjXsQUM5wI,3417
2
+ typecast/_user_agent.py,sha256=LZcGNLJ3h2tFk0wj0gS-Ci0krutzH6Aw6UjSLbzOCfU,3032
4
3
  typecast/_voice_clone.py,sha256=oXmi4GXvm1X3hBNumkNpEz8Pl7iDoo85wrKw_dMfqe0,3696
5
- typecast/async_client.py,sha256=a0JLgrxUI4ECdBXQgHvlVxEISFadHZfSsMK5Hrp1wdM,27508
6
- typecast/client.py,sha256=SxaWUReoMSDOQXemkk0bQSpxCbvdhkTKdOhkQMfprkc,26067
4
+ typecast/async_client.py,sha256=5Z_32D7EeRUU1K7o8Me7yZpxDiVY-omdMpBrtBumUeA,26468
5
+ typecast/client.py,sha256=umaDp0LJllVgjYnsOuLXJXBe0sxEuW0XVqATDxer5WM,25570
7
6
  typecast/composer.py,sha256=pkRSVIv99uuAwPyOXw7KSW7NAkqvAUK4hCS5pmBb7bA,7313
8
7
  typecast/conf.py,sha256=yfOHYZDvIshWWMriN5wE0GFPR8AX3zv4Rg2655OvA9Q,724
9
8
  typecast/exceptions.py,sha256=Y0ZzYebe8zOSOSAHbXfKR0G_RJgdmZXxi15Z7ZxPLIk,1568
@@ -13,7 +12,7 @@ typecast/models/error.py,sha256=XomIjx7jvlCjItqzJuCAT4mXC9jwTjxR8lLDUk6P8KA,152
13
12
  typecast/models/subscription.py,sha256=pUn8_GB_03Na0eKFCzgsTkOEs5WTFu-lAdv3NYYxX94,1049
14
13
  typecast/models/tts.py,sha256=ejwBwJvwfwu-X4SJD1WEO2XjOmG6jcu7_gxgxyoMiGs,17027
15
14
  typecast/models/voices.py,sha256=L3MmlVz468_iv8t25TOAWlpQrcB9L8Wh9LGfA7sXcnc,3531
16
- typecast_python-0.3.15.dist-info/METADATA,sha256=aX5o05wOLVOEpyilz50eO0NIWkaZx-I8H3-MAslX3Vc,25933
17
- typecast_python-0.3.15.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
18
- typecast_python-0.3.15.dist-info/licenses/LICENSE,sha256=HvtJ-S89uUkuYmt-OvVk4MRxmzwtbn84__qJtSrGU2Q,11348
19
- typecast_python-0.3.15.dist-info/RECORD,,
15
+ typecast_python-0.4.1.dist-info/METADATA,sha256=xD1X7hjDEKPgrOqubPA4j0Y8n5ubSRRh2j47BEBzZl8,26068
16
+ typecast_python-0.4.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
17
+ typecast_python-0.4.1.dist-info/licenses/LICENSE,sha256=HvtJ-S89uUkuYmt-OvVk4MRxmzwtbn84__qJtSrGU2Q,11348
18
+ typecast_python-0.4.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
typecast/_httpx_compat.py DELETED
@@ -1,191 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any, AsyncIterator, Dict, Iterator, List, Optional, Tuple
4
-
5
- import httpx
6
-
7
-
8
- def _timeout(value: Any) -> Any:
9
- if isinstance(value, ClientTimeout):
10
- total = value.total or value.sock_read or 5.0
11
- return httpx.Timeout(
12
- total, connect=value.connect or value.sock_connect or total
13
- )
14
- if isinstance(value, tuple):
15
- return httpx.Timeout(value[1], connect=value[0])
16
- return value
17
-
18
-
19
- def _headers(defaults: Dict[str, str], supplied: Optional[dict]) -> Dict[str, str]:
20
- headers = dict(defaults)
21
- for key, value in (supplied or {}).items():
22
- if value is None:
23
- headers.pop(key, None)
24
- else:
25
- headers[key] = value
26
- return headers
27
-
28
-
29
- class RequestsCompatResponse:
30
- def __init__(self, response: httpx.Response):
31
- self._response = response
32
-
33
- def __getattr__(self, name: str) -> Any:
34
- return getattr(self._response, name)
35
-
36
- def iter_content(self, chunk_size: int) -> Iterator[bytes]:
37
- return self._response.iter_bytes(chunk_size)
38
-
39
- def close(self) -> None:
40
- self._response.close()
41
-
42
-
43
- class RequestsCompatSession:
44
- def __init__(self, client: Optional[httpx.Client] = None):
45
- self._client = client or httpx.Client(timeout=None)
46
- self.headers: Dict[str, str] = {}
47
-
48
- def _request(self, method: str, url: str, **kwargs: Any) -> RequestsCompatResponse:
49
- stream = kwargs.pop("stream", False)
50
- kwargs["headers"] = _headers(self.headers, kwargs.get("headers"))
51
- if "timeout" in kwargs:
52
- kwargs["timeout"] = _timeout(kwargs["timeout"])
53
- if stream:
54
- request = self._client.build_request(method, url, **kwargs)
55
- response = self._client.send(request, stream=True)
56
- else:
57
- response = self._client.request(method, url, **kwargs)
58
- return RequestsCompatResponse(response)
59
-
60
- def get(self, url: str, **kwargs: Any) -> RequestsCompatResponse:
61
- return self._request("GET", url, **kwargs)
62
-
63
- def post(self, url: str, **kwargs: Any) -> RequestsCompatResponse:
64
- return self._request("POST", url, **kwargs)
65
-
66
- def delete(self, url: str, **kwargs: Any) -> RequestsCompatResponse:
67
- return self._request("DELETE", url, **kwargs)
68
-
69
- def close(self) -> None:
70
- self._client.close()
71
-
72
-
73
- class ClientTimeout:
74
- def __init__(
75
- self,
76
- *,
77
- total: Optional[float] = None,
78
- connect: Optional[float] = None,
79
- sock_connect: Optional[float] = None,
80
- sock_read: Optional[float] = None,
81
- ):
82
- self.total = total
83
- self.connect = connect
84
- self.sock_connect = sock_connect
85
- self.sock_read = sock_read
86
-
87
-
88
- class FormData:
89
- def __init__(self):
90
- self.fields: List[Tuple[str, Any, Optional[str], Optional[str]]] = []
91
-
92
- def add_field(
93
- self,
94
- name: str,
95
- value: Any,
96
- *,
97
- filename: Optional[str] = None,
98
- content_type: Optional[str] = None,
99
- ) -> None:
100
- self.fields.append((name, value, filename, content_type))
101
-
102
-
103
- class _AsyncContent:
104
- def __init__(self, response: httpx.Response):
105
- self._response = response
106
-
107
- async def iter_chunked(self, chunk_size: int) -> AsyncIterator[bytes]:
108
- async for chunk in self._response.aiter_bytes(chunk_size):
109
- yield chunk
110
-
111
-
112
- class AiohttpCompatResponse:
113
- def __init__(self, response: httpx.Response):
114
- self._response = response
115
- self.status = response.status_code
116
- self.headers = response.headers
117
- self.content = _AsyncContent(response)
118
-
119
- async def read(self) -> bytes:
120
- return await self._response.aread()
121
-
122
- async def text(self) -> str:
123
- await self._response.aread()
124
- return self._response.text
125
-
126
- async def json(self) -> Any:
127
- await self._response.aread()
128
- return self._response.json()
129
-
130
- async def close(self) -> None:
131
- await self._response.aclose()
132
-
133
-
134
- class _AsyncRequestContext:
135
- def __init__(self, request: Any):
136
- self._request = request
137
- self._response: Optional[AiohttpCompatResponse] = None
138
-
139
- async def __aenter__(self) -> AiohttpCompatResponse:
140
- response = await self._request
141
- self._response = response
142
- return response
143
-
144
- async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
145
- if self._response:
146
- await self._response.close()
147
-
148
-
149
- class AiohttpCompatSession:
150
- def __init__(
151
- self,
152
- headers: Optional[dict] = None,
153
- client: Optional[httpx.AsyncClient] = None,
154
- ):
155
- self._client = client or httpx.AsyncClient(timeout=300)
156
- self.headers = dict(headers or {})
157
-
158
- async def _request(
159
- self, method: str, url: str, **kwargs: Any
160
- ) -> AiohttpCompatResponse:
161
- kwargs["headers"] = _headers(self.headers, kwargs.get("headers"))
162
- if "timeout" in kwargs:
163
- kwargs["timeout"] = _timeout(kwargs["timeout"])
164
- form = kwargs.get("data")
165
- if isinstance(form, FormData):
166
- kwargs.pop("data")
167
- kwargs["data"] = {
168
- name: value
169
- for name, value, filename, _ in form.fields
170
- if filename is None
171
- }
172
- kwargs["files"] = [
173
- (name, (filename, value, content_type))
174
- for name, value, filename, content_type in form.fields
175
- if filename is not None
176
- ]
177
- request = self._client.build_request(method, url, **kwargs)
178
- response = await self._client.send(request, stream=True)
179
- return AiohttpCompatResponse(response)
180
-
181
- def get(self, url: str, **kwargs: Any) -> _AsyncRequestContext:
182
- return _AsyncRequestContext(self._request("GET", url, **kwargs))
183
-
184
- def post(self, url: str, **kwargs: Any) -> _AsyncRequestContext:
185
- return _AsyncRequestContext(self._request("POST", url, **kwargs))
186
-
187
- def delete(self, url: str, **kwargs: Any) -> _AsyncRequestContext:
188
- return _AsyncRequestContext(self._request("DELETE", url, **kwargs))
189
-
190
- async def close(self) -> None:
191
- await self._client.aclose()