hushscript 1.0.0__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.
- hushscript/__init__.py +97 -0
- hushscript/_client.py +687 -0
- hushscript/_clock.py +36 -0
- hushscript/_config.py +139 -0
- hushscript/_errors.py +563 -0
- hushscript/_guard.py +61 -0
- hushscript/_http.py +624 -0
- hushscript/_json.py +55 -0
- hushscript/_log.py +155 -0
- hushscript/_models.py +144 -0
- hushscript/_pacing.py +51 -0
- hushscript/_redact.py +140 -0
- hushscript/_response.py +67 -0
- hushscript/_retry.py +205 -0
- hushscript/_shape.py +197 -0
- hushscript/_shapes.py +248 -0
- hushscript/_version.py +1 -0
- hushscript/format.py +224 -0
- hushscript/generated/__init__.py +8 -0
- hushscript/generated/api/__init__.py +1 -0
- hushscript/generated/api/default/__init__.py +1 -0
- hushscript/generated/api/default/buy_minutes.py +248 -0
- hushscript/generated/api/default/claim_agent_account.py +238 -0
- hushscript/generated/api/default/complete_upload.py +238 -0
- hushscript/generated/api/default/create_agent_account_signup.py +261 -0
- hushscript/generated/api/default/create_setup_session.py +244 -0
- hushscript/generated/api/default/create_upload.py +225 -0
- hushscript/generated/api/default/get_account.py +169 -0
- hushscript/generated/api/default/get_job.py +198 -0
- hushscript/generated/api/default/get_org.py +169 -0
- hushscript/generated/api/default/get_transcript.py +226 -0
- hushscript/generated/api/default/get_transcription_context.py +173 -0
- hushscript/generated/api/default/get_upload_part_urls.py +246 -0
- hushscript/generated/api/default/heartbeat_upload.py +197 -0
- hushscript/generated/api/default/lift_transcript_hold.py +206 -0
- hushscript/generated/api/default/list_credit_ledger.py +229 -0
- hushscript/generated/api/default/list_deleted_transcripts.py +237 -0
- hushscript/generated/api/default/list_holds.py +233 -0
- hushscript/generated/api/default/list_jobs.py +249 -0
- hushscript/generated/api/default/list_org_audit.py +221 -0
- hushscript/generated/api/default/list_org_invoices.py +221 -0
- hushscript/generated/api/default/list_org_ledger.py +221 -0
- hushscript/generated/api/default/list_org_matters.py +247 -0
- hushscript/generated/api/default/list_org_members.py +221 -0
- hushscript/generated/api/default/list_org_transcripts.py +233 -0
- hushscript/generated/api/default/list_packs.py +169 -0
- hushscript/generated/api/default/list_saved_cards.py +169 -0
- hushscript/generated/api/default/list_transactions.py +225 -0
- hushscript/generated/api/default/list_transcripts.py +225 -0
- hushscript/generated/api/default/org_audit_csv.py +163 -0
- hushscript/generated/api/default/org_ledger_csv.py +163 -0
- hushscript/generated/api/default/org_usage_report.py +247 -0
- hushscript/generated/api/default/org_usage_report_csv.py +229 -0
- hushscript/generated/api/default/patch_org_member.py +237 -0
- hushscript/generated/api/default/place_transcript_hold.py +206 -0
- hushscript/generated/api/default/rotate_agent_credentials.py +177 -0
- hushscript/generated/api/default/schedule_org_custody.py +215 -0
- hushscript/generated/api/default/set_default_card.py +223 -0
- hushscript/generated/api/default/upload_part.py +249 -0
- hushscript/generated/catalog.py +138 -0
- hushscript/generated/client.py +282 -0
- hushscript/generated/errors.py +16 -0
- hushscript/generated/hints.py +28 -0
- hushscript/generated/models/__init__.py +193 -0
- hushscript/generated/models/account_response.py +114 -0
- hushscript/generated/models/active_hold.py +88 -0
- hushscript/generated/models/active_hold_kind.py +18 -0
- hushscript/generated/models/agent_claim_request.py +61 -0
- hushscript/generated/models/agent_claim_response.py +85 -0
- hushscript/generated/models/agent_credentials_rotate_response.py +101 -0
- hushscript/generated/models/agent_signup_request.py +118 -0
- hushscript/generated/models/agent_signup_request_agent.py +80 -0
- hushscript/generated/models/agent_signup_response.py +111 -0
- hushscript/generated/models/buy_minutes_body.py +89 -0
- hushscript/generated/models/card.py +132 -0
- hushscript/generated/models/cards_response.py +90 -0
- hushscript/generated/models/complete_upload_body.py +107 -0
- hushscript/generated/models/complete_upload_body_parts_type_0_item.py +69 -0
- hushscript/generated/models/context_dictionary.py +143 -0
- hushscript/generated/models/context_prompt_preset.py +118 -0
- hushscript/generated/models/create_setup_session_body.py +62 -0
- hushscript/generated/models/create_upload_body.py +254 -0
- hushscript/generated/models/create_upload_body_transcription_options_type_0.py +178 -0
- hushscript/generated/models/credit_ledger_entry.py +113 -0
- hushscript/generated/models/credit_ledger_response.py +90 -0
- hushscript/generated/models/deleted_transcript.py +148 -0
- hushscript/generated/models/deleted_transcript_cause.py +18 -0
- hushscript/generated/models/deleted_transcript_receipt_type_0.py +107 -0
- hushscript/generated/models/deleted_transcript_receipt_type_0_items_item.py +69 -0
- hushscript/generated/models/deleted_transcripts_response.py +90 -0
- hushscript/generated/models/dictionary_entry.py +123 -0
- hushscript/generated/models/dictionary_entry_category.py +37 -0
- hushscript/generated/models/error.py +155 -0
- hushscript/generated/models/error_details.py +94 -0
- hushscript/generated/models/get_upload_part_urls_response_200_type_0.py +108 -0
- hushscript/generated/models/heartbeat_upload_response_200.py +61 -0
- hushscript/generated/models/holds_response.py +90 -0
- hushscript/generated/models/job.py +309 -0
- hushscript/generated/models/job_failure_kind_type_1.py +16 -0
- hushscript/generated/models/job_failure_kind_type_2_type_1.py +16 -0
- hushscript/generated/models/job_failure_kind_type_3_type_1.py +16 -0
- hushscript/generated/models/job_list.py +90 -0
- hushscript/generated/models/lift_transcript_hold_response_200.py +61 -0
- hushscript/generated/models/list_jobs_active.py +17 -0
- hushscript/generated/models/list_org_matters_status.py +16 -0
- hushscript/generated/models/options_summary.py +135 -0
- hushscript/generated/models/org_audit_entry.py +105 -0
- hushscript/generated/models/org_audit_response.py +90 -0
- hushscript/generated/models/org_invoice.py +175 -0
- hushscript/generated/models/org_invoices_response.py +90 -0
- hushscript/generated/models/org_ledger_entry.py +155 -0
- hushscript/generated/models/org_ledger_response.py +90 -0
- hushscript/generated/models/org_matter.py +79 -0
- hushscript/generated/models/org_matter_status.py +16 -0
- hushscript/generated/models/org_matters_response.py +75 -0
- hushscript/generated/models/org_member.py +123 -0
- hushscript/generated/models/org_members_response.py +90 -0
- hushscript/generated/models/org_summary_response.py +482 -0
- hushscript/generated/models/org_transcript.py +103 -0
- hushscript/generated/models/org_transcripts_response.py +90 -0
- hushscript/generated/models/org_usage_report_csv_group_by.py +17 -0
- hushscript/generated/models/org_usage_report_group_by.py +17 -0
- hushscript/generated/models/org_usage_response.py +112 -0
- hushscript/generated/models/org_usage_response_group_by.py +17 -0
- hushscript/generated/models/org_usage_row.py +86 -0
- hushscript/generated/models/pack.py +107 -0
- hushscript/generated/models/packs_response.py +75 -0
- hushscript/generated/models/patch_org_member_body.py +102 -0
- hushscript/generated/models/patch_org_member_response_200.py +61 -0
- hushscript/generated/models/place_transcript_hold_response_200.py +61 -0
- hushscript/generated/models/presigned_part_url.py +69 -0
- hushscript/generated/models/purchase_response.py +76 -0
- hushscript/generated/models/schedule_org_custody_body.py +66 -0
- hushscript/generated/models/schedule_org_custody_body_custody.py +17 -0
- hushscript/generated/models/schedule_org_custody_response_200.py +69 -0
- hushscript/generated/models/set_default_card_body.py +61 -0
- hushscript/generated/models/set_default_card_response_200.py +61 -0
- hushscript/generated/models/setup_session_response.py +77 -0
- hushscript/generated/models/transaction.py +183 -0
- hushscript/generated/models/transactions_response.py +98 -0
- hushscript/generated/models/transcript_detail.py +194 -0
- hushscript/generated/models/transcript_detail_speaker_names.py +47 -0
- hushscript/generated/models/transcript_envelope.py +89 -0
- hushscript/generated/models/transcript_envelope_translations.py +62 -0
- hushscript/generated/models/transcript_language.py +75 -0
- hushscript/generated/models/transcript_list.py +90 -0
- hushscript/generated/models/transcript_list_row.py +152 -0
- hushscript/generated/models/transcript_list_row_insights_state.py +20 -0
- hushscript/generated/models/transcription_context_response.py +95 -0
- hushscript/generated/models/upload_complete_response.py +100 -0
- hushscript/generated/models/upload_create_response.py +148 -0
- hushscript/generated/models/upload_create_response_funding.py +16 -0
- hushscript/generated/models/upload_create_response_upload_type_0.py +108 -0
- hushscript/generated/models/upload_limits.py +86 -0
- hushscript/generated/models/upload_part_response_200.py +69 -0
- hushscript/generated/models/utterance.py +85 -0
- hushscript/generated/scopes.py +42 -0
- hushscript/generated/types.py +54 -0
- hushscript/money.py +79 -0
- hushscript/paginate.py +78 -0
- hushscript/py.typed +0 -0
- hushscript/resources/__init__.py +1 -0
- hushscript/resources/_params.py +33 -0
- hushscript/resources/account.py +121 -0
- hushscript/resources/agent.py +421 -0
- hushscript/resources/billing.py +567 -0
- hushscript/resources/jobs.py +105 -0
- hushscript/resources/transcripts.py +209 -0
- hushscript/transcribe/__init__.py +1 -0
- hushscript/transcribe/apipeline.py +234 -0
- hushscript/transcribe/aupload.py +197 -0
- hushscript/transcribe/heartbeat.py +78 -0
- hushscript/transcribe/pipeline.py +312 -0
- hushscript/transcribe/preflight.py +272 -0
- hushscript/transcribe/probe.py +185 -0
- hushscript/transcribe/resume.py +115 -0
- hushscript/transcribe/segment.py +23 -0
- hushscript/transcribe/session.py +34 -0
- hushscript/transcribe/sniff.py +30 -0
- hushscript/transcribe/source.py +218 -0
- hushscript/transcribe/upload.py +498 -0
- hushscript/transcribe/wait.py +124 -0
- hushscript/transcript.py +197 -0
- hushscript-1.0.0.dist-info/METADATA +502 -0
- hushscript-1.0.0.dist-info/RECORD +187 -0
- hushscript-1.0.0.dist-info/WHEEL +4 -0
- hushscript-1.0.0.dist-info/licenses/LICENSE +21 -0
hushscript/_client.py
ADDED
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
5
|
+
from typing import TYPE_CHECKING, BinaryIO
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
|
|
10
|
+
from ._clock import Clock, SystemClock
|
|
11
|
+
from ._config import PART_TIMEOUT, Config, resolve_config, resolve_tokenless_config
|
|
12
|
+
from ._errors import ConfigurationError
|
|
13
|
+
from ._http import AsyncRetryingTransport, RetryingTransport, TransportSettings
|
|
14
|
+
from ._log import RequestLog
|
|
15
|
+
from ._pacing import TokenBucket
|
|
16
|
+
from ._redact import REDACTED, Secret
|
|
17
|
+
from ._response import parsed
|
|
18
|
+
from ._version import __version__
|
|
19
|
+
from .generated.client import AuthenticatedClient
|
|
20
|
+
from .resources.account import Account, Balance
|
|
21
|
+
from .resources.agent import (
|
|
22
|
+
Agent,
|
|
23
|
+
AgentAccount,
|
|
24
|
+
AgentInfo,
|
|
25
|
+
AgentSignup,
|
|
26
|
+
AsyncAgent,
|
|
27
|
+
check_accept_terms,
|
|
28
|
+
claim_body,
|
|
29
|
+
claim_path,
|
|
30
|
+
claimed,
|
|
31
|
+
remember_claim_secret,
|
|
32
|
+
signed_up,
|
|
33
|
+
signup_body,
|
|
34
|
+
)
|
|
35
|
+
from .resources.billing import AsyncBilling, Billing, Purchase
|
|
36
|
+
from .resources.jobs import AsyncJobs, Jobs
|
|
37
|
+
from .resources.transcripts import AsyncTranscripts, Transcripts
|
|
38
|
+
from .transcribe import apipeline, pipeline
|
|
39
|
+
from .transcribe.pipeline import Callbacks
|
|
40
|
+
from .transcribe.preflight import Options
|
|
41
|
+
from .transcribe.session import AsyncSession, SyncSession
|
|
42
|
+
from .transcribe.wait import DEFAULT_WAIT_TIMEOUT
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
import os
|
|
46
|
+
from collections.abc import AsyncGenerator, Callable, Generator, Sequence
|
|
47
|
+
from types import TracebackType
|
|
48
|
+
|
|
49
|
+
from ._models import Progress
|
|
50
|
+
from .transcript import Transcript
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def user_agent() -> str:
|
|
54
|
+
return f"hushscript-python/{__version__} python/{platform.python_version()}"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _SharedTransport(httpx.BaseTransport):
|
|
58
|
+
"""Gives a derived httpx client the hardened transport without letting it close it."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, inner: httpx.BaseTransport) -> None:
|
|
61
|
+
self._inner = inner
|
|
62
|
+
|
|
63
|
+
def handle_request(self, request: httpx.Request) -> httpx.Response:
|
|
64
|
+
try:
|
|
65
|
+
return self._inner.handle_request(request)
|
|
66
|
+
finally:
|
|
67
|
+
del request
|
|
68
|
+
|
|
69
|
+
def close(self) -> None:
|
|
70
|
+
"""The owning `Hushscript` closes the transport."""
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class _SharedAsyncTransport(httpx.AsyncBaseTransport):
|
|
74
|
+
def __init__(self, inner: httpx.AsyncBaseTransport) -> None:
|
|
75
|
+
self._inner = inner
|
|
76
|
+
|
|
77
|
+
async def handle_async_request(self, request: httpx.Request) -> httpx.Response:
|
|
78
|
+
try:
|
|
79
|
+
return await self._inner.handle_async_request(request)
|
|
80
|
+
finally:
|
|
81
|
+
del request
|
|
82
|
+
|
|
83
|
+
async def aclose(self) -> None:
|
|
84
|
+
"""The owning `AsyncHushscript` closes the transport."""
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class _ApiClient(AuthenticatedClient):
|
|
88
|
+
"""The generated client on the hardened httpx client.
|
|
89
|
+
|
|
90
|
+
It holds no token: the transport adds the Authorization header per request, so a rotated token
|
|
91
|
+
is picked up and nothing here can show it. The generated `with_*` methods would build a plain
|
|
92
|
+
httpx client without the retrying transport, so they are replaced by ones that keep it.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
_sync_transport: httpx.BaseTransport | None = None
|
|
96
|
+
_async_transport: httpx.AsyncBaseTransport | None = None
|
|
97
|
+
|
|
98
|
+
def __repr__(self) -> str:
|
|
99
|
+
return f"AuthenticatedClient(base_url={self._base_url!r}, token={REDACTED!r})"
|
|
100
|
+
|
|
101
|
+
def bind_sync(self, client: httpx.Client, transport: httpx.BaseTransport) -> None:
|
|
102
|
+
self.set_httpx_client(client)
|
|
103
|
+
self._sync_transport = transport
|
|
104
|
+
|
|
105
|
+
def bind_async(self, client: httpx.AsyncClient, transport: httpx.AsyncBaseTransport) -> None:
|
|
106
|
+
self.set_async_httpx_client(client)
|
|
107
|
+
self._async_transport = transport
|
|
108
|
+
|
|
109
|
+
def get_httpx_client(self) -> httpx.Client:
|
|
110
|
+
if self._client is None:
|
|
111
|
+
raise ConfigurationError("This is an async client; use get_async_httpx_client().")
|
|
112
|
+
return self._client
|
|
113
|
+
|
|
114
|
+
def get_async_httpx_client(self) -> httpx.AsyncClient:
|
|
115
|
+
if self._async_client is None:
|
|
116
|
+
raise ConfigurationError("This is a sync client; use get_httpx_client().")
|
|
117
|
+
return self._async_client
|
|
118
|
+
|
|
119
|
+
def with_timeout(self, timeout: httpx.Timeout) -> _ApiClient:
|
|
120
|
+
return self._derive(timeout=timeout)
|
|
121
|
+
|
|
122
|
+
def with_headers(self, headers: dict[str, str]) -> _ApiClient:
|
|
123
|
+
return self._derive(headers=headers)
|
|
124
|
+
|
|
125
|
+
def with_cookies(self, cookies: dict[str, str]) -> _ApiClient:
|
|
126
|
+
return self._derive(cookies=cookies)
|
|
127
|
+
|
|
128
|
+
def _derive(
|
|
129
|
+
self,
|
|
130
|
+
*,
|
|
131
|
+
timeout: httpx.Timeout | None = None,
|
|
132
|
+
headers: dict[str, str] | None = None,
|
|
133
|
+
cookies: dict[str, str] | None = None,
|
|
134
|
+
) -> _ApiClient:
|
|
135
|
+
derived = _ApiClient(base_url=self._base_url, token="", timeout=timeout or self._timeout)
|
|
136
|
+
if self._client is not None and self._sync_transport is not None:
|
|
137
|
+
derived.bind_sync(
|
|
138
|
+
httpx.Client(
|
|
139
|
+
base_url=self._client.base_url,
|
|
140
|
+
headers=_merged_headers(self._client.headers, headers),
|
|
141
|
+
cookies=_merged_cookies(self._client.cookies, cookies),
|
|
142
|
+
timeout=timeout or self._client.timeout,
|
|
143
|
+
transport=_SharedTransport(self._sync_transport),
|
|
144
|
+
),
|
|
145
|
+
self._sync_transport,
|
|
146
|
+
)
|
|
147
|
+
if self._async_client is not None and self._async_transport is not None:
|
|
148
|
+
derived.bind_async(
|
|
149
|
+
httpx.AsyncClient(
|
|
150
|
+
base_url=self._async_client.base_url,
|
|
151
|
+
headers=_merged_headers(self._async_client.headers, headers),
|
|
152
|
+
cookies=_merged_cookies(self._async_client.cookies, cookies),
|
|
153
|
+
timeout=timeout or self._async_client.timeout,
|
|
154
|
+
transport=_SharedAsyncTransport(self._async_transport),
|
|
155
|
+
),
|
|
156
|
+
self._async_transport,
|
|
157
|
+
)
|
|
158
|
+
return derived
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _merged_headers(current: httpx.Headers, extra: dict[str, str] | None) -> httpx.Headers:
|
|
162
|
+
merged = httpx.Headers(current)
|
|
163
|
+
merged.update(extra or {})
|
|
164
|
+
return merged
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _merged_cookies(current: httpx.Cookies, extra: dict[str, str] | None) -> httpx.Cookies:
|
|
168
|
+
merged = httpx.Cookies(current)
|
|
169
|
+
merged.update(extra or {})
|
|
170
|
+
return merged
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _sync_inner() -> httpx.BaseTransport:
|
|
174
|
+
return httpx.HTTPTransport()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _async_inner() -> httpx.AsyncBaseTransport:
|
|
178
|
+
return httpx.AsyncHTTPTransport()
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _api_client(config: Config) -> _ApiClient:
|
|
182
|
+
return _ApiClient(base_url=config.base_url, token="", timeout=httpx.Timeout(config.timeout))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _settings(config: Config, clock: Clock, bucket: TokenBucket) -> TransportSettings:
|
|
186
|
+
return TransportSettings(
|
|
187
|
+
clock=clock,
|
|
188
|
+
log=RequestLog(config.log_level),
|
|
189
|
+
credentials=config.credentials,
|
|
190
|
+
max_retries=config.max_retries,
|
|
191
|
+
base_url=httpx.URL(config.base_url),
|
|
192
|
+
timeout=config.timeout,
|
|
193
|
+
bucket=bucket,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# Signed direct-to-R2 part URLs are not API requests: this base URL matches no request, so the
|
|
198
|
+
# client built on it never sends the token and the part class of the call is honored.
|
|
199
|
+
_R2_ORIGIN = httpx.URL("https://r2.invalid")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _r2_settings(config: Config, clock: Clock) -> TransportSettings:
|
|
203
|
+
return TransportSettings(
|
|
204
|
+
clock=clock,
|
|
205
|
+
log=RequestLog(config.log_level),
|
|
206
|
+
credentials=None,
|
|
207
|
+
max_retries=config.max_retries,
|
|
208
|
+
base_url=_R2_ORIGIN,
|
|
209
|
+
timeout=PART_TIMEOUT,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@contextmanager
|
|
214
|
+
def _tokenless_http(config: Config, clock: Clock | None) -> Generator[httpx.Client]:
|
|
215
|
+
"""A client for the calls that need no token: it sends none, and never retries."""
|
|
216
|
+
chosen = clock or SystemClock()
|
|
217
|
+
settings = _settings(config, chosen, TokenBucket(chosen))
|
|
218
|
+
with httpx.Client(
|
|
219
|
+
base_url=config.base_url,
|
|
220
|
+
timeout=config.timeout,
|
|
221
|
+
headers={"User-Agent": user_agent()},
|
|
222
|
+
transport=RetryingTransport(_sync_inner(), settings),
|
|
223
|
+
follow_redirects=False,
|
|
224
|
+
) as http:
|
|
225
|
+
yield http
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@asynccontextmanager
|
|
229
|
+
async def _tokenless_async_http(
|
|
230
|
+
config: Config, clock: Clock | None
|
|
231
|
+
) -> AsyncGenerator[httpx.AsyncClient]:
|
|
232
|
+
chosen = clock or SystemClock()
|
|
233
|
+
settings = _settings(config, chosen, TokenBucket(chosen))
|
|
234
|
+
async with httpx.AsyncClient(
|
|
235
|
+
base_url=config.base_url,
|
|
236
|
+
timeout=config.timeout,
|
|
237
|
+
headers={"User-Agent": user_agent()},
|
|
238
|
+
transport=AsyncRetryingTransport(_async_inner(), settings),
|
|
239
|
+
follow_redirects=False,
|
|
240
|
+
) as http:
|
|
241
|
+
yield http
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _options( # noqa: PLR0913 the keyword surface of `transcribe()`, validated before any request
|
|
245
|
+
*,
|
|
246
|
+
title: str | None,
|
|
247
|
+
language: str | None,
|
|
248
|
+
speakers: bool,
|
|
249
|
+
translate_to: Sequence[str] | None,
|
|
250
|
+
keyterms: Sequence[str] | None,
|
|
251
|
+
prompt: str | None,
|
|
252
|
+
medical: bool,
|
|
253
|
+
multichannel: bool,
|
|
254
|
+
auto_insights: bool | None,
|
|
255
|
+
matter_code: str | None,
|
|
256
|
+
duration_seconds: float | None,
|
|
257
|
+
idempotency_key: str | None,
|
|
258
|
+
concurrency: int,
|
|
259
|
+
wait_timeout: float,
|
|
260
|
+
) -> Options:
|
|
261
|
+
return Options(
|
|
262
|
+
title=title,
|
|
263
|
+
language=language,
|
|
264
|
+
speakers=speakers,
|
|
265
|
+
translate_to=tuple(translate_to or ()),
|
|
266
|
+
keyterms=tuple(keyterms or ()),
|
|
267
|
+
prompt=prompt,
|
|
268
|
+
medical=medical,
|
|
269
|
+
multichannel=multichannel,
|
|
270
|
+
auto_insights=auto_insights,
|
|
271
|
+
matter_code=matter_code,
|
|
272
|
+
duration_seconds=duration_seconds,
|
|
273
|
+
idempotency_key=idempotency_key,
|
|
274
|
+
concurrency=concurrency,
|
|
275
|
+
wait_timeout=wait_timeout,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class Hushscript:
|
|
280
|
+
"""The synchronous Hushscript client. `api` is the generated low-level client on the same
|
|
281
|
+
retrying transport."""
|
|
282
|
+
|
|
283
|
+
api: AuthenticatedClient
|
|
284
|
+
|
|
285
|
+
def __init__( # the public surface of spec section 3
|
|
286
|
+
self,
|
|
287
|
+
token: str | None = None,
|
|
288
|
+
*,
|
|
289
|
+
base_url: str | None = None,
|
|
290
|
+
timeout: float = 60,
|
|
291
|
+
max_retries: int = 3,
|
|
292
|
+
log_level: str | None = None,
|
|
293
|
+
clock: Clock | None = None,
|
|
294
|
+
) -> None:
|
|
295
|
+
self._config = resolve_config(
|
|
296
|
+
token, base_url=base_url, timeout=timeout, max_retries=max_retries, log_level=log_level
|
|
297
|
+
)
|
|
298
|
+
self._clock: Clock = clock or SystemClock()
|
|
299
|
+
bucket = TokenBucket(self._clock)
|
|
300
|
+
transport = RetryingTransport(_sync_inner(), _settings(self._config, self._clock, bucket))
|
|
301
|
+
self._http = httpx.Client(
|
|
302
|
+
base_url=self._config.base_url,
|
|
303
|
+
timeout=self._config.timeout,
|
|
304
|
+
headers={"User-Agent": user_agent()},
|
|
305
|
+
transport=transport,
|
|
306
|
+
follow_redirects=False,
|
|
307
|
+
)
|
|
308
|
+
self._r2 = httpx.Client(
|
|
309
|
+
timeout=PART_TIMEOUT,
|
|
310
|
+
headers={"User-Agent": user_agent()},
|
|
311
|
+
transport=RetryingTransport(_sync_inner(), _r2_settings(self._config, self._clock)),
|
|
312
|
+
follow_redirects=False,
|
|
313
|
+
)
|
|
314
|
+
api = _api_client(self._config)
|
|
315
|
+
api.bind_sync(self._http, transport)
|
|
316
|
+
self.api = api
|
|
317
|
+
self._session = SyncSession(
|
|
318
|
+
clock=self._clock,
|
|
319
|
+
credentials=self._config.credentials,
|
|
320
|
+
log=RequestLog(self._config.log_level),
|
|
321
|
+
http=self._http,
|
|
322
|
+
r2=self._r2,
|
|
323
|
+
)
|
|
324
|
+
self.jobs = Jobs(self._session)
|
|
325
|
+
self.transcripts = Transcripts(self._session)
|
|
326
|
+
self.billing = Billing(self._session)
|
|
327
|
+
self.agent = Agent(self._session)
|
|
328
|
+
|
|
329
|
+
def balance(self) -> Balance:
|
|
330
|
+
"""What the account can spend. Net of holds: `held_seconds` says how much of it is already
|
|
331
|
+
committed, and is not to be subtracted again."""
|
|
332
|
+
response = self._http.get("/v1/account")
|
|
333
|
+
return parsed(response, self._config.credentials, Balance.from_account)
|
|
334
|
+
|
|
335
|
+
def account(self) -> Account:
|
|
336
|
+
"""The account: balance, limits, card on file and data region."""
|
|
337
|
+
response = self._http.get("/v1/account")
|
|
338
|
+
return parsed(response, self._config.credentials, Account.from_body)
|
|
339
|
+
|
|
340
|
+
def buy(
|
|
341
|
+
self,
|
|
342
|
+
pack: str,
|
|
343
|
+
*,
|
|
344
|
+
acknowledge_no_refund: bool = False,
|
|
345
|
+
idempotency_key: str | None = None,
|
|
346
|
+
) -> Purchase:
|
|
347
|
+
"""Buys a minute pack with the saved card; it is `hs.billing.buy`."""
|
|
348
|
+
return self.billing.buy(
|
|
349
|
+
pack, acknowledge_no_refund=acknowledge_no_refund, idempotency_key=idempotency_key
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
@staticmethod
|
|
353
|
+
def agent_signup( # noqa: PLR0913 the keyword surface of spec 6.6
|
|
354
|
+
*,
|
|
355
|
+
email: str,
|
|
356
|
+
pack: str,
|
|
357
|
+
accept_terms: str,
|
|
358
|
+
agent: AgentInfo,
|
|
359
|
+
data_region: str | None = None,
|
|
360
|
+
dry_run: bool = False,
|
|
361
|
+
base_url: str | None = None,
|
|
362
|
+
timeout: float = 60,
|
|
363
|
+
log_level: str | None = None,
|
|
364
|
+
clock: Clock | None = None,
|
|
365
|
+
) -> AgentSignup:
|
|
366
|
+
"""Opens an agent account. It needs no token (HUSHSCRIPT_TOKEN is not read or sent): the
|
|
367
|
+
agent then has the checkout paid at `checkout_url` and claims the account with
|
|
368
|
+
`agent_claim`. `accept_terms`, the policy version being accepted, is never defaulted. The
|
|
369
|
+
request is never retried; a lost answer is an `OutcomeUnknownError`."""
|
|
370
|
+
check_accept_terms(accept_terms)
|
|
371
|
+
config = resolve_tokenless_config(base_url=base_url, timeout=timeout, log_level=log_level)
|
|
372
|
+
body = signup_body(
|
|
373
|
+
email=email,
|
|
374
|
+
pack=pack,
|
|
375
|
+
accept_terms=accept_terms,
|
|
376
|
+
agent=agent,
|
|
377
|
+
data_region=data_region,
|
|
378
|
+
dry_run=dry_run,
|
|
379
|
+
)
|
|
380
|
+
with _tokenless_http(config, clock) as http:
|
|
381
|
+
return signed_up(http.post("/v1/agent/accounts", json=body), config.credentials)
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def agent_claim(
|
|
385
|
+
signup_id: str,
|
|
386
|
+
claim_secret: str,
|
|
387
|
+
*,
|
|
388
|
+
base_url: str | None = None,
|
|
389
|
+
timeout: float = 60,
|
|
390
|
+
log_level: str | None = None,
|
|
391
|
+
clock: Clock | None = None,
|
|
392
|
+
) -> AgentAccount:
|
|
393
|
+
"""Claims a paid agent signup and returns the account's token, which the server shows
|
|
394
|
+
once: write it down before doing anything else. It answers `wrong_state` (a
|
|
395
|
+
`ConflictError`) until the checkout is paid, and is never retried: a lost answer, or one
|
|
396
|
+
without a usable token, is an `OutcomeUnknownError` whose hint says what to check."""
|
|
397
|
+
path = claim_path(signup_id)
|
|
398
|
+
secret = Secret(claim_secret)
|
|
399
|
+
del claim_secret # no frame of the SDK holds the secret as text from here on (R81)
|
|
400
|
+
config = resolve_tokenless_config(base_url=base_url, timeout=timeout, log_level=log_level)
|
|
401
|
+
remember_claim_secret(config.credentials, secret)
|
|
402
|
+
with _tokenless_http(config, clock) as http:
|
|
403
|
+
response = http.post(path, json=claim_body(secret))
|
|
404
|
+
return claimed(response, config.credentials)
|
|
405
|
+
|
|
406
|
+
def transcribe( # noqa: PLR0913 the keyword surface of spec 6.1
|
|
407
|
+
self,
|
|
408
|
+
file: str | os.PathLike[str] | bytes | BinaryIO,
|
|
409
|
+
*,
|
|
410
|
+
title: str | None = None,
|
|
411
|
+
language: str | None = None,
|
|
412
|
+
speakers: bool = True,
|
|
413
|
+
translate_to: Sequence[str] | None = None,
|
|
414
|
+
keyterms: Sequence[str] | None = None,
|
|
415
|
+
prompt: str | None = None,
|
|
416
|
+
medical: bool = False,
|
|
417
|
+
multichannel: bool = False,
|
|
418
|
+
auto_insights: bool | None = None,
|
|
419
|
+
matter_code: str | None = None,
|
|
420
|
+
duration_seconds: float | None = None,
|
|
421
|
+
size: int | None = None,
|
|
422
|
+
idempotency_key: str | None = None,
|
|
423
|
+
concurrency: int = 4,
|
|
424
|
+
wait_timeout: float = DEFAULT_WAIT_TIMEOUT,
|
|
425
|
+
on_progress: Callable[[Progress], None] | None = None,
|
|
426
|
+
on_resume_token: Callable[[str], None] | None = None,
|
|
427
|
+
) -> Transcript:
|
|
428
|
+
"""Uploads `file`, waits for the transcript and returns it (spec 8). A non-seekable
|
|
429
|
+
stream needs `size` and `duration_seconds`.
|
|
430
|
+
|
|
431
|
+
Key terms and the prompt are trimmed first: blank terms are dropped, and an empty
|
|
432
|
+
list or a blank prompt is not sent. `keyterms` takes up to 1000 unique terms of at most
|
|
433
|
+
6 words and 128 characters each. `prompt` takes up to 2000 characters; with key terms
|
|
434
|
+
set, the 2000 covers the prompt and the key terms the API wraps around it (203
|
|
435
|
+
characters of fixed text, each term plus 2, and 1 between terms)."""
|
|
436
|
+
options = _options(
|
|
437
|
+
title=title,
|
|
438
|
+
language=language,
|
|
439
|
+
speakers=speakers,
|
|
440
|
+
translate_to=translate_to,
|
|
441
|
+
keyterms=keyterms,
|
|
442
|
+
prompt=prompt,
|
|
443
|
+
medical=medical,
|
|
444
|
+
multichannel=multichannel,
|
|
445
|
+
auto_insights=auto_insights,
|
|
446
|
+
matter_code=matter_code,
|
|
447
|
+
duration_seconds=duration_seconds,
|
|
448
|
+
idempotency_key=idempotency_key,
|
|
449
|
+
concurrency=concurrency,
|
|
450
|
+
wait_timeout=wait_timeout,
|
|
451
|
+
)
|
|
452
|
+
callbacks = Callbacks(on_progress=on_progress, on_resume_token=on_resume_token)
|
|
453
|
+
return pipeline.run_transcribe(self._session, file, options, size, callbacks)
|
|
454
|
+
|
|
455
|
+
def resume(
|
|
456
|
+
self,
|
|
457
|
+
token: str,
|
|
458
|
+
file: str | os.PathLike[str] | bytes | BinaryIO | None = None,
|
|
459
|
+
*,
|
|
460
|
+
concurrency: int = 4,
|
|
461
|
+
wait_timeout: float = DEFAULT_WAIT_TIMEOUT,
|
|
462
|
+
on_progress: Callable[[Progress], None] | None = None,
|
|
463
|
+
) -> Transcript:
|
|
464
|
+
"""Picks a transcription up from the token of an earlier call (spec 7.3). `file` is needed
|
|
465
|
+
only while the job is still uploading."""
|
|
466
|
+
options = Options(concurrency=concurrency, wait_timeout=wait_timeout)
|
|
467
|
+
return pipeline.run_resume(
|
|
468
|
+
self._session, token, file, options, Callbacks(on_progress=on_progress)
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
def close(self) -> None:
|
|
472
|
+
self._http.close()
|
|
473
|
+
self._r2.close()
|
|
474
|
+
|
|
475
|
+
def __enter__(self) -> Self:
|
|
476
|
+
return self
|
|
477
|
+
|
|
478
|
+
def __exit__(
|
|
479
|
+
self,
|
|
480
|
+
exc_type: type[BaseException] | None,
|
|
481
|
+
exc: BaseException | None,
|
|
482
|
+
traceback: TracebackType | None,
|
|
483
|
+
) -> None:
|
|
484
|
+
self.close()
|
|
485
|
+
|
|
486
|
+
def __repr__(self) -> str:
|
|
487
|
+
return f"Hushscript(base_url={self._config.base_url!r})"
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
class AsyncHushscript:
|
|
491
|
+
"""The asyncio client with the same surface as `Hushscript`."""
|
|
492
|
+
|
|
493
|
+
api: AuthenticatedClient
|
|
494
|
+
|
|
495
|
+
def __init__( # the public surface of spec section 3
|
|
496
|
+
self,
|
|
497
|
+
token: str | None = None,
|
|
498
|
+
*,
|
|
499
|
+
base_url: str | None = None,
|
|
500
|
+
timeout: float = 60,
|
|
501
|
+
max_retries: int = 3,
|
|
502
|
+
log_level: str | None = None,
|
|
503
|
+
clock: Clock | None = None,
|
|
504
|
+
) -> None:
|
|
505
|
+
self._config = resolve_config(
|
|
506
|
+
token, base_url=base_url, timeout=timeout, max_retries=max_retries, log_level=log_level
|
|
507
|
+
)
|
|
508
|
+
self._clock: Clock = clock or SystemClock()
|
|
509
|
+
bucket = TokenBucket(self._clock)
|
|
510
|
+
transport = AsyncRetryingTransport(
|
|
511
|
+
_async_inner(), _settings(self._config, self._clock, bucket)
|
|
512
|
+
)
|
|
513
|
+
self._http = httpx.AsyncClient(
|
|
514
|
+
base_url=self._config.base_url,
|
|
515
|
+
timeout=self._config.timeout,
|
|
516
|
+
headers={"User-Agent": user_agent()},
|
|
517
|
+
transport=transport,
|
|
518
|
+
follow_redirects=False,
|
|
519
|
+
)
|
|
520
|
+
self._r2 = httpx.AsyncClient(
|
|
521
|
+
timeout=PART_TIMEOUT,
|
|
522
|
+
headers={"User-Agent": user_agent()},
|
|
523
|
+
transport=AsyncRetryingTransport(
|
|
524
|
+
_async_inner(), _r2_settings(self._config, self._clock)
|
|
525
|
+
),
|
|
526
|
+
follow_redirects=False,
|
|
527
|
+
)
|
|
528
|
+
api = _api_client(self._config)
|
|
529
|
+
api.bind_async(self._http, transport)
|
|
530
|
+
self.api = api
|
|
531
|
+
self._session = AsyncSession(
|
|
532
|
+
clock=self._clock,
|
|
533
|
+
credentials=self._config.credentials,
|
|
534
|
+
log=RequestLog(self._config.log_level),
|
|
535
|
+
http=self._http,
|
|
536
|
+
r2=self._r2,
|
|
537
|
+
)
|
|
538
|
+
self.jobs = AsyncJobs(self._session)
|
|
539
|
+
self.transcripts = AsyncTranscripts(self._session)
|
|
540
|
+
self.billing = AsyncBilling(self._session)
|
|
541
|
+
self.agent = AsyncAgent(self._session)
|
|
542
|
+
|
|
543
|
+
async def balance(self) -> Balance:
|
|
544
|
+
response = await self._http.get("/v1/account")
|
|
545
|
+
return parsed(response, self._config.credentials, Balance.from_account)
|
|
546
|
+
|
|
547
|
+
async def account(self) -> Account:
|
|
548
|
+
response = await self._http.get("/v1/account")
|
|
549
|
+
return parsed(response, self._config.credentials, Account.from_body)
|
|
550
|
+
|
|
551
|
+
async def buy(
|
|
552
|
+
self,
|
|
553
|
+
pack: str,
|
|
554
|
+
*,
|
|
555
|
+
acknowledge_no_refund: bool = False,
|
|
556
|
+
idempotency_key: str | None = None,
|
|
557
|
+
) -> Purchase:
|
|
558
|
+
return await self.billing.buy(
|
|
559
|
+
pack, acknowledge_no_refund=acknowledge_no_refund, idempotency_key=idempotency_key
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
@staticmethod
|
|
563
|
+
async def agent_signup( # noqa: PLR0913 the keyword surface of spec 6.6
|
|
564
|
+
*,
|
|
565
|
+
email: str,
|
|
566
|
+
pack: str,
|
|
567
|
+
accept_terms: str,
|
|
568
|
+
agent: AgentInfo,
|
|
569
|
+
data_region: str | None = None,
|
|
570
|
+
dry_run: bool = False,
|
|
571
|
+
base_url: str | None = None,
|
|
572
|
+
timeout: float = 60,
|
|
573
|
+
log_level: str | None = None,
|
|
574
|
+
clock: Clock | None = None,
|
|
575
|
+
) -> AgentSignup:
|
|
576
|
+
check_accept_terms(accept_terms)
|
|
577
|
+
config = resolve_tokenless_config(base_url=base_url, timeout=timeout, log_level=log_level)
|
|
578
|
+
body = signup_body(
|
|
579
|
+
email=email,
|
|
580
|
+
pack=pack,
|
|
581
|
+
accept_terms=accept_terms,
|
|
582
|
+
agent=agent,
|
|
583
|
+
data_region=data_region,
|
|
584
|
+
dry_run=dry_run,
|
|
585
|
+
)
|
|
586
|
+
async with _tokenless_async_http(config, clock) as http:
|
|
587
|
+
response = await http.post("/v1/agent/accounts", json=body)
|
|
588
|
+
return signed_up(response, config.credentials)
|
|
589
|
+
|
|
590
|
+
@staticmethod
|
|
591
|
+
async def agent_claim(
|
|
592
|
+
signup_id: str,
|
|
593
|
+
claim_secret: str,
|
|
594
|
+
*,
|
|
595
|
+
base_url: str | None = None,
|
|
596
|
+
timeout: float = 60,
|
|
597
|
+
log_level: str | None = None,
|
|
598
|
+
clock: Clock | None = None,
|
|
599
|
+
) -> AgentAccount:
|
|
600
|
+
path = claim_path(signup_id)
|
|
601
|
+
secret = Secret(claim_secret)
|
|
602
|
+
del claim_secret # no frame of the SDK holds the secret as text from here on (R81)
|
|
603
|
+
config = resolve_tokenless_config(base_url=base_url, timeout=timeout, log_level=log_level)
|
|
604
|
+
remember_claim_secret(config.credentials, secret)
|
|
605
|
+
async with _tokenless_async_http(config, clock) as http:
|
|
606
|
+
response = await http.post(path, json=claim_body(secret))
|
|
607
|
+
return claimed(response, config.credentials)
|
|
608
|
+
|
|
609
|
+
async def transcribe( # noqa: PLR0913 the keyword surface of spec 6.1
|
|
610
|
+
self,
|
|
611
|
+
file: str | os.PathLike[str] | bytes | BinaryIO,
|
|
612
|
+
*,
|
|
613
|
+
title: str | None = None,
|
|
614
|
+
language: str | None = None,
|
|
615
|
+
speakers: bool = True,
|
|
616
|
+
translate_to: Sequence[str] | None = None,
|
|
617
|
+
keyterms: Sequence[str] | None = None,
|
|
618
|
+
prompt: str | None = None,
|
|
619
|
+
medical: bool = False,
|
|
620
|
+
multichannel: bool = False,
|
|
621
|
+
auto_insights: bool | None = None,
|
|
622
|
+
matter_code: str | None = None,
|
|
623
|
+
duration_seconds: float | None = None,
|
|
624
|
+
size: int | None = None,
|
|
625
|
+
idempotency_key: str | None = None,
|
|
626
|
+
concurrency: int = 4,
|
|
627
|
+
wait_timeout: float = DEFAULT_WAIT_TIMEOUT,
|
|
628
|
+
on_progress: Callable[[Progress], None] | None = None,
|
|
629
|
+
on_resume_token: Callable[[str], None] | None = None,
|
|
630
|
+
) -> Transcript:
|
|
631
|
+
"""The async twin of `Hushscript.transcribe()`.
|
|
632
|
+
|
|
633
|
+
Key terms and the prompt are trimmed first: blank terms are dropped, and an empty
|
|
634
|
+
list or a blank prompt is not sent. `keyterms` takes up to 1000 unique terms of at most
|
|
635
|
+
6 words and 128 characters each. `prompt` takes up to 2000 characters; with key terms
|
|
636
|
+
set, the 2000 covers the prompt and the key terms the API wraps around it (203
|
|
637
|
+
characters of fixed text, each term plus 2, and 1 between terms)."""
|
|
638
|
+
options = _options(
|
|
639
|
+
title=title,
|
|
640
|
+
language=language,
|
|
641
|
+
speakers=speakers,
|
|
642
|
+
translate_to=translate_to,
|
|
643
|
+
keyterms=keyterms,
|
|
644
|
+
prompt=prompt,
|
|
645
|
+
medical=medical,
|
|
646
|
+
multichannel=multichannel,
|
|
647
|
+
auto_insights=auto_insights,
|
|
648
|
+
matter_code=matter_code,
|
|
649
|
+
duration_seconds=duration_seconds,
|
|
650
|
+
idempotency_key=idempotency_key,
|
|
651
|
+
concurrency=concurrency,
|
|
652
|
+
wait_timeout=wait_timeout,
|
|
653
|
+
)
|
|
654
|
+
callbacks = Callbacks(on_progress=on_progress, on_resume_token=on_resume_token)
|
|
655
|
+
return await apipeline.run_transcribe(self._session, file, options, size, callbacks)
|
|
656
|
+
|
|
657
|
+
async def resume(
|
|
658
|
+
self,
|
|
659
|
+
token: str,
|
|
660
|
+
file: str | os.PathLike[str] | bytes | BinaryIO | None = None,
|
|
661
|
+
*,
|
|
662
|
+
concurrency: int = 4,
|
|
663
|
+
wait_timeout: float = DEFAULT_WAIT_TIMEOUT,
|
|
664
|
+
on_progress: Callable[[Progress], None] | None = None,
|
|
665
|
+
) -> Transcript:
|
|
666
|
+
options = Options(concurrency=concurrency, wait_timeout=wait_timeout)
|
|
667
|
+
return await apipeline.run_resume(
|
|
668
|
+
self._session, token, file, options, Callbacks(on_progress=on_progress)
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
async def aclose(self) -> None:
|
|
672
|
+
await self._http.aclose()
|
|
673
|
+
await self._r2.aclose()
|
|
674
|
+
|
|
675
|
+
async def __aenter__(self) -> Self:
|
|
676
|
+
return self
|
|
677
|
+
|
|
678
|
+
async def __aexit__(
|
|
679
|
+
self,
|
|
680
|
+
exc_type: type[BaseException] | None,
|
|
681
|
+
exc: BaseException | None,
|
|
682
|
+
traceback: TracebackType | None,
|
|
683
|
+
) -> None:
|
|
684
|
+
await self.aclose()
|
|
685
|
+
|
|
686
|
+
def __repr__(self) -> str:
|
|
687
|
+
return f"AsyncHushscript(base_url={self._config.base_url!r})"
|