typecast-python 0.3.8__py3-none-any.whl → 0.3.10__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/async_client.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  from pathlib import Path
3
5
  from typing import AsyncIterator, BinaryIO, Optional, Union
@@ -208,7 +210,8 @@ class AsyncTypecast:
208
210
  seed=seed,
209
211
  )
210
212
  )
211
- await asyncio.to_thread(output_path.write_bytes, response.audio_data)
213
+ loop = asyncio.get_running_loop()
214
+ await loop.run_in_executor(None, output_path.write_bytes, response.audio_data)
212
215
  return response
213
216
 
214
217
  async def text_to_speech_stream(
@@ -255,7 +258,7 @@ class AsyncTypecast:
255
258
  error_text = await response.text()
256
259
  self._handle_error(response.status, error_text)
257
260
 
258
- async for chunk in response.content.iter_chunked(chunk_size):
261
+ async for chunk in response.content.iter_chunked(chunk_size): # pragma: no branch
259
262
  yield chunk
260
263
 
261
264
  async def text_to_speech_with_timestamps(
typecast/client.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pathlib import Path
2
4
  from typing import BinaryIO, Iterator, Optional, Union
3
5
  from urllib.parse import quote
@@ -195,10 +197,11 @@ class Typecast:
195
197
  if response.status_code != 200:
196
198
  self._handle_error(response.status_code, response.text)
197
199
 
200
+ content_type = response.headers.get("Content-Type", "audio/wav").lower()
198
201
  return TTSResponse(
199
202
  audio_data=response.content,
200
203
  duration=response.headers.get("X-Audio-Duration", 0),
201
- format=response.headers.get("Content-Type", "audio/wav").split("/")[-1],
204
+ format="mp3" if "mp3" in content_type or "mpeg" in content_type else "wav",
202
205
  )
203
206
 
204
207
  def compose_speech(self) -> SpeechComposer:
@@ -207,7 +210,23 @@ class Typecast:
207
210
  Text passed to ``say()`` may include pause markup such as ``<|0.3s|>``.
208
211
  ``pause(seconds)`` also uses seconds, e.g. ``0.3`` for 300 ms.
209
212
  """
210
- return SpeechComposer(self.text_to_speech)
213
+ return SpeechComposer(self.compose_text_to_speech)
214
+
215
+ def compose_text_to_speech(self, segments: list[dict]) -> TTSResponse:
216
+ response = self.session.post(
217
+ f"{self.host}/v1/text-to-speech/compose",
218
+ json={"segments": segments},
219
+ headers=self._request_headers(),
220
+ timeout=(10, 300),
221
+ )
222
+ if response.status_code != 200:
223
+ self._handle_error(response.status_code, response.text)
224
+ content_type = response.headers.get("Content-Type", "audio/wav").lower()
225
+ return TTSResponse(
226
+ audio_data=response.content,
227
+ duration=response.headers.get("X-Audio-Duration", 0),
228
+ format="mp3" if "mp3" in content_type or "mpeg" in content_type else "wav",
229
+ )
211
230
 
212
231
  def generate_to_file(
213
232
  self,
typecast/composer.py CHANGED
@@ -1,10 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- import io
4
3
  import math
5
4
  import re
6
- import struct
7
- import wave
8
5
  from dataclasses import dataclass
9
6
  from typing import Callable, Literal, Optional, Union
10
7
 
@@ -31,19 +28,6 @@ class _SpeechPart:
31
28
  settings: "_ComposerSettings"
32
29
 
33
30
 
34
- @dataclass(frozen=True)
35
- class _WavSpec:
36
- sample_rate: int
37
- channels: int
38
- sample_width: int
39
-
40
-
41
- @dataclass(frozen=True)
42
- class _ParsedWav:
43
- spec: _WavSpec
44
- samples: list[int]
45
-
46
-
47
31
  @dataclass(frozen=True)
48
32
  class _ComposerSettings:
49
33
  voice_id: Optional[str] = None
@@ -55,8 +39,8 @@ class _ComposerSettings:
55
39
 
56
40
 
57
41
  class SpeechComposer:
58
- def __init__(self, text_to_speech: Callable[[TTSRequest], TTSResponse]):
59
- self._text_to_speech = text_to_speech
42
+ def __init__(self, compose: Callable[[list[dict]], TTSResponse]):
43
+ self._compose = compose
60
44
  self._defaults = _ComposerSettings()
61
45
  self._parts: list[Union[_SpeechPart, _PausePart]] = []
62
46
 
@@ -124,41 +108,29 @@ class SpeechComposer:
124
108
  if not any(isinstance(part, _SpeechPart) for part in plan):
125
109
  raise ValueError("at least one speech segment is required")
126
110
 
127
- output_format = (
128
- self._defaults.output.audio_format if self._defaults.output else "wav"
129
- )
111
+ formats = {
112
+ part.settings.output.audio_format
113
+ for part in plan
114
+ if isinstance(part, _SpeechPart)
115
+ and part.settings.output
116
+ and part.settings.output.audio_format
117
+ }
118
+ if len(formats) > 1:
119
+ raise ValueError("composed speech segments must use one audio format")
120
+ output_format = next(iter(formats), "wav")
130
121
  if output_format not in ("wav", "mp3"):
131
- raise ValueError(f"unsupported composed speech output format: {output_format}")
122
+ raise ValueError(
123
+ f"unsupported composed speech output format: {output_format}"
124
+ )
132
125
 
133
- wav_spec: Optional[_WavSpec] = None
134
- output_samples: list[int] = []
126
+ segments: list[dict] = []
135
127
  for part in plan:
136
128
  if isinstance(part, _PausePart):
137
- if wav_spec is None:
138
- raise ValueError("pause cannot be the first composed part")
139
- output_samples.extend(
140
- [0] * _seconds_to_samples(part.seconds, wav_spec.sample_rate)
141
- )
129
+ segments.append({"type": "pause", "duration_seconds": part.seconds})
142
130
  continue
143
-
144
- request = _settings_to_request(part.text, part.settings)
145
- response = self._text_to_speech(request)
146
- wav = _parse_wav(response.audio_data)
147
- if wav_spec is not None and wav.spec != wav_spec:
148
- raise ValueError("all composed WAV segments must use the same PCM format")
149
- wav_spec = wav.spec
150
- output_samples.extend(_trim_silence(wav.samples))
151
-
152
- final_spec = wav_spec
153
- assert final_spec is not None
154
- wav_bytes = _encode_wav(output_samples, final_spec)
155
- if output_format == "mp3":
156
- raise ValueError("ffmpeg is required to encode composed speech as mp3")
157
- return TTSResponse(
158
- audio_data=wav_bytes,
159
- duration=len(output_samples) / final_spec.sample_rate,
160
- format="wav",
161
- )
131
+ request = _settings_to_request(part.text, part.settings, output_format)
132
+ segments.append({"type": "tts", **request.model_dump(exclude_none=True)})
133
+ return self._compose(segments)
162
134
 
163
135
  def _build_plan(self) -> list[Union[_SpeechPart, _PausePart]]:
164
136
  plan: list[Union[_SpeechPart, _PausePart]] = []
@@ -173,7 +145,9 @@ class SpeechComposer:
173
145
  if not parsed.text.strip():
174
146
  continue
175
147
  if not part.settings.voice_id:
176
- raise ValueError("voice_id is required for composed speech segments")
148
+ raise ValueError(
149
+ "voice_id is required for composed speech segments"
150
+ )
177
151
  if not part.settings.model:
178
152
  raise ValueError("model is required for composed speech segments")
179
153
  plan.append(_SpeechPart(text=parsed.text, settings=part.settings))
@@ -184,10 +158,14 @@ def parse_pause_markup(text: str) -> list[Union[_TextPart, _PausePart]]:
184
158
  parts: list[Union[_TextPart, _PausePart]] = []
185
159
  last_index = 0
186
160
  for match in _PAUSE_TOKEN.finditer(text):
187
- if match.start() > last_index:
188
- parts.append(_TextPart(kind="text", text=text[last_index : match.start()]))
189
- parts.append(_PausePart(kind="pause", seconds=float(match.group(1))))
190
- last_index = match.end()
161
+ seconds = float(match.group(1))
162
+ if math.isfinite(seconds) and seconds > 0:
163
+ if match.start() > last_index:
164
+ parts.append(
165
+ _TextPart(kind="text", text=text[last_index : match.start()])
166
+ )
167
+ parts.append(_PausePart(kind="pause", seconds=seconds))
168
+ last_index = match.end()
191
169
  if last_index < len(text):
192
170
  parts.append(_TextPart(kind="text", text=text[last_index:]))
193
171
  return parts
@@ -207,69 +185,33 @@ def _merge_settings(
207
185
  )
208
186
 
209
187
 
210
- def _merge_output(base: Optional[Output], override: Optional[Output]) -> Optional[Output]:
188
+ def _merge_output(
189
+ base: Optional[Output], override: Optional[Output]
190
+ ) -> Optional[Output]:
211
191
  if base is None and override is None:
212
192
  return None
213
- data = base.model_dump(exclude_none=True, exclude_unset=True) if base is not None else {}
193
+ data = (
194
+ base.model_dump(exclude_none=True, exclude_unset=True)
195
+ if base is not None
196
+ else {}
197
+ )
214
198
  if override is not None:
215
199
  data.update(override.model_dump(exclude_none=True, exclude_unset=True))
216
200
  return Output(**data)
217
201
 
218
202
 
219
- def _settings_to_request(text: str, settings: _ComposerSettings) -> TTSRequest:
220
- output = _merge_output(settings.output, Output(audio_format="wav"))
203
+ def _settings_to_request(
204
+ text: str, settings: _ComposerSettings, output_format: str
205
+ ) -> TTSRequest:
206
+ output = _merge_output(settings.output, Output(audio_format=output_format))
221
207
  return TTSRequest(
222
208
  text=text,
223
209
  voice_id=settings.voice_id or "",
224
- model=settings.model if isinstance(settings.model, TTSModel) else TTSModel(settings.model),
210
+ model=settings.model
211
+ if isinstance(settings.model, TTSModel)
212
+ else TTSModel(settings.model),
225
213
  language=settings.language,
226
214
  prompt=settings.prompt,
227
215
  output=output,
228
216
  seed=settings.seed,
229
217
  )
230
-
231
-
232
- def _parse_wav(data: bytes) -> _ParsedWav:
233
- try:
234
- with wave.open(io.BytesIO(data), "rb") as reader:
235
- channels = reader.getnchannels()
236
- sample_width = reader.getsampwidth()
237
- sample_rate = reader.getframerate()
238
- if channels != 1 or sample_width != 2:
239
- raise ValueError("only mono 16-bit PCM WAV is supported for composed speech")
240
- frames = reader.readframes(reader.getnframes())
241
- except (EOFError, wave.Error) as exc:
242
- raise ValueError("unsupported WAV data") from exc
243
- samples = [
244
- struct.unpack("<h", frames[offset : offset + 2])[0]
245
- for offset in range(0, len(frames), 2)
246
- ]
247
- return _ParsedWav(
248
- spec=_WavSpec(sample_rate=sample_rate, channels=channels, sample_width=sample_width),
249
- samples=samples,
250
- )
251
-
252
-
253
- def _encode_wav(samples: list[int], spec: _WavSpec) -> bytes:
254
- payload = b"".join(struct.pack("<h", sample) for sample in samples)
255
- out = io.BytesIO()
256
- with wave.open(out, "wb") as writer:
257
- writer.setnchannels(spec.channels)
258
- writer.setsampwidth(spec.sample_width)
259
- writer.setframerate(spec.sample_rate)
260
- writer.writeframes(payload)
261
- return out.getvalue()
262
-
263
-
264
- def _trim_silence(samples: list[int]) -> list[int]:
265
- start = 0
266
- end = len(samples)
267
- while start < end and samples[start] == 0:
268
- start += 1
269
- while end > start and samples[end - 1] == 0:
270
- end -= 1
271
- return samples[start:end]
272
-
273
-
274
- def _seconds_to_samples(seconds: float, sample_rate: int) -> int:
275
- return round(seconds * sample_rate)
typecast/models/tts.py CHANGED
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
- from typing import Literal, Optional, Union
4
+ from typing import List, Literal, Optional, Union
3
5
 
4
6
  from pydantic import BaseModel, ConfigDict, Field, model_validator
5
7
 
@@ -330,7 +332,7 @@ def _group_into_cues(
330
332
  word_mode=True: parts are joined with a single space.
331
333
  word_mode=False: parts are concatenated directly.
332
334
 
333
- Returns list[(text, start, end)] tuples.
335
+ Returns List[(text, start, end)] tuples.
334
336
  """
335
337
  cues = []
336
338
  cur_text_parts = []
@@ -404,11 +406,11 @@ class TTSWithTimestampsResponse(BaseModel):
404
406
  audio: str = Field(description="Base64-encoded audio bytes.")
405
407
  audio_format: Literal["wav", "mp3"] = Field(description="Audio encoding format.")
406
408
  audio_duration: float = Field(description="Length of audio in seconds.")
407
- words: Optional[list[AlignmentSegmentWord]] = Field(
409
+ words: Optional[List[AlignmentSegmentWord]] = Field(
408
410
  default=None,
409
411
  description="Word-level timestamps; null when granularity=char.",
410
412
  )
411
- characters: Optional[list[AlignmentSegmentCharacter]] = Field(
413
+ characters: Optional[List[AlignmentSegmentCharacter]] = Field(
412
414
  default=None,
413
415
  description="Character-level timestamps; null when granularity=word.",
414
416
  )
typecast/models/voices.py CHANGED
@@ -1,5 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
- from typing import Optional
4
+ from typing import List, Optional
3
5
 
4
6
  from pydantic import BaseModel, Field
5
7
 
@@ -12,7 +14,7 @@ class VoicesResponse(BaseModel):
12
14
  voice_id: str
13
15
  voice_name: str
14
16
  model: str
15
- emotions: list[str]
17
+ emotions: List[str]
16
18
 
17
19
 
18
20
  class GenderEnum(str, Enum):
@@ -54,7 +56,7 @@ class ModelInfo(BaseModel):
54
56
  """Model information with supported emotions"""
55
57
 
56
58
  version: TTSModel
57
- emotions: list[str]
59
+ emotions: List[str]
58
60
 
59
61
 
60
62
  class VoiceV2Response(BaseModel):
@@ -62,10 +64,10 @@ class VoiceV2Response(BaseModel):
62
64
 
63
65
  voice_id: str
64
66
  voice_name: str
65
- models: list[ModelInfo]
67
+ models: List[ModelInfo]
66
68
  gender: Optional[GenderEnum] = None
67
69
  age: Optional[AgeEnum] = None
68
- use_cases: Optional[list[str]] = None
70
+ use_cases: Optional[List[str]] = None
69
71
 
70
72
 
71
73
  class RecommendedVoice(BaseModel):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: typecast-python
3
- Version: 0.3.8
3
+ Version: 0.3.10
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,26 +217,30 @@ 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
+ Classifier: Programming Language :: Python :: 3.10
220
223
  Classifier: Programming Language :: Python :: 3.11
221
224
  Classifier: Programming Language :: Python :: 3.12
222
225
  Classifier: Programming Language :: Python :: 3.13
226
+ Classifier: Programming Language :: Python :: 3.14
223
227
  Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
224
228
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
225
- Requires-Python: >=3.11
226
- Requires-Dist: aiohttp>=3.14.0
229
+ Requires-Python: <3.15,>=3.8
230
+ Requires-Dist: aiohttp>=3.9.0
227
231
  Requires-Dist: pydantic>=2.0.0
228
232
  Requires-Dist: requests>=2.28.0
229
233
  Requires-Dist: typing-extensions>=4.0.0
230
234
  Provides-Extra: dev
231
235
  Requires-Dist: aioresponses>=0.7.6; extra == 'dev'
232
- Requires-Dist: black>=23.0.0; extra == 'dev'
233
- Requires-Dist: flake8>=6.0.0; extra == 'dev'
236
+ Requires-Dist: black<25.0.0,>=23.0.0; extra == 'dev'
237
+ Requires-Dist: flake8<6.0.0,>=5.0.0; extra == 'dev'
234
238
  Requires-Dist: isort>=5.0.0; extra == 'dev'
235
- Requires-Dist: mypy>=1.0.0; extra == 'dev'
236
- Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
237
- Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
239
+ Requires-Dist: mypy<1.11.0,>=1.0.0; extra == 'dev'
240
+ Requires-Dist: pytest-asyncio<1.0.0,>=0.21.0; extra == 'dev'
241
+ Requires-Dist: pytest-cov<6.0.0,>=5.0.0; extra == 'dev'
238
242
  Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
239
- Requires-Dist: pytest>=7.0.0; extra == 'dev'
243
+ Requires-Dist: pytest<9.0.0,>=7.0.0; extra == 'dev'
240
244
  Description-Content-Type: text/markdown
241
245
 
242
246
  <div align="center">
@@ -250,7 +254,7 @@ Convert text to lifelike speech using AI-powered voices
250
254
  [![PyPI version](https://img.shields.io/pypi/v/typecast-python.svg?style=flat-square)](https://pypi.org/project/typecast-python/)
251
255
  [![coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square)](../docs/coverage-policy.md)
252
256
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg?style=flat-square)](LICENSE)
253
- [![Python](https://img.shields.io/badge/Python-3.9+-3776ab.svg?style=flat-square&logo=python&logoColor=white)](https://www.python.org/)
257
+ [![Python](https://img.shields.io/badge/Python-3.8--3.14-3776ab.svg?style=flat-square&logo=python&logoColor=white)](https://www.python.org/)
254
258
 
255
259
  [Documentation](https://typecast.ai/docs) | [API Reference](https://typecast.ai/docs/api-reference) | [Get API Key](https://typecast.ai/developers/api/api-key)
256
260
 
@@ -0,0 +1,18 @@
1
+ typecast/__init__.py,sha256=L6m0Z1jn1UgGjPp9AFiTwhHk9DVV_B4WrLfQDEfbcZI,1093
2
+ typecast/_user_agent.py,sha256=R_0eSKpuP9jLp_wDJcJGFvFtUh9BZwEgsT1buVeHRAA,1786
3
+ typecast/_voice_clone.py,sha256=TN2tbB3b5lC5uFCBnbERhz37bNJlbv6VZ-vj70EfTs4,3464
4
+ typecast/async_client.py,sha256=Gidl-gj0zGMzZCPzmMRcr2GiYFi0cBmZOwrLhSVRL_Y,21700
5
+ typecast/client.py,sha256=Ac6hcginhJheh6CcstzaMtyXzRbnSfUerI8rt6l_f58,21695
6
+ typecast/composer.py,sha256=pkRSVIv99uuAwPyOXw7KSW7NAkqvAUK4hCS5pmBb7bA,7313
7
+ typecast/conf.py,sha256=yfOHYZDvIshWWMriN5wE0GFPR8AX3zv4Rg2655OvA9Q,724
8
+ typecast/exceptions.py,sha256=Y0ZzYebe8zOSOSAHbXfKR0G_RJgdmZXxi15Z7ZxPLIk,1568
9
+ typecast/utils.py,sha256=XuNuX7gW8_CGKqZ-cv_tKlPVMPBluAYJBw2clwmjIMI,708
10
+ typecast/models/__init__.py,sha256=OmaFdGpH6gK7EJyNgvbwqZHXVxjnWKJzynn7D3pfULo,1249
11
+ typecast/models/error.py,sha256=XomIjx7jvlCjItqzJuCAT4mXC9jwTjxR8lLDUk6P8KA,152
12
+ typecast/models/subscription.py,sha256=EIaAAo3cCRw8LYT_O6D9AVwxqIHrWCijzl4UTx7FZB8,894
13
+ typecast/models/tts.py,sha256=xiO1e936vFr7vZqUErTEhexydoaMem6BcY_x1A1zFow,16495
14
+ typecast/models/voices.py,sha256=3UeBLwAlaNbQQUrt7FO18zqczLoP7jmi2x68yaUtllU,2687
15
+ typecast_python-0.3.10.dist-info/METADATA,sha256=z8W8o3SeRtx4C9PWgEvRCJ3fOcfoVTy2Utp0mqUvkOs,25786
16
+ typecast_python-0.3.10.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
17
+ typecast_python-0.3.10.dist-info/licenses/LICENSE,sha256=HvtJ-S89uUkuYmt-OvVk4MRxmzwtbn84__qJtSrGU2Q,11348
18
+ typecast_python-0.3.10.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.31.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,18 +0,0 @@
1
- typecast/__init__.py,sha256=L6m0Z1jn1UgGjPp9AFiTwhHk9DVV_B4WrLfQDEfbcZI,1093
2
- typecast/_user_agent.py,sha256=R_0eSKpuP9jLp_wDJcJGFvFtUh9BZwEgsT1buVeHRAA,1786
3
- typecast/_voice_clone.py,sha256=TN2tbB3b5lC5uFCBnbERhz37bNJlbv6VZ-vj70EfTs4,3464
4
- typecast/async_client.py,sha256=H3hCoGb6h0UyrnYUlQ0lhnpaCV-hxnZtsQKYlqaGPSk,21592
5
- typecast/client.py,sha256=VPZVRnB9_T4VkSlrHpKLRahhEaxIF05XUAk-skqYKz8,20850
6
- typecast/composer.py,sha256=r1kpRGlSJ1s4C1PkE7yIpGwnKtxYfIdFSLWaf8v1RAQ,9422
7
- typecast/conf.py,sha256=yfOHYZDvIshWWMriN5wE0GFPR8AX3zv4Rg2655OvA9Q,724
8
- typecast/exceptions.py,sha256=Y0ZzYebe8zOSOSAHbXfKR0G_RJgdmZXxi15Z7ZxPLIk,1568
9
- typecast/utils.py,sha256=XuNuX7gW8_CGKqZ-cv_tKlPVMPBluAYJBw2clwmjIMI,708
10
- typecast/models/__init__.py,sha256=OmaFdGpH6gK7EJyNgvbwqZHXVxjnWKJzynn7D3pfULo,1249
11
- typecast/models/error.py,sha256=XomIjx7jvlCjItqzJuCAT4mXC9jwTjxR8lLDUk6P8KA,152
12
- typecast/models/subscription.py,sha256=EIaAAo3cCRw8LYT_O6D9AVwxqIHrWCijzl4UTx7FZB8,894
13
- typecast/models/tts.py,sha256=W2Bp9M-xPJPODlk89P9CnLA2fwb__4SR4yoMkG_O8kA,16453
14
- typecast/models/voices.py,sha256=3yJD78QbZe3pTZv61GnB8up8qlIHKd1iSe5Bow-4ocU,2645
15
- typecast_python-0.3.8.dist-info/METADATA,sha256=5Gbl-QxbRCGnqUwn_8Yr2xssRMZD_3GvZghgWsUxByg,25530
16
- typecast_python-0.3.8.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
17
- typecast_python-0.3.8.dist-info/licenses/LICENSE,sha256=HvtJ-S89uUkuYmt-OvVk4MRxmzwtbn84__qJtSrGU2Q,11348
18
- typecast_python-0.3.8.dist-info/RECORD,,