sarvamai 0.1.13a2__py3-none-any.whl → 0.1.15__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.
@@ -0,0 +1,505 @@
1
+ import asyncio
2
+ import mimetypes
3
+ import os
4
+ import time
5
+ import typing
6
+ import httpx
7
+ from http import HTTPStatus
8
+
9
+ from ..types import JobStatusV1Response
10
+
11
+ if typing.TYPE_CHECKING:
12
+ from .client import (
13
+ AsyncSpeechToTextTranslateJobClient,
14
+ SpeechToTextTranslateJobClient,
15
+ )
16
+
17
+
18
+ class AsyncSpeechToTextTranslateJob:
19
+ def __init__(
20
+ self,
21
+ job_id: str,
22
+ client: "AsyncSpeechToTextTranslateJobClient",
23
+ ):
24
+ """
25
+ Initialise the asynchronous speech-to-text-translate-translate job.
26
+
27
+ Parameters
28
+ ----------
29
+ job_id : str
30
+ The unique job identifier returned from a previous job initialisation.
31
+
32
+ client : AsyncSpeechToTextTranslateJobClient
33
+ The async client instance used to create the job.
34
+
35
+ !!! important
36
+ This must be the **same client instance** that was used to initialise
37
+ the job originally, as it contains the subscription key and configuration
38
+ required to authenticate and manage the job.
39
+
40
+ """
41
+ self._job_id = job_id
42
+ self._client = client
43
+
44
+ @property
45
+ def job_id(self) -> str:
46
+ """
47
+ Returns the job ID associated with this job instance.
48
+
49
+ Returns
50
+ -------
51
+ str
52
+ """
53
+ return self._job_id
54
+
55
+ async def upload_files(
56
+ self, file_paths: typing.Sequence[str], timeout: float = 60.0
57
+ ) -> bool:
58
+ """
59
+ Upload input audio files for the speech-to-text-translate job.
60
+
61
+ Parameters
62
+ ----------
63
+ file_paths : Sequence[str]
64
+ List of full paths to local audio files.
65
+
66
+ timeout : float, optional
67
+ The maximum time to wait for the upload to complete (in seconds),
68
+ by default 60.0
69
+ Returns
70
+ -------
71
+ bool
72
+ True if all files are uploaded successfully.
73
+ """
74
+ upload_links = await self._client.get_upload_links(
75
+ job_id=self._job_id,
76
+ files=[os.path.basename(p) for p in file_paths],
77
+ )
78
+ client_timeout = httpx.Timeout(timeout=timeout)
79
+ async with httpx.AsyncClient(timeout=client_timeout) as session:
80
+ for path in file_paths:
81
+ file_name = os.path.basename(path)
82
+ url = upload_links.upload_urls[file_name].file_url
83
+ with open(path, "rb") as f:
84
+ response = await session.put(
85
+ url,
86
+ content=f.read(),
87
+ headers={
88
+ "x-ms-blob-type": "BlockBlob",
89
+ "Content-Type": "audio/wav",
90
+ },
91
+ )
92
+ if (
93
+ response.status_code > HTTPStatus.IM_USED
94
+ or response.status_code < HTTPStatus.OK
95
+ ):
96
+ raise RuntimeError(
97
+ f"Upload failed for {file_name}: {response.status_code}"
98
+ )
99
+ return True
100
+
101
+ async def wait_until_complete(
102
+ self, poll_interval: int = 5, timeout: int = 600
103
+ ) -> JobStatusV1Response:
104
+ """
105
+ Polls job status until it completes or fails.
106
+
107
+ Parameters
108
+ ----------
109
+ poll_interval : int, optional
110
+ Time in seconds between polling attempts (default is 5).
111
+
112
+ timeout : int, optional
113
+ Maximum time to wait for completion in seconds (default is 600).
114
+
115
+ Returns
116
+ -------
117
+ JobStatusV1Response
118
+ Final job status.
119
+
120
+ Raises
121
+ ------
122
+ TimeoutError
123
+ If the job does not complete within the given timeout.
124
+ """
125
+ start = asyncio.get_event_loop().time()
126
+ while True:
127
+ status = await self.get_status()
128
+ state = status.job_state.lower()
129
+ if state in {"completed", "failed"}:
130
+ return status
131
+ if asyncio.get_event_loop().time() - start > timeout:
132
+ raise TimeoutError(
133
+ f"Job {self._job_id} did not complete within {timeout} seconds."
134
+ )
135
+ await asyncio.sleep(poll_interval)
136
+
137
+ async def get_output_mappings(self) -> typing.List[typing.Dict[str, str]]:
138
+ """
139
+ Get the mapping of input files to their corresponding output files.
140
+
141
+ Returns
142
+ -------
143
+ List[Dict[str, str]]
144
+ List of mappings with keys 'input_file' and 'output_file'.
145
+ """
146
+ job_status = await self.get_status()
147
+ return [
148
+ {
149
+ "input_file": detail.inputs[0].file_name,
150
+ "output_file": detail.outputs[0].file_name,
151
+ }
152
+ for detail in (job_status.job_details or [])
153
+ if detail.inputs and detail.outputs
154
+ ]
155
+
156
+ async def download_outputs(self, output_dir: str) -> bool:
157
+ """
158
+ Download output files to the specified directory.
159
+
160
+ Parameters
161
+ ----------
162
+ output_dir : str
163
+ Local directory where outputs will be saved.
164
+
165
+ Returns
166
+ -------
167
+ bool
168
+ True if all files downloaded successfully.
169
+
170
+ Raises
171
+ ------
172
+ RuntimeError
173
+ If a file fails to download.
174
+ """
175
+ mappings = await self.get_output_mappings()
176
+ file_names = [m["output_file"] for m in mappings]
177
+ download_links = await self._client.get_download_links(
178
+ job_id=self._job_id, files=file_names
179
+ )
180
+
181
+ os.makedirs(output_dir, exist_ok=True)
182
+ async with httpx.AsyncClient() as session:
183
+ for m in mappings:
184
+ url = download_links.download_urls[m["output_file"]].file_url
185
+ response = await session.get(url)
186
+ if (
187
+ response.status_code > HTTPStatus.IM_USED
188
+ or response.status_code < HTTPStatus.OK
189
+ ):
190
+ raise RuntimeError(
191
+ f"Download failed for {m['output_file']}: {response.status_code}"
192
+ )
193
+ output_path = os.path.join(output_dir, f"{m['input_file']}.json")
194
+ with open(output_path, "wb") as f:
195
+ f.write(response.content)
196
+ return True
197
+
198
+ async def get_status(self) -> JobStatusV1Response:
199
+ """
200
+ Retrieve the current status of the job.
201
+
202
+ Returns
203
+ -------
204
+ JobStatusV1Response
205
+ """
206
+ return await self._client.get_status(self._job_id)
207
+
208
+ async def start(self) -> JobStatusV1Response:
209
+ """
210
+ Start the speech-to-text-translate job processing.
211
+
212
+ Returns
213
+ -------
214
+ JobStatusV1Response
215
+ """
216
+ return await self._client.start(job_id=self._job_id)
217
+
218
+ async def exists(self) -> bool:
219
+ """
220
+ Check if the job exists in the system.
221
+
222
+ Returns
223
+ -------
224
+ bool
225
+ """
226
+ try:
227
+ await self.get_status()
228
+ return True
229
+ except httpx.HTTPStatusError:
230
+ return False
231
+
232
+ async def is_complete(self) -> bool:
233
+ """
234
+ Check if the job is either completed or failed.
235
+
236
+ Returns
237
+ -------
238
+ bool
239
+ """
240
+ state = (await self.get_status()).job_state.lower()
241
+ return state in {"completed", "failed"}
242
+
243
+ async def is_successful(self) -> bool:
244
+ """
245
+ Check if the job completed successfully.
246
+
247
+ Returns
248
+ -------
249
+ bool
250
+ """
251
+ return (await self.get_status()).job_state.lower() == "completed"
252
+
253
+ async def is_failed(self) -> bool:
254
+ """
255
+ Check if the job has failed.
256
+
257
+ Returns
258
+ -------
259
+ bool
260
+ """
261
+ return (await self.get_status()).job_state.lower() == "failed"
262
+
263
+
264
+ class SpeechToTextTranslateJob:
265
+ def __init__(self, job_id: str, client: "SpeechToTextTranslateJobClient"):
266
+ """
267
+ Initialise the synchronous speech-to-text-translate job.
268
+
269
+ Parameters
270
+ ----------
271
+ job_id : str
272
+ The unique job identifier returned from a previous job initialisation.
273
+
274
+ client : SpeechToTextTranslateJobClient
275
+ The client instance used to create the job.
276
+
277
+ !!! important
278
+ This must be the **same client instance** that was used to initialise
279
+ the job originally, as it contains the subscription key and configuration
280
+ required to authenticate and manage the job.
281
+
282
+ """
283
+ self._job_id = job_id
284
+ self._client = client
285
+
286
+ @property
287
+ def job_id(self) -> str:
288
+ """
289
+ Returns the job ID associated with this job instance.
290
+
291
+ Returns
292
+ -------
293
+ str
294
+ """
295
+ return self._job_id
296
+
297
+ def upload_files(
298
+ self, file_paths: typing.Sequence[str], timeout: float = 60.0
299
+ ) -> bool:
300
+ """
301
+ Upload input audio files for the speech-to-text-translate job.
302
+
303
+ Parameters
304
+ ----------
305
+ file_paths : Sequence[str]
306
+ List of full paths to local audio files.
307
+
308
+ timeout : float, optional
309
+ The maximum time to wait for the upload to complete (in seconds),
310
+ by default 60.0
311
+
312
+ Returns
313
+ -------
314
+ bool
315
+ True if all files are uploaded successfully.
316
+ """
317
+ upload_links = self._client.get_upload_links(
318
+ job_id=self._job_id, files=[os.path.basename(p) for p in file_paths]
319
+ )
320
+ client_timeout = httpx.Timeout(timeout=timeout)
321
+ with httpx.Client(timeout=client_timeout) as client:
322
+ for path in file_paths:
323
+ file_name = os.path.basename(path)
324
+ url = upload_links.upload_urls[file_name].file_url
325
+ content_type, _ = mimetypes.guess_type(path)
326
+ if content_type is None:
327
+ content_type = "audio/wav"
328
+ with open(path, "rb") as f:
329
+ response = client.put(
330
+ url,
331
+ content=f,
332
+ headers={
333
+ "x-ms-blob-type": "BlockBlob",
334
+ "Content-Type": content_type,
335
+ },
336
+ )
337
+ if (
338
+ response.status_code > HTTPStatus.IM_USED
339
+ or response.status_code < HTTPStatus.OK
340
+ ):
341
+ raise RuntimeError(
342
+ f"Upload failed for {file_name}: {response.status_code}"
343
+ )
344
+ return True
345
+
346
+ def wait_until_complete(
347
+ self, poll_interval: int = 5, timeout: int = 600
348
+ ) -> JobStatusV1Response:
349
+ """
350
+ Polls job status until it completes or fails.
351
+
352
+ Parameters
353
+ ----------
354
+ poll_interval : int, optional
355
+ Time in seconds between polling attempts (default is 5).
356
+
357
+ timeout : int, optional
358
+ Maximum time to wait for completion in seconds (default is 600).
359
+
360
+ Returns
361
+ -------
362
+ JobStatusV1Response
363
+ Final job status.
364
+
365
+ Raises
366
+ ------
367
+ TimeoutError
368
+ If the job does not complete within the given timeout.
369
+ """
370
+ start = time.monotonic()
371
+ while True:
372
+ status = self.get_status()
373
+ state = status.job_state.lower()
374
+ if state in {"completed", "failed"}:
375
+ return status
376
+ if time.monotonic() - start > timeout:
377
+ raise TimeoutError(
378
+ f"Job {self._job_id} did not complete within {timeout} seconds."
379
+ )
380
+ time.sleep(poll_interval)
381
+
382
+ def get_output_mappings(self) -> typing.List[typing.Dict[str, str]]:
383
+ """
384
+ Get the mapping of input files to their corresponding output files.
385
+
386
+ Returns
387
+ -------
388
+ List[Dict[str, str]]
389
+ List of mappings with keys 'input_file' and 'output_file'.
390
+ """
391
+ job_status = self.get_status()
392
+ return [
393
+ {
394
+ "input_file": detail.inputs[0].file_name,
395
+ "output_file": detail.outputs[0].file_name,
396
+ }
397
+ for detail in (job_status.job_details or [])
398
+ if detail.inputs and detail.outputs
399
+ ]
400
+
401
+ def download_outputs(self, output_dir: str) -> bool:
402
+ """
403
+ Download output files to the specified directory.
404
+
405
+ Parameters
406
+ ----------
407
+ output_dir : str
408
+ Local directory where outputs will be saved.
409
+
410
+ Returns
411
+ -------
412
+ bool
413
+ True if all files downloaded successfully.
414
+
415
+ Raises
416
+ ------
417
+ RuntimeError
418
+ If a file fails to download.
419
+ """
420
+ mappings = self.get_output_mappings()
421
+ file_names = [m["output_file"] for m in mappings]
422
+ download_links = self._client.get_download_links(
423
+ job_id=self._job_id, files=file_names
424
+ )
425
+
426
+ os.makedirs(output_dir, exist_ok=True)
427
+ with httpx.Client() as client:
428
+ for m in mappings:
429
+ url = download_links.download_urls[m["output_file"]].file_url
430
+ response = client.get(url)
431
+ if (
432
+ response.status_code > HTTPStatus.IM_USED
433
+ or response.status_code < HTTPStatus.OK
434
+ ):
435
+ raise RuntimeError(
436
+ f"Download failed for {m['output_file']}: {response.status_code}"
437
+ )
438
+ output_path = os.path.join(output_dir, f"{m['input_file']}.json")
439
+ with open(output_path, "wb") as f:
440
+ f.write(response.content)
441
+ return True
442
+
443
+ def get_status(self) -> JobStatusV1Response:
444
+ """
445
+ Retrieve the current status of the job.
446
+
447
+ Returns
448
+ -------
449
+ JobStatusV1Response
450
+ """
451
+ return self._client.get_status(self._job_id)
452
+
453
+ def start(self) -> JobStatusV1Response:
454
+ """
455
+ Start the speech-to-text-translate job processing.
456
+
457
+ Returns
458
+ -------
459
+ JobStatusV1Response
460
+ """
461
+ return self._client.start(job_id=self._job_id)
462
+
463
+ def exists(self) -> bool:
464
+ """
465
+ Check if the job exists in the system.
466
+
467
+ Returns
468
+ -------
469
+ bool
470
+ """
471
+ try:
472
+ self.get_status()
473
+ return True
474
+ except httpx.HTTPStatusError:
475
+ return False
476
+
477
+ def is_complete(self) -> bool:
478
+ """
479
+ Check if the job is either completed or failed.
480
+
481
+ Returns
482
+ -------
483
+ bool
484
+ """
485
+ return self.get_status().job_state.lower() in {"completed", "failed"}
486
+
487
+ def is_successful(self) -> bool:
488
+ """
489
+ Check if the job completed successfully.
490
+
491
+ Returns
492
+ -------
493
+ bool
494
+ """
495
+ return self.get_status().job_state.lower() == "completed"
496
+
497
+ def is_failed(self) -> bool:
498
+ """
499
+ Check if the job has failed.
500
+
501
+ Returns
502
+ -------
503
+ bool
504
+ """
505
+ return self.get_status().job_state.lower() == "failed"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sarvamai
3
- Version: 0.1.13a2
3
+ Version: 0.1.15
4
4
  Summary:
5
5
  Requires-Python: >=3.8,<4.0
6
6
  Classifier: Intended Audience :: Developers
@@ -5,7 +5,7 @@ sarvamai/chat/raw_client.py,sha256=A2kRuZcVWlJhyYCD7YKgqNkZEp3cYa1731KhRkhirU0,1
5
5
  sarvamai/client.py,sha256=J30X_os1lPf8Wml0KDFEf6p8VGHhgF_lf3nw1T2D3qo,8207
6
6
  sarvamai/core/__init__.py,sha256=YE2CtXeASe1RAbaI39twKWYKCuT4tW5is9HWHhJjR_g,1653
7
7
  sarvamai/core/api_error.py,sha256=44vPoTyWN59gonCIZMdzw7M1uspygiLnr3GNFOoVL2Q,614
8
- sarvamai/core/client_wrapper.py,sha256=FC_3Kep5NGzWOSiwBhHJ1vKfR3_Pqm6CZ7kWWh_r5AI,2570
8
+ sarvamai/core/client_wrapper.py,sha256=PqRROSSVXjzMUxkf9c2QHUsL_UOhEa2AJ6nE9vcLb1o,2566
9
9
  sarvamai/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
10
10
  sarvamai/core/events.py,sha256=HvKBdSoYcFetk7cgNXb7FxuY-FtY8NtUhZIN7mGVx8U,1159
11
11
  sarvamai/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
@@ -84,10 +84,11 @@ sarvamai/requests/transcription_metrics.py,sha256=FDclX2Z9Z3azrDXxtZW8xbkxxWMZQX
84
84
  sarvamai/requests/translation_response.py,sha256=8iwQeZB1purHY757bIQI-n9QeVRBItaAVcBJ_la-k1Y,414
85
85
  sarvamai/requests/transliteration_response.py,sha256=KqRkqnegLmt7LjdVxjRePX6RoqaLm64KFGZ6q7mXyfw,426
86
86
  sarvamai/speech_to_text/__init__.py,sha256=_VhToAyIt_5axN6CLJwtxg3-CO7THa_23pbUzqhXJa4,85
87
- sarvamai/speech_to_text/client.py,sha256=lp2G2fI9SUbeOBBE1S5tjcp-Xb8wIhAuVadLKwXveh8,11003
88
- sarvamai/speech_to_text/raw_client.py,sha256=A_56vEVeJdyttVJRiFxTMJ4n-s4l_PS8rI1DiLZlOmc,25331
87
+ sarvamai/speech_to_text/client.py,sha256=v9p6bqRbA9h3f4wuylPTVDwetG7MCOlEtmrqW18KHCU,10857
88
+ sarvamai/speech_to_text/raw_client.py,sha256=IhflpQX1P2kvZw8L2qbneMbRs5gtr9ye6ebvEK-hn3A,25185
89
89
  sarvamai/speech_to_text_job/__init__.py,sha256=_VhToAyIt_5axN6CLJwtxg3-CO7THa_23pbUzqhXJa4,85
90
- sarvamai/speech_to_text_job/client.py,sha256=MeX6V_34pbeWczBdsiT3VmAp8O4e4QmNwXNR1ue6s7A,12577
90
+ sarvamai/speech_to_text_job/client.py,sha256=WSGBJxYcNxl77Zd1X6VVWjg4zshqecXf6WCyhfLXVlI,18007
91
+ sarvamai/speech_to_text_job/job.py,sha256=K8HOmwrYd6l82-MZfWDBmNkZeeERyg9YOihnFfvl-Js,15021
91
92
  sarvamai/speech_to_text_job/raw_client.py,sha256=OZTPzMhAn-ckE_xKzfZ9QLsEX5EZVOJS0Pf-PBa19jM,48200
92
93
  sarvamai/speech_to_text_streaming/__init__.py,sha256=q7QygMmZCHJ-4FMhhL_6XNV_dsqlIFRCO1iSxoyxaaY,437
93
94
  sarvamai/speech_to_text_streaming/client.py,sha256=AzStfZDXhu2YAJEpnVbsy0WObub5ctlGBzqfeYOUlpA,8442
@@ -99,7 +100,8 @@ sarvamai/speech_to_text_streaming/types/speech_to_text_streaming_language_code.p
99
100
  sarvamai/speech_to_text_streaming/types/speech_to_text_streaming_model.py,sha256=b6F4ymgz4got6KVDqrweYvkET8itze63wUwWyjqDlO4,180
100
101
  sarvamai/speech_to_text_streaming/types/speech_to_text_streaming_vad_signals.py,sha256=8wiFOB7WDMbYCcMTYgNFJaIjEytYeXpJLwr_O_mH0TI,172
101
102
  sarvamai/speech_to_text_translate_job/__init__.py,sha256=_VhToAyIt_5axN6CLJwtxg3-CO7THa_23pbUzqhXJa4,85
102
- sarvamai/speech_to_text_translate_job/client.py,sha256=LxmP_T_KsPtI4Xg25RqAVN0Nn1c0V1VLZecZzngiCJ0,13674
103
+ sarvamai/speech_to_text_translate_job/client.py,sha256=xu8kYtCESDB7LzL8YKBUq5qhTPMIl3_H3XD2L_7y4UU,18969
104
+ sarvamai/speech_to_text_translate_job/job.py,sha256=DU4k3eB28V8N16M_QEchakVng4IOul6_Qrdn3FumgHA,15208
103
105
  sarvamai/speech_to_text_translate_job/raw_client.py,sha256=dAitbu2B9afPK6iT9zNjUJnE5BIr5-lrAlwrfwFxdkU,49507
104
106
  sarvamai/speech_to_text_translate_streaming/__init__.py,sha256=_hmlce1Zs1grylysZhBUdtKfkaUROwVydtwz6l-1qqg,411
105
107
  sarvamai/speech_to_text_translate_streaming/client.py,sha256=xPPg5_JgpH8tYDUte6FGtpzXO2LGBUSRADN-ICqqA6U,8286
@@ -207,6 +209,6 @@ sarvamai/types/transliterate_mode.py,sha256=1jSEMlGcoLkWuk12TgoOpSgwifa4rThGKZ1h
207
209
  sarvamai/types/transliterate_source_language.py,sha256=bSY9wJszF0sg-Cgg6F-YcWC8ly1mIlj9rqa15-jBtx8,283
208
210
  sarvamai/types/transliteration_response.py,sha256=yt-lzTbDeJ_ZL4I8kQa6oESxA9ebeJJY7LfFHpdEsmM,815
209
211
  sarvamai/version.py,sha256=Qkp3Ee9YH-O9RTix90e0i7iNrFAGN-QDt2AFwGA4n8k,75
210
- sarvamai-0.1.13a2.dist-info/METADATA,sha256=-i6GJmDknM_EKjKfdo2vWiwx0ipLvO5tmR6K8Sh9soY,26753
211
- sarvamai-0.1.13a2.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
212
- sarvamai-0.1.13a2.dist-info/RECORD,,
212
+ sarvamai-0.1.15.dist-info/METADATA,sha256=Y_dnyHQdRGnBxh2KGV6woOtFQDPOX4XeUIykiUSuYPg,26751
213
+ sarvamai-0.1.15.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
214
+ sarvamai-0.1.15.dist-info/RECORD,,