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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
from attrs import define as _attrs_define
|
|
7
|
+
from attrs import field as _attrs_field
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T", bound="UploadLimits")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@_attrs_define
|
|
13
|
+
class UploadLimits:
|
|
14
|
+
"""The caller's current upload admission limits, shared by every response that carries them.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
max_daily_seconds (int): The account's daily transcription-minutes cap, in seconds.
|
|
18
|
+
max_active_jobs (int): The maximum number of concurrent pipeline jobs this account may have in flight.
|
|
19
|
+
active_jobs (int): How many pipeline jobs are currently in flight.
|
|
20
|
+
used_today_seconds (int): Billable seconds already used today, in the account's own rolling day.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
max_daily_seconds: int
|
|
24
|
+
max_active_jobs: int
|
|
25
|
+
active_jobs: int
|
|
26
|
+
used_today_seconds: int
|
|
27
|
+
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
28
|
+
|
|
29
|
+
def to_dict(self) -> dict[str, Any]:
|
|
30
|
+
max_daily_seconds = self.max_daily_seconds
|
|
31
|
+
|
|
32
|
+
max_active_jobs = self.max_active_jobs
|
|
33
|
+
|
|
34
|
+
active_jobs = self.active_jobs
|
|
35
|
+
|
|
36
|
+
used_today_seconds = self.used_today_seconds
|
|
37
|
+
|
|
38
|
+
field_dict: dict[str, Any] = {}
|
|
39
|
+
field_dict.update(self.additional_properties)
|
|
40
|
+
field_dict.update(
|
|
41
|
+
{
|
|
42
|
+
"max_daily_seconds": max_daily_seconds,
|
|
43
|
+
"max_active_jobs": max_active_jobs,
|
|
44
|
+
"active_jobs": active_jobs,
|
|
45
|
+
"used_today_seconds": used_today_seconds,
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return field_dict
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
53
|
+
d = dict(src_dict)
|
|
54
|
+
max_daily_seconds = d.pop("max_daily_seconds")
|
|
55
|
+
|
|
56
|
+
max_active_jobs = d.pop("max_active_jobs")
|
|
57
|
+
|
|
58
|
+
active_jobs = d.pop("active_jobs")
|
|
59
|
+
|
|
60
|
+
used_today_seconds = d.pop("used_today_seconds")
|
|
61
|
+
|
|
62
|
+
upload_limits = cls(
|
|
63
|
+
max_daily_seconds=max_daily_seconds,
|
|
64
|
+
max_active_jobs=max_active_jobs,
|
|
65
|
+
active_jobs=active_jobs,
|
|
66
|
+
used_today_seconds=used_today_seconds,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
upload_limits.additional_properties = d
|
|
70
|
+
return upload_limits
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def additional_keys(self) -> list[str]:
|
|
74
|
+
return list(self.additional_properties.keys())
|
|
75
|
+
|
|
76
|
+
def __getitem__(self, key: str) -> Any:
|
|
77
|
+
return self.additional_properties[key]
|
|
78
|
+
|
|
79
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
80
|
+
self.additional_properties[key] = value
|
|
81
|
+
|
|
82
|
+
def __delitem__(self, key: str) -> None:
|
|
83
|
+
del self.additional_properties[key]
|
|
84
|
+
|
|
85
|
+
def __contains__(self, key: str) -> bool:
|
|
86
|
+
return key in self.additional_properties
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
from attrs import define as _attrs_define
|
|
7
|
+
from attrs import field as _attrs_field
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T", bound="UploadPartResponse200")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@_attrs_define
|
|
13
|
+
class UploadPartResponse200:
|
|
14
|
+
"""
|
|
15
|
+
Attributes:
|
|
16
|
+
ok (bool):
|
|
17
|
+
part (int):
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
ok: bool
|
|
21
|
+
part: int
|
|
22
|
+
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
23
|
+
|
|
24
|
+
def to_dict(self) -> dict[str, Any]:
|
|
25
|
+
ok = self.ok
|
|
26
|
+
|
|
27
|
+
part = self.part
|
|
28
|
+
|
|
29
|
+
field_dict: dict[str, Any] = {}
|
|
30
|
+
field_dict.update(self.additional_properties)
|
|
31
|
+
field_dict.update(
|
|
32
|
+
{
|
|
33
|
+
"ok": ok,
|
|
34
|
+
"part": part,
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return field_dict
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
42
|
+
d = dict(src_dict)
|
|
43
|
+
ok = d.pop("ok")
|
|
44
|
+
|
|
45
|
+
part = d.pop("part")
|
|
46
|
+
|
|
47
|
+
upload_part_response_200 = cls(
|
|
48
|
+
ok=ok,
|
|
49
|
+
part=part,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
upload_part_response_200.additional_properties = d
|
|
53
|
+
return upload_part_response_200
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def additional_keys(self) -> list[str]:
|
|
57
|
+
return list(self.additional_properties.keys())
|
|
58
|
+
|
|
59
|
+
def __getitem__(self, key: str) -> Any:
|
|
60
|
+
return self.additional_properties[key]
|
|
61
|
+
|
|
62
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
63
|
+
self.additional_properties[key] = value
|
|
64
|
+
|
|
65
|
+
def __delitem__(self, key: str) -> None:
|
|
66
|
+
del self.additional_properties[key]
|
|
67
|
+
|
|
68
|
+
def __contains__(self, key: str) -> bool:
|
|
69
|
+
return key in self.additional_properties
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping
|
|
4
|
+
from typing import Any, TypeVar
|
|
5
|
+
|
|
6
|
+
from attrs import define as _attrs_define
|
|
7
|
+
from attrs import field as _attrs_field
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T", bound="Utterance")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@_attrs_define
|
|
13
|
+
class Utterance:
|
|
14
|
+
"""
|
|
15
|
+
Attributes:
|
|
16
|
+
speaker (str):
|
|
17
|
+
start_ms (int):
|
|
18
|
+
end_ms (int):
|
|
19
|
+
text (str):
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
speaker: str
|
|
23
|
+
start_ms: int
|
|
24
|
+
end_ms: int
|
|
25
|
+
text: str
|
|
26
|
+
additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
|
|
27
|
+
|
|
28
|
+
def to_dict(self) -> dict[str, Any]:
|
|
29
|
+
speaker = self.speaker
|
|
30
|
+
|
|
31
|
+
start_ms = self.start_ms
|
|
32
|
+
|
|
33
|
+
end_ms = self.end_ms
|
|
34
|
+
|
|
35
|
+
text = self.text
|
|
36
|
+
|
|
37
|
+
field_dict: dict[str, Any] = {}
|
|
38
|
+
field_dict.update(self.additional_properties)
|
|
39
|
+
field_dict.update(
|
|
40
|
+
{
|
|
41
|
+
"speaker": speaker,
|
|
42
|
+
"start_ms": start_ms,
|
|
43
|
+
"end_ms": end_ms,
|
|
44
|
+
"text": text,
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
return field_dict
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
|
|
52
|
+
d = dict(src_dict)
|
|
53
|
+
speaker = d.pop("speaker")
|
|
54
|
+
|
|
55
|
+
start_ms = d.pop("start_ms")
|
|
56
|
+
|
|
57
|
+
end_ms = d.pop("end_ms")
|
|
58
|
+
|
|
59
|
+
text = d.pop("text")
|
|
60
|
+
|
|
61
|
+
utterance = cls(
|
|
62
|
+
speaker=speaker,
|
|
63
|
+
start_ms=start_ms,
|
|
64
|
+
end_ms=end_ms,
|
|
65
|
+
text=text,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
utterance.additional_properties = d
|
|
69
|
+
return utterance
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def additional_keys(self) -> list[str]:
|
|
73
|
+
return list(self.additional_properties.keys())
|
|
74
|
+
|
|
75
|
+
def __getitem__(self, key: str) -> Any:
|
|
76
|
+
return self.additional_properties[key]
|
|
77
|
+
|
|
78
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
79
|
+
self.additional_properties[key] = value
|
|
80
|
+
|
|
81
|
+
def __delitem__(self, key: str) -> None:
|
|
82
|
+
del self.additional_properties[key]
|
|
83
|
+
|
|
84
|
+
def __contains__(self, key: str) -> bool:
|
|
85
|
+
return key in self.additional_properties
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Generated by scripts/generate.py from openapi.json and the conformance hints.json. Do not edit.
|
|
2
|
+
|
|
3
|
+
from typing import Final
|
|
4
|
+
|
|
5
|
+
OPERATION_SCOPES: Final[dict[str, str]] = {
|
|
6
|
+
"DELETE /v1/org/transcripts/{id}/hold": "org:write",
|
|
7
|
+
"GET /v1/account": "account:read",
|
|
8
|
+
"GET /v1/billing/cards": "billing",
|
|
9
|
+
"GET /v1/billing/holds": "account:read",
|
|
10
|
+
"GET /v1/billing/ledger": "account:read",
|
|
11
|
+
"GET /v1/billing/packs": "billing",
|
|
12
|
+
"GET /v1/billing/transactions": "account:read",
|
|
13
|
+
"GET /v1/jobs": "transcribe",
|
|
14
|
+
"GET /v1/jobs/{id}": "transcribe",
|
|
15
|
+
"GET /v1/org": "org:read",
|
|
16
|
+
"GET /v1/org/audit": "org:read",
|
|
17
|
+
"GET /v1/org/audit.csv": "org:read",
|
|
18
|
+
"GET /v1/org/invoices": "org:read",
|
|
19
|
+
"GET /v1/org/ledger": "org:read",
|
|
20
|
+
"GET /v1/org/ledger.csv": "org:read",
|
|
21
|
+
"GET /v1/org/matters": "org:read",
|
|
22
|
+
"GET /v1/org/members": "org:read",
|
|
23
|
+
"GET /v1/org/transcripts": "org:read",
|
|
24
|
+
"GET /v1/org/usage": "org:read",
|
|
25
|
+
"GET /v1/org/usage.csv": "org:read",
|
|
26
|
+
"GET /v1/transcription-context": "context",
|
|
27
|
+
"GET /v1/transcripts": "transcripts:read",
|
|
28
|
+
"GET /v1/transcripts/deleted": "transcripts:read",
|
|
29
|
+
"GET /v1/transcripts/{id}": "transcripts:read",
|
|
30
|
+
"GET /v1/uploads/{job}/part-urls": "transcribe",
|
|
31
|
+
"PATCH /v1/org/members/{user_id}": "org:write",
|
|
32
|
+
"POST /v1/agent/credentials/rotate": "pat:rotate",
|
|
33
|
+
"POST /v1/billing/cards/default": "billing",
|
|
34
|
+
"POST /v1/billing/purchase": "billing",
|
|
35
|
+
"POST /v1/billing/setup-session": "billing",
|
|
36
|
+
"POST /v1/org/custody": "org:write",
|
|
37
|
+
"POST /v1/org/transcripts/{id}/hold": "org:write",
|
|
38
|
+
"POST /v1/uploads": "transcribe",
|
|
39
|
+
"POST /v1/uploads/{job}/complete": "transcribe",
|
|
40
|
+
"POST /v1/uploads/{job}/heartbeat": "transcribe",
|
|
41
|
+
"PUT /v1/uploads/{job}/parts/{n}": "transcribe",
|
|
42
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Contains some shared types for properties"""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, MutableMapping
|
|
4
|
+
from http import HTTPStatus
|
|
5
|
+
from typing import IO, BinaryIO, Generic, Literal, TypeVar
|
|
6
|
+
|
|
7
|
+
from attrs import define
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Unset:
|
|
11
|
+
def __bool__(self) -> Literal[False]:
|
|
12
|
+
return False
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
UNSET: Unset = Unset()
|
|
16
|
+
|
|
17
|
+
# The types that `httpx.Client(files=)` can accept, copied from that library.
|
|
18
|
+
FileContent = IO[bytes] | bytes | str
|
|
19
|
+
FileTypes = (
|
|
20
|
+
# (filename, file (or bytes), content_type)
|
|
21
|
+
tuple[str | None, FileContent, str | None]
|
|
22
|
+
# (filename, file (or bytes), content_type, headers)
|
|
23
|
+
| tuple[str | None, FileContent, str | None, Mapping[str, str]]
|
|
24
|
+
)
|
|
25
|
+
RequestFiles = list[tuple[str, FileTypes]]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@define
|
|
29
|
+
class File:
|
|
30
|
+
"""Contains information for file uploads"""
|
|
31
|
+
|
|
32
|
+
payload: BinaryIO
|
|
33
|
+
file_name: str | None = None
|
|
34
|
+
mime_type: str | None = None
|
|
35
|
+
|
|
36
|
+
def to_tuple(self) -> FileTypes:
|
|
37
|
+
"""Return a tuple representation that httpx will accept for multipart/form-data"""
|
|
38
|
+
return self.file_name, self.payload, self.mime_type
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
T = TypeVar("T")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@define
|
|
45
|
+
class Response(Generic[T]):
|
|
46
|
+
"""A response from an endpoint"""
|
|
47
|
+
|
|
48
|
+
status_code: HTTPStatus
|
|
49
|
+
content: bytes
|
|
50
|
+
headers: MutableMapping[str, str]
|
|
51
|
+
parsed: T | None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
__all__ = ["UNSET", "File", "FileTypes", "RequestFiles", "Response", "Unset"]
|
hushscript/money.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Final
|
|
5
|
+
|
|
6
|
+
from ._json import ExtraFields
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True, slots=True)
|
|
10
|
+
class Money(ExtraFields):
|
|
11
|
+
"""A price as the API states it, with a display string in the `en` locale.
|
|
12
|
+
|
|
13
|
+
The amount digits and the grouping (minor-unit digits from Stripe's table, thousands grouped
|
|
14
|
+
with commas) are the same in every Hushscript SDK. The rest of `formatted` is this SDK's own: it
|
|
15
|
+
writes `$`, `€`, `£` or `¥` for USD, EUR, GBP and JPY, and for every other known currency the
|
|
16
|
+
code and a no-break space before the number (`KRW 1,234`), where the TypeScript SDK takes its
|
|
17
|
+
symbols from Intl and may print another. Compare `amount` and `currency`, never `formatted`,
|
|
18
|
+
across SDKs.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
amount: int
|
|
22
|
+
"""In the currency's minor units, from the API."""
|
|
23
|
+
currency: str
|
|
24
|
+
"""The ISO 4217 code as the API sends it."""
|
|
25
|
+
formatted: str
|
|
26
|
+
"""`amount` divided by Stripe's minor-unit digits for the currency, for example `$5.99`."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Stripe's minor-unit digits, which the API's amounts use (spec 6.5). They come from this table and
|
|
30
|
+
# never from locale data: ICU disagrees for IDR, HUF, ISK and others.
|
|
31
|
+
_ZERO_DIGITS: Final = frozenset(
|
|
32
|
+
"BIF CLP DJF GNF JPY KMF KRW MGA PYG RWF UGX VND VUV XAF XOF XPF".split() # noqa: SIM905
|
|
33
|
+
)
|
|
34
|
+
_THREE_DIGITS: Final = frozenset("BHD JOD KWD OMR TND".split()) # noqa: SIM905
|
|
35
|
+
# The ISO 4217 codes printed with the code in front; any other string is shown plainly, as
|
|
36
|
+
# "5.99 ZZZ", and never fails.
|
|
37
|
+
_KNOWN: Final = frozenset(
|
|
38
|
+
"""
|
|
39
|
+
AED AFN ALL AMD ANG AOA ARS AUD AWG AZN BAM BBD BDT BGN BHD BIF BMD BND BOB BRL BSD
|
|
40
|
+
BTN BWP BYN BZD CAD CDF CHF CLP CNY COP CRC CUP CVE CZK DJF DKK DOP DZD EGP ERN ETB
|
|
41
|
+
EUR FJD FKP GBP GEL GHS GIP GMD GNF GTQ GYD HKD HNL HTG HUF IDR ILS INR IQD IRR ISK
|
|
42
|
+
JMD JOD JPY KES KGS KHR KMF KPW KRW KWD KYD KZT LAK LBP LKR LRD LSL LYD MAD MDL MGA
|
|
43
|
+
MKD MMK MNT MOP MRU MUR MVR MWK MXN MYR MZN NAD NGN NIO NOK NPR NZD OMR PAB PEN PGK
|
|
44
|
+
PHP PKR PLN PYG QAR RON RSD RUB RWF SAR SBD SCR SDG SEK SGD SHP SLE SOS SRD SSP STN
|
|
45
|
+
SVC SYP SZL THB TJS TMT TND TOP TRY TTD TWD TZS UAH UGX USD UYU UZS VES VND VUV WST
|
|
46
|
+
XAF XCD XOF XPF YER ZAR ZMW ZWG
|
|
47
|
+
""".split() # noqa: SIM905
|
|
48
|
+
)
|
|
49
|
+
_SYMBOLS: Final = {"USD": "$", "EUR": "€", "GBP": "£", "JPY": "¥"}
|
|
50
|
+
_NO_BREAK_SPACE: Final = chr(0xA0)
|
|
51
|
+
_ZERO_DIGIT_COUNT: Final = 0
|
|
52
|
+
_THREE_DIGIT_COUNT: Final = 3
|
|
53
|
+
_DEFAULT_DIGITS: Final = 2
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def minor_unit_digits(code: str) -> int:
|
|
57
|
+
if code in _ZERO_DIGITS:
|
|
58
|
+
return _ZERO_DIGIT_COUNT
|
|
59
|
+
return _THREE_DIGIT_COUNT if code in _THREE_DIGITS else _DEFAULT_DIGITS
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def to_money(amount: int, currency: str) -> Money:
|
|
63
|
+
"""The `Money` of an API amount and currency; an unknown currency is shown as the plain amount
|
|
64
|
+
and the upper-cased code with 2 digits, and never raises."""
|
|
65
|
+
code = currency.upper()
|
|
66
|
+
digits = minor_unit_digits(code)
|
|
67
|
+
magnitude = abs(amount)
|
|
68
|
+
whole, fraction = divmod(magnitude, 10**digits)
|
|
69
|
+
known = code in _KNOWN
|
|
70
|
+
number = f"{whole:,}" if known else str(whole)
|
|
71
|
+
if digits:
|
|
72
|
+
number += f".{fraction:0{digits}d}"
|
|
73
|
+
sign = "-" if amount < 0 else ""
|
|
74
|
+
if not known:
|
|
75
|
+
return Money(amount, currency, f"{sign}{number} {code}")
|
|
76
|
+
symbol = _SYMBOLS.get(code)
|
|
77
|
+
if symbol is not None:
|
|
78
|
+
return Money(amount, currency, f"{sign}{symbol}{number}")
|
|
79
|
+
return Money(amount, currency, f"{sign}{code}{_NO_BREAK_SPACE}{number}")
|
hushscript/paginate.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, Generic, TypeVar, cast
|
|
6
|
+
|
|
7
|
+
from ._errors import HushscriptError
|
|
8
|
+
from ._json import ExtraFields
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import AsyncIterator, Awaitable, Callable, Iterator
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True, slots=True)
|
|
17
|
+
class Page(ExtraFields, Generic[T]):
|
|
18
|
+
"""One page of a list. `next_cursor` is opaque and None on the last page."""
|
|
19
|
+
|
|
20
|
+
items: list[T]
|
|
21
|
+
next_cursor: str | None
|
|
22
|
+
|
|
23
|
+
__hash__ = None # type: ignore[assignment] # holds a list, so it is unhashable by design
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if sys.version_info < (3, 11):
|
|
27
|
+
_page_setattr = cast("Callable[[object, str, object], None]", vars(Page)["__setattr__"])
|
|
28
|
+
|
|
29
|
+
def _page_setattr_compat(self: object, name: str, value: object) -> None:
|
|
30
|
+
# Python 3.10's generic constructor only ignores AttributeError for type metadata.
|
|
31
|
+
if name == "__orig_class__":
|
|
32
|
+
raise AttributeError(name)
|
|
33
|
+
_page_setattr(self, name, value)
|
|
34
|
+
|
|
35
|
+
Page.__setattr__ = _page_setattr_compat # type: ignore[method-assign]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def pagination_loop() -> HushscriptError:
|
|
39
|
+
return HushscriptError(
|
|
40
|
+
"The API returned the same page cursor it was sent; stopping instead of repeating it.",
|
|
41
|
+
code="pagination_loop",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def check_cursor_advanced(sent: str | None, page: Page[T]) -> None:
|
|
46
|
+
"""R89: a page that hands back the cursor it was asked with would repeat forever."""
|
|
47
|
+
if page.next_cursor and page.next_cursor == sent:
|
|
48
|
+
raise pagination_loop()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def paginate(fetch: Callable[[str | None], Page[T]]) -> Iterator[T]:
|
|
52
|
+
"""Walks a cursor-paged list one page at a time, yielding every item in order. The walk is lazy:
|
|
53
|
+
closing the iterator (or breaking out of a loop over it) sends no further request. A page that
|
|
54
|
+
hands back the cursor it was asked with would repeat forever, so it raises `pagination_loop`
|
|
55
|
+
once its items have been yielded."""
|
|
56
|
+
cursor: str | None = None
|
|
57
|
+
while True:
|
|
58
|
+
sent = cursor
|
|
59
|
+
page = fetch(sent)
|
|
60
|
+
yield from page.items
|
|
61
|
+
check_cursor_advanced(sent, page)
|
|
62
|
+
cursor = page.next_cursor or None
|
|
63
|
+
if cursor is None:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def apaginate(fetch: Callable[[str | None], Awaitable[Page[T]]]) -> AsyncIterator[T]:
|
|
68
|
+
"""`paginate` on the event loop; a cancellation between pages ends the walk there."""
|
|
69
|
+
cursor: str | None = None
|
|
70
|
+
while True:
|
|
71
|
+
sent = cursor
|
|
72
|
+
page = await fetch(sent)
|
|
73
|
+
for item in page.items:
|
|
74
|
+
yield item
|
|
75
|
+
check_cursor_advanced(sent, page)
|
|
76
|
+
cursor = page.next_cursor or None
|
|
77
|
+
if cursor is None:
|
|
78
|
+
return
|
hushscript/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
5
|
+
from hushscript.transcribe.preflight import invalid_option
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def check_page_size(page_size: object) -> None:
|
|
9
|
+
if page_size is None:
|
|
10
|
+
return
|
|
11
|
+
if isinstance(page_size, bool) or not isinstance(page_size, int) or page_size < 1:
|
|
12
|
+
raise invalid_option("page_size must be a whole number of 1 or more.")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def path_id(resource_id: object) -> str:
|
|
16
|
+
"""An id as one percent-encoded path segment. An empty or non-string id, `.` and `..` (which a
|
|
17
|
+
client or proxy would resolve to another route) and text that cannot be encoded as UTF-8 (a
|
|
18
|
+
lone surrogate) are `invalid_option`, before any request."""
|
|
19
|
+
if not isinstance(resource_id, str) or resource_id in {"", ".", ".."}:
|
|
20
|
+
raise invalid_option("An id is a non-empty string other than . and ..")
|
|
21
|
+
try:
|
|
22
|
+
return quote(resource_id, safe="")
|
|
23
|
+
except UnicodeEncodeError:
|
|
24
|
+
raise invalid_option("An id must be text that encodes as UTF-8.") from None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def list_params(cursor: str | None, page_size: int | None) -> dict[str, str | int]:
|
|
28
|
+
params: dict[str, str | int] = {}
|
|
29
|
+
if page_size is not None:
|
|
30
|
+
params["limit"] = page_size
|
|
31
|
+
if cursor is not None:
|
|
32
|
+
params["cursor"] = cursor
|
|
33
|
+
return params
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from hushscript._json import ExtraFields, as_dict, extra_fields
|
|
7
|
+
from hushscript._shape import as_flag, as_text, as_whole, check_shape, maybe_whole, to_datetime
|
|
8
|
+
from hushscript._shapes import ACCOUNT, BALANCE
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _expiry(fields: dict[str, object]) -> datetime | None:
|
|
15
|
+
expires = fields.get("credits_expire_at")
|
|
16
|
+
return None if expires is None else to_datetime(expires)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def minutes_of(seconds: int) -> int:
|
|
20
|
+
"""`seconds / 60` rounded toward zero, exact for any size of int."""
|
|
21
|
+
return seconds // 60 if seconds >= 0 else -(-seconds // 60)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class Balance(ExtraFields):
|
|
26
|
+
seconds: int
|
|
27
|
+
"""Spendable now, already net of holds (not "balance minus held"); may be negative
|
|
28
|
+
(clawback debt)."""
|
|
29
|
+
minutes: int
|
|
30
|
+
"""`seconds / 60`, rounded toward zero."""
|
|
31
|
+
held_seconds: int
|
|
32
|
+
"""Committed to in-flight jobs, and already deducted from `seconds`."""
|
|
33
|
+
expires_at: datetime | None
|
|
34
|
+
purchases_remaining: int | None
|
|
35
|
+
"""Agent accounts only, else None."""
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def from_account(cls, body: object) -> Balance:
|
|
39
|
+
check_shape(body, BALANCE, "account response")
|
|
40
|
+
fields = as_dict(body) or {}
|
|
41
|
+
seconds = as_whole(fields["balance_seconds"])
|
|
42
|
+
return cls(
|
|
43
|
+
_extra=extra_fields(
|
|
44
|
+
fields,
|
|
45
|
+
"balance_seconds",
|
|
46
|
+
"held_seconds",
|
|
47
|
+
"credits_expire_at",
|
|
48
|
+
"purchases_remaining_in_window",
|
|
49
|
+
),
|
|
50
|
+
seconds=seconds,
|
|
51
|
+
minutes=minutes_of(seconds),
|
|
52
|
+
held_seconds=as_whole(fields["held_seconds"]),
|
|
53
|
+
expires_at=_expiry(fields),
|
|
54
|
+
purchases_remaining=maybe_whole(fields.get("purchases_remaining_in_window")),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class UploadLimits(ExtraFields):
|
|
60
|
+
"""The caller's upload admission limits. Unknown API keys remain available in `extra`."""
|
|
61
|
+
|
|
62
|
+
max_daily_seconds: int
|
|
63
|
+
"""The daily transcription cap, in seconds."""
|
|
64
|
+
max_active_jobs: int
|
|
65
|
+
"""The most pipeline jobs in flight at once."""
|
|
66
|
+
active_jobs: int
|
|
67
|
+
"""Pipeline jobs in flight now."""
|
|
68
|
+
used_today_seconds: int
|
|
69
|
+
"""Billable seconds used today."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass(frozen=True, slots=True)
|
|
73
|
+
class Account(ExtraFields):
|
|
74
|
+
balance_seconds: int
|
|
75
|
+
"""Net of holds and clawback debt; may be negative."""
|
|
76
|
+
held_seconds: int
|
|
77
|
+
"""Committed to in-flight jobs, and already deducted from `balance_seconds`."""
|
|
78
|
+
credits_expire_at: datetime | None
|
|
79
|
+
card_on_file: bool
|
|
80
|
+
data_region: str
|
|
81
|
+
"""For example `us`, `eu`, `apac` or `oc`."""
|
|
82
|
+
limits: UploadLimits
|
|
83
|
+
purchases_remaining_in_window: int | None
|
|
84
|
+
"""Agent accounts only, else None."""
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def from_body(cls, body: object) -> Account:
|
|
88
|
+
check_shape(body, ACCOUNT, "account response")
|
|
89
|
+
fields = as_dict(body) or {}
|
|
90
|
+
limits = as_dict(fields["limits"]) or {}
|
|
91
|
+
return cls(
|
|
92
|
+
_extra=extra_fields(
|
|
93
|
+
fields,
|
|
94
|
+
"balance_seconds",
|
|
95
|
+
"held_seconds",
|
|
96
|
+
"credits_expire_at",
|
|
97
|
+
"card_on_file",
|
|
98
|
+
"data_region",
|
|
99
|
+
"limits",
|
|
100
|
+
"purchases_remaining_in_window",
|
|
101
|
+
),
|
|
102
|
+
balance_seconds=as_whole(fields["balance_seconds"]),
|
|
103
|
+
held_seconds=as_whole(fields["held_seconds"]),
|
|
104
|
+
credits_expire_at=_expiry(fields),
|
|
105
|
+
card_on_file=as_flag(fields["card_on_file"]),
|
|
106
|
+
data_region=as_text(fields["data_region"]),
|
|
107
|
+
limits=UploadLimits(
|
|
108
|
+
_extra=extra_fields(
|
|
109
|
+
limits,
|
|
110
|
+
"max_daily_seconds",
|
|
111
|
+
"max_active_jobs",
|
|
112
|
+
"active_jobs",
|
|
113
|
+
"used_today_seconds",
|
|
114
|
+
),
|
|
115
|
+
max_daily_seconds=as_whole(limits["max_daily_seconds"]),
|
|
116
|
+
max_active_jobs=as_whole(limits["max_active_jobs"]),
|
|
117
|
+
active_jobs=as_whole(limits["active_jobs"]),
|
|
118
|
+
used_today_seconds=as_whole(limits["used_today_seconds"]),
|
|
119
|
+
),
|
|
120
|
+
purchases_remaining_in_window=maybe_whole(fields.get("purchases_remaining_in_window")),
|
|
121
|
+
)
|