transcodely 0.3.1__py3-none-any.whl → 0.3.3__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.
- transcodely/__init__.py +41 -21
- transcodely/_codec/json_codec.py +2 -3
- transcodely/_transport/error_mapping.py +8 -7
- transcodely/_transport/transport.py +29 -25
- transcodely/client.py +38 -21
- transcodely/errors.py +12 -12
- transcodely/pagination.py +8 -8
- transcodely/resources/_helpers.py +6 -5
- transcodely/resources/api_keys.py +6 -6
- transcodely/resources/apps.py +53 -8
- transcodely/resources/billing.py +90 -0
- transcodely/resources/events.py +10 -10
- transcodely/resources/health.py +1 -3
- transcodely/resources/jobs.py +57 -29
- transcodely/resources/memberships.py +9 -8
- transcodely/resources/organizations.py +6 -6
- transcodely/resources/origins.py +6 -6
- transcodely/resources/presets.py +7 -7
- transcodely/resources/users.py +9 -8
- transcodely/resources/videos.py +48 -8
- transcodely/resources/webhook_endpoints.py +32 -31
- transcodely/streaming.py +3 -2
- transcodely/types.py +260 -182
- transcodely/v1/app_pb2.py +52 -38
- transcodely/v1/app_pb2.pyi +48 -4
- transcodely/v1/billing_pb2.py +65 -0
- transcodely/v1/billing_pb2.pyi +131 -0
- transcodely/v1/job_pb2.py +86 -65
- transcodely/v1/job_pb2.pyi +74 -10
- transcodely/v1/subtitles_pb2.py +13 -7
- transcodely/v1/subtitles_pb2.pyi +60 -3
- transcodely/v1/thumbnails_pb2.py +14 -8
- transcodely/v1/thumbnails_pb2.pyi +14 -4
- transcodely/v1/video_pb2.py +106 -64
- transcodely/v1/video_pb2.pyi +168 -10
- transcodely/v1/watermark_pb2.py +57 -0
- transcodely/v1/watermark_pb2.pyi +56 -0
- transcodely/v1/webhook_pb2.py +68 -64
- transcodely/v1/webhook_pb2.pyi +22 -6
- transcodely/version.py +1 -1
- transcodely/webhooks/construct_event.py +5 -4
- transcodely/webhooks/signature.py +6 -6
- transcodely/webhooks/types.py +34 -9
- {transcodely-0.3.1.dist-info → transcodely-0.3.3.dist-info}/METADATA +87 -4
- transcodely-0.3.3.dist-info/RECORD +91 -0
- transcodely-0.3.1.dist-info/RECORD +0 -86
- {transcodely-0.3.1.dist-info → transcodely-0.3.3.dist-info}/WHEEL +0 -0
- {transcodely-0.3.1.dist-info → transcodely-0.3.3.dist-info}/licenses/LICENSE +0 -0
transcodely/__init__.py
CHANGED
|
@@ -47,19 +47,6 @@ from .errors import (
|
|
|
47
47
|
WebhookTimestampError,
|
|
48
48
|
)
|
|
49
49
|
from .pagination import Page, PageContents
|
|
50
|
-
from .version import API_VERSION, DEFAULT_BASE_URL, SDK_VERSION
|
|
51
|
-
from .webhooks import (
|
|
52
|
-
DEFAULT_TOLERANCE_SECONDS,
|
|
53
|
-
EVENT_ID_HEADER,
|
|
54
|
-
SIGNATURE_HEADER,
|
|
55
|
-
Event,
|
|
56
|
-
EventRequest,
|
|
57
|
-
EventType,
|
|
58
|
-
WebhookEvent,
|
|
59
|
-
Webhooks,
|
|
60
|
-
construct_event,
|
|
61
|
-
verify_signature,
|
|
62
|
-
)
|
|
63
50
|
|
|
64
51
|
# Headline message classes — most callers reach for these. Everything else lives
|
|
65
52
|
# in :mod:`transcodely.types`.
|
|
@@ -68,11 +55,18 @@ from .types import (
|
|
|
68
55
|
App,
|
|
69
56
|
AudioTrackConfig,
|
|
70
57
|
AutoABRConfig,
|
|
58
|
+
ChapterPoint,
|
|
59
|
+
ChapterResult,
|
|
60
|
+
ClipConfig,
|
|
71
61
|
ContentAwareConfig,
|
|
72
62
|
DASHConfig,
|
|
73
63
|
DRMConfig,
|
|
74
64
|
HDRConfig,
|
|
75
65
|
HLSConfig,
|
|
66
|
+
Invoice,
|
|
67
|
+
InvoiceLineItem,
|
|
68
|
+
InvoiceLineType,
|
|
69
|
+
InvoiceStatus,
|
|
76
70
|
Job,
|
|
77
71
|
JobOutput,
|
|
78
72
|
JobPriority,
|
|
@@ -87,6 +81,7 @@ from .types import (
|
|
|
87
81
|
PresetVariant,
|
|
88
82
|
Resolution,
|
|
89
83
|
StreamingConfig,
|
|
84
|
+
SubtitleResult,
|
|
90
85
|
SubtitleTrack,
|
|
91
86
|
ThumbnailResult,
|
|
92
87
|
ThumbnailSpec,
|
|
@@ -95,16 +90,36 @@ from .types import (
|
|
|
95
90
|
Video,
|
|
96
91
|
VideoCodec,
|
|
97
92
|
VideoRendition,
|
|
93
|
+
VideoTextTrack,
|
|
98
94
|
VideoVariant,
|
|
99
95
|
WatchEventType,
|
|
100
96
|
WatchJobResponse,
|
|
101
97
|
WatchVideoResponse,
|
|
98
|
+
WatermarkConfig,
|
|
102
99
|
WebhookDelivery,
|
|
103
100
|
WebhookEndpoint,
|
|
104
101
|
)
|
|
102
|
+
from .version import API_VERSION, DEFAULT_BASE_URL, SDK_VERSION
|
|
103
|
+
from .webhooks import (
|
|
104
|
+
DEFAULT_TOLERANCE_SECONDS,
|
|
105
|
+
EVENT_ID_HEADER,
|
|
106
|
+
SIGNATURE_HEADER,
|
|
107
|
+
Event,
|
|
108
|
+
EventRequest,
|
|
109
|
+
EventType,
|
|
110
|
+
WebhookEvent,
|
|
111
|
+
Webhooks,
|
|
112
|
+
construct_event,
|
|
113
|
+
verify_signature,
|
|
114
|
+
)
|
|
105
115
|
|
|
106
116
|
__all__ = [
|
|
107
117
|
"API_VERSION",
|
|
118
|
+
"DEFAULT_BASE_URL",
|
|
119
|
+
"DEFAULT_TOLERANCE_SECONDS",
|
|
120
|
+
"EVENT_ID_HEADER",
|
|
121
|
+
"SDK_VERSION",
|
|
122
|
+
"SIGNATURE_HEADER",
|
|
108
123
|
"APIConnectionError",
|
|
109
124
|
"APIError",
|
|
110
125
|
"APIKey",
|
|
@@ -112,21 +127,24 @@ __all__ = [
|
|
|
112
127
|
"AudioTrackConfig",
|
|
113
128
|
"AuthenticationError",
|
|
114
129
|
"AutoABRConfig",
|
|
130
|
+
"ChapterPoint",
|
|
131
|
+
"ChapterResult",
|
|
132
|
+
"ClipConfig",
|
|
115
133
|
"ConflictError",
|
|
116
|
-
"construct_event",
|
|
117
134
|
"ContentAwareConfig",
|
|
118
135
|
"DASHConfig",
|
|
119
|
-
"DEFAULT_BASE_URL",
|
|
120
|
-
"DEFAULT_TOLERANCE_SECONDS",
|
|
121
136
|
"DRMConfig",
|
|
122
137
|
"Event",
|
|
123
|
-
"EVENT_ID_HEADER",
|
|
124
138
|
"EventRequest",
|
|
125
139
|
"EventType",
|
|
126
140
|
"FieldViolation",
|
|
127
141
|
"HDRConfig",
|
|
128
142
|
"HLSConfig",
|
|
129
143
|
"InvalidRequestError",
|
|
144
|
+
"Invoice",
|
|
145
|
+
"InvoiceLineItem",
|
|
146
|
+
"InvoiceLineType",
|
|
147
|
+
"InvoiceStatus",
|
|
130
148
|
"Job",
|
|
131
149
|
"JobOutput",
|
|
132
150
|
"JobPriority",
|
|
@@ -146,9 +164,8 @@ __all__ = [
|
|
|
146
164
|
"PresetVariant",
|
|
147
165
|
"RateLimitError",
|
|
148
166
|
"Resolution",
|
|
149
|
-
"SDK_VERSION",
|
|
150
|
-
"SIGNATURE_HEADER",
|
|
151
167
|
"StreamingConfig",
|
|
168
|
+
"SubtitleResult",
|
|
152
169
|
"SubtitleTrack",
|
|
153
170
|
"ThumbnailResult",
|
|
154
171
|
"ThumbnailSpec",
|
|
@@ -156,20 +173,23 @@ __all__ = [
|
|
|
156
173
|
"TranscodelyError",
|
|
157
174
|
"User",
|
|
158
175
|
"UserWithOrganizations",
|
|
159
|
-
"verify_signature",
|
|
160
176
|
"Video",
|
|
161
177
|
"VideoCodec",
|
|
162
178
|
"VideoRendition",
|
|
179
|
+
"VideoTextTrack",
|
|
163
180
|
"VideoVariant",
|
|
164
181
|
"WatchEventType",
|
|
165
182
|
"WatchJobResponse",
|
|
166
183
|
"WatchVideoResponse",
|
|
184
|
+
"WatermarkConfig",
|
|
167
185
|
"WebhookDelivery",
|
|
168
186
|
"WebhookEndpoint",
|
|
169
187
|
"WebhookError",
|
|
170
188
|
"WebhookEvent",
|
|
171
189
|
"WebhookPayloadError",
|
|
172
|
-
"Webhooks",
|
|
173
190
|
"WebhookSignatureError",
|
|
174
191
|
"WebhookTimestampError",
|
|
192
|
+
"Webhooks",
|
|
193
|
+
"construct_event",
|
|
194
|
+
"verify_signature",
|
|
175
195
|
]
|
transcodely/_codec/json_codec.py
CHANGED
|
@@ -132,9 +132,8 @@ def _transform_field(
|
|
|
132
132
|
parent[key] = _apply_enum(v, field.enum_type, mode)
|
|
133
133
|
return
|
|
134
134
|
|
|
135
|
-
if field.type == FieldDescriptor.TYPE_MESSAGE:
|
|
136
|
-
|
|
137
|
-
transform_enums_in_dict(v, field.message_type, mode=mode)
|
|
135
|
+
if field.type == FieldDescriptor.TYPE_MESSAGE and isinstance(v, dict):
|
|
136
|
+
transform_enums_in_dict(v, field.message_type, mode=mode)
|
|
138
137
|
|
|
139
138
|
|
|
140
139
|
def _apply_enum(value: str, enum_desc: EnumDescriptor, mode: str) -> str:
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
-
from
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from typing import Any
|
|
7
8
|
|
|
8
9
|
from ..errors import (
|
|
9
10
|
APIConnectionError,
|
|
@@ -55,7 +56,7 @@ def map_http_error(*, status: int, body: Any, headers: Mapping[str, str]) -> Tra
|
|
|
55
56
|
return InvalidRequestError(message, **kwargs)
|
|
56
57
|
if status == 429:
|
|
57
58
|
retry_after = _header(headers, "retry-after")
|
|
58
|
-
retry_after_ms:
|
|
59
|
+
retry_after_ms: int | None = None
|
|
59
60
|
if retry_after:
|
|
60
61
|
try:
|
|
61
62
|
retry_after_ms = max(0, int(retry_after) * 1000)
|
|
@@ -69,7 +70,7 @@ def map_http_error(*, status: int, body: Any, headers: Mapping[str, str]) -> Tra
|
|
|
69
70
|
return APIError(message, **kwargs)
|
|
70
71
|
|
|
71
72
|
|
|
72
|
-
def connection_error(cause: BaseException, message:
|
|
73
|
+
def connection_error(cause: BaseException, message: str | None = None) -> APIConnectionError:
|
|
73
74
|
msg = message or str(cause) or "network request failed"
|
|
74
75
|
err = APIConnectionError(msg)
|
|
75
76
|
err.__cause__ = cause
|
|
@@ -82,21 +83,21 @@ def _parse_body(body: Any) -> dict[str, Any]:
|
|
|
82
83
|
if isinstance(body, (bytes, bytearray)):
|
|
83
84
|
try:
|
|
84
85
|
return json.loads(body.decode("utf-8"))
|
|
85
|
-
except
|
|
86
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
86
87
|
return {"message": body.decode("utf-8", errors="replace")}
|
|
87
88
|
if isinstance(body, str):
|
|
88
89
|
try:
|
|
89
90
|
return json.loads(body)
|
|
90
|
-
except
|
|
91
|
+
except json.JSONDecodeError:
|
|
91
92
|
return {"message": body}
|
|
92
93
|
return {}
|
|
93
94
|
|
|
94
95
|
|
|
95
|
-
def _header(headers: Mapping[str, str], key: str) ->
|
|
96
|
+
def _header(headers: Mapping[str, str], key: str) -> str | None:
|
|
96
97
|
# httpx headers are case-insensitive but expose .get with the lowercase form.
|
|
97
98
|
if hasattr(headers, "get"):
|
|
98
99
|
try:
|
|
99
100
|
return headers.get(key) or headers.get(key.title())
|
|
100
|
-
except Exception:
|
|
101
|
+
except Exception: # noqa: BLE001 — headers may be any duck-typed mapping
|
|
101
102
|
return None
|
|
102
103
|
return None
|
|
@@ -19,11 +19,13 @@ from __future__ import annotations
|
|
|
19
19
|
import random
|
|
20
20
|
import struct
|
|
21
21
|
import time
|
|
22
|
+
from collections.abc import Callable, Generator, Iterable
|
|
22
23
|
from dataclasses import dataclass, field
|
|
23
|
-
from typing import Any,
|
|
24
|
+
from typing import Any, TypeVar
|
|
24
25
|
|
|
25
26
|
import httpx
|
|
26
27
|
from google.protobuf.message import Message
|
|
28
|
+
from typing_extensions import Self
|
|
27
29
|
|
|
28
30
|
from .._codec import json_codec
|
|
29
31
|
from ..errors import RateLimitError, TranscodelyError
|
|
@@ -39,10 +41,10 @@ TRes = TypeVar("TRes", bound=Message)
|
|
|
39
41
|
class CallOptions:
|
|
40
42
|
"""Per-call overrides for timeout, retries, idempotency, headers."""
|
|
41
43
|
|
|
42
|
-
timeout:
|
|
43
|
-
max_retries:
|
|
44
|
-
idempotency_key:
|
|
45
|
-
api_version:
|
|
44
|
+
timeout: float | None = None
|
|
45
|
+
max_retries: int | None = None
|
|
46
|
+
idempotency_key: str | None = None
|
|
47
|
+
api_version: str | None = None
|
|
46
48
|
headers: dict[str, str] = field(default_factory=dict)
|
|
47
49
|
|
|
48
50
|
|
|
@@ -52,9 +54,9 @@ class LogEvent:
|
|
|
52
54
|
method: str
|
|
53
55
|
duration_ms: int
|
|
54
56
|
attempt: int
|
|
55
|
-
status:
|
|
56
|
-
request_id:
|
|
57
|
-
error:
|
|
57
|
+
status: int | None = None
|
|
58
|
+
request_id: str | None = None
|
|
59
|
+
error: TranscodelyError | None = None
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
_WRITE_PREFIXES = ("Create", "Update", "Delete", "Cancel", "Confirm", "Revoke", "Archive", "Enable")
|
|
@@ -71,17 +73,19 @@ class Transport:
|
|
|
71
73
|
self,
|
|
72
74
|
api_key: str,
|
|
73
75
|
*,
|
|
74
|
-
|
|
76
|
+
organization_id: str | None = None,
|
|
77
|
+
base_url: str | None = None,
|
|
75
78
|
timeout: float = 30.0,
|
|
76
79
|
max_retries: int = 3,
|
|
77
|
-
api_version:
|
|
78
|
-
default_headers:
|
|
79
|
-
http_client:
|
|
80
|
-
logger:
|
|
80
|
+
api_version: str | None = None,
|
|
81
|
+
default_headers: dict[str, str] | None = None,
|
|
82
|
+
http_client: httpx.Client | None = None,
|
|
83
|
+
logger: Callable[[LogEvent], None] | None = None,
|
|
81
84
|
) -> None:
|
|
82
85
|
if not api_key:
|
|
83
86
|
raise ValueError("Transcodely: api_key is required")
|
|
84
87
|
self.api_key = api_key
|
|
88
|
+
self.organization_id = organization_id
|
|
85
89
|
self.base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
|
|
86
90
|
self.timeout = timeout
|
|
87
91
|
self.max_retries = max_retries
|
|
@@ -91,16 +95,16 @@ class Transport:
|
|
|
91
95
|
self._owns_client = http_client is None
|
|
92
96
|
self._logger = logger
|
|
93
97
|
#: ID of the most recent successful or failed request, Stripe-style.
|
|
94
|
-
self.last_request_id:
|
|
98
|
+
self.last_request_id: str | None = None
|
|
95
99
|
|
|
96
100
|
def close(self) -> None:
|
|
97
101
|
if self._owns_client:
|
|
98
102
|
self._client.close()
|
|
99
103
|
|
|
100
|
-
def __enter__(self) ->
|
|
104
|
+
def __enter__(self) -> Self:
|
|
101
105
|
return self
|
|
102
106
|
|
|
103
|
-
def __exit__(self, *args:
|
|
107
|
+
def __exit__(self, *args: object) -> None:
|
|
104
108
|
self.close()
|
|
105
109
|
|
|
106
110
|
# ---------- unary ----------
|
|
@@ -111,7 +115,7 @@ class Transport:
|
|
|
111
115
|
method_name: str,
|
|
112
116
|
request: Message,
|
|
113
117
|
response: TRes,
|
|
114
|
-
opts:
|
|
118
|
+
opts: CallOptions | None = None,
|
|
115
119
|
) -> TRes:
|
|
116
120
|
opts = opts or CallOptions()
|
|
117
121
|
url = f"{self.base_url}/{service_name}/{method_name}"
|
|
@@ -161,7 +165,7 @@ class Transport:
|
|
|
161
165
|
method_name: str,
|
|
162
166
|
request: Message,
|
|
163
167
|
response_factory: Callable[[], TRes],
|
|
164
|
-
opts:
|
|
168
|
+
opts: CallOptions | None = None,
|
|
165
169
|
) -> Generator[TRes, None, None]:
|
|
166
170
|
opts = opts or CallOptions()
|
|
167
171
|
url = f"{self.base_url}/{service_name}/{method_name}"
|
|
@@ -207,7 +211,7 @@ class Transport:
|
|
|
207
211
|
self,
|
|
208
212
|
opts: CallOptions,
|
|
209
213
|
content_type: str,
|
|
210
|
-
idempotency_key:
|
|
214
|
+
idempotency_key: str | None = None,
|
|
211
215
|
) -> dict[str, str]:
|
|
212
216
|
headers = {
|
|
213
217
|
"content-type": content_type,
|
|
@@ -218,6 +222,8 @@ class Transport:
|
|
|
218
222
|
"transcodely-version": opts.api_version or self.api_version,
|
|
219
223
|
"accept": content_type,
|
|
220
224
|
}
|
|
225
|
+
if self.organization_id:
|
|
226
|
+
headers["x-organization-id"] = self.organization_id
|
|
221
227
|
if idempotency_key:
|
|
222
228
|
headers["idempotency-key"] = idempotency_key
|
|
223
229
|
headers.update(self.default_headers)
|
|
@@ -228,7 +234,7 @@ class Transport:
|
|
|
228
234
|
if self._logger is not None:
|
|
229
235
|
try:
|
|
230
236
|
self._logger(event)
|
|
231
|
-
except Exception:
|
|
237
|
+
except Exception: # noqa: BLE001, S110 — a caller's logger must never break requests
|
|
232
238
|
pass
|
|
233
239
|
|
|
234
240
|
def _with_retry(
|
|
@@ -239,7 +245,7 @@ class Transport:
|
|
|
239
245
|
fn: Callable[[int], TRes],
|
|
240
246
|
) -> TRes:
|
|
241
247
|
max_retries = opts.max_retries if opts.max_retries is not None else self.max_retries
|
|
242
|
-
last_error:
|
|
248
|
+
last_error: BaseException | None = None
|
|
243
249
|
for attempt in range(1, max_retries + 2):
|
|
244
250
|
try:
|
|
245
251
|
return fn(attempt)
|
|
@@ -260,9 +266,7 @@ def _is_retryable(exc: BaseException) -> bool:
|
|
|
260
266
|
return True
|
|
261
267
|
if exc.__class__.__name__ == "APIConnectionError":
|
|
262
268
|
return True
|
|
263
|
-
|
|
264
|
-
return True
|
|
265
|
-
return False
|
|
269
|
+
return exc.http_status is not None and exc.http_status >= 500
|
|
266
270
|
|
|
267
271
|
|
|
268
272
|
def _sleep_backoff(attempt: int, exc: BaseException) -> None:
|
|
@@ -308,5 +312,5 @@ def _try_json(data: bytes) -> Any:
|
|
|
308
312
|
import json
|
|
309
313
|
|
|
310
314
|
return json.loads(data.decode("utf-8"))
|
|
311
|
-
except
|
|
315
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
312
316
|
return data.decode("utf-8", errors="replace")
|
transcodely/client.py
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Callable
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
|
+
from typing_extensions import Self
|
|
8
9
|
|
|
9
10
|
from ._transport.transport import LogEvent, Transport
|
|
10
11
|
from .resources.api_keys import ApiKeys
|
|
11
12
|
from .resources.apps import Apps
|
|
13
|
+
from .resources.billing import Billing
|
|
12
14
|
from .resources.events import Events
|
|
13
15
|
from .resources.health import Health
|
|
14
16
|
from .resources.jobs import Jobs
|
|
@@ -37,16 +39,18 @@ class Transcodely:
|
|
|
37
39
|
self,
|
|
38
40
|
api_key: str,
|
|
39
41
|
*,
|
|
40
|
-
|
|
42
|
+
organization_id: str | None = None,
|
|
43
|
+
base_url: str | None = None,
|
|
41
44
|
timeout: float = 30.0,
|
|
42
45
|
max_retries: int = 3,
|
|
43
|
-
api_version:
|
|
44
|
-
default_headers:
|
|
45
|
-
http_client:
|
|
46
|
-
logger:
|
|
46
|
+
api_version: str | None = None,
|
|
47
|
+
default_headers: dict[str, str] | None = None,
|
|
48
|
+
http_client: httpx.Client | None = None,
|
|
49
|
+
logger: Callable[[LogEvent], None] | None = None,
|
|
47
50
|
) -> None:
|
|
48
51
|
self._transport = Transport(
|
|
49
52
|
api_key,
|
|
53
|
+
organization_id=organization_id,
|
|
50
54
|
base_url=base_url,
|
|
51
55
|
timeout=timeout,
|
|
52
56
|
max_retries=max_retries,
|
|
@@ -55,30 +59,31 @@ class Transcodely:
|
|
|
55
59
|
http_client=http_client,
|
|
56
60
|
logger=logger,
|
|
57
61
|
)
|
|
58
|
-
self._jobs:
|
|
59
|
-
self._videos:
|
|
60
|
-
self._presets:
|
|
61
|
-
self._origins:
|
|
62
|
-
self._apps:
|
|
63
|
-
self._api_keys:
|
|
64
|
-
self._organizations:
|
|
65
|
-
self._memberships:
|
|
66
|
-
self._users:
|
|
67
|
-
self._health:
|
|
68
|
-
self._webhook_endpoints:
|
|
69
|
-
self._events:
|
|
62
|
+
self._jobs: Jobs | None = None
|
|
63
|
+
self._videos: Videos | None = None
|
|
64
|
+
self._presets: Presets | None = None
|
|
65
|
+
self._origins: Origins | None = None
|
|
66
|
+
self._apps: Apps | None = None
|
|
67
|
+
self._api_keys: ApiKeys | None = None
|
|
68
|
+
self._organizations: Organizations | None = None
|
|
69
|
+
self._memberships: Memberships | None = None
|
|
70
|
+
self._users: Users | None = None
|
|
71
|
+
self._health: Health | None = None
|
|
72
|
+
self._webhook_endpoints: WebhookEndpoints | None = None
|
|
73
|
+
self._events: Events | None = None
|
|
74
|
+
self._billing: Billing | None = None
|
|
70
75
|
|
|
71
76
|
def close(self) -> None:
|
|
72
77
|
self._transport.close()
|
|
73
78
|
|
|
74
|
-
def __enter__(self) ->
|
|
79
|
+
def __enter__(self) -> Self:
|
|
75
80
|
return self
|
|
76
81
|
|
|
77
|
-
def __exit__(self, *args:
|
|
82
|
+
def __exit__(self, *args: object) -> None:
|
|
78
83
|
self.close()
|
|
79
84
|
|
|
80
85
|
@property
|
|
81
|
-
def last_request_id(self) ->
|
|
86
|
+
def last_request_id(self) -> str | None:
|
|
82
87
|
"""Stripe-style: ID of the most recent successful or failed request."""
|
|
83
88
|
return self._transport.last_request_id
|
|
84
89
|
|
|
@@ -154,6 +159,18 @@ class Transcodely:
|
|
|
154
159
|
self._events = Events(self._transport)
|
|
155
160
|
return self._events
|
|
156
161
|
|
|
162
|
+
@property
|
|
163
|
+
def billing(self) -> Billing:
|
|
164
|
+
"""The organization's invoices.
|
|
165
|
+
|
|
166
|
+
Needs a dashboard session token for an organization owner plus
|
|
167
|
+
``organization_id`` on the client; API keys are rejected. See
|
|
168
|
+
:class:`~transcodely.resources.billing.Billing`.
|
|
169
|
+
"""
|
|
170
|
+
if self._billing is None:
|
|
171
|
+
self._billing = Billing(self._transport)
|
|
172
|
+
return self._billing
|
|
173
|
+
|
|
157
174
|
@property
|
|
158
175
|
def webhooks(self) -> type[Webhooks]:
|
|
159
176
|
"""Stripe-style facade: ``client.webhooks.construct_event(body, sig, secret)``."""
|
transcodely/errors.py
CHANGED
|
@@ -8,7 +8,7 @@ error handling. Mirrors the TypeScript and Go SDK error classes.
|
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
10
10
|
from dataclasses import dataclass
|
|
11
|
-
from typing import Any
|
|
11
|
+
from typing import Any
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass(frozen=True)
|
|
@@ -26,19 +26,19 @@ class TranscodelyError(Exception):
|
|
|
26
26
|
self,
|
|
27
27
|
message: str,
|
|
28
28
|
*,
|
|
29
|
-
code:
|
|
30
|
-
type:
|
|
31
|
-
errors:
|
|
32
|
-
http_status:
|
|
33
|
-
request_id:
|
|
29
|
+
code: str | None = None,
|
|
30
|
+
type: str | None = None,
|
|
31
|
+
errors: list[FieldViolation] | None = None,
|
|
32
|
+
http_status: int | None = None,
|
|
33
|
+
request_id: str | None = None,
|
|
34
34
|
raw: Any = None,
|
|
35
35
|
) -> None:
|
|
36
36
|
super().__init__(message)
|
|
37
|
-
self.code:
|
|
38
|
-
self.type:
|
|
37
|
+
self.code: str | None = code
|
|
38
|
+
self.type: str | None = type
|
|
39
39
|
self.errors: list[FieldViolation] = errors or []
|
|
40
|
-
self.http_status:
|
|
41
|
-
self.request_id:
|
|
40
|
+
self.http_status: int | None = http_status
|
|
41
|
+
self.request_id: str | None = request_id
|
|
42
42
|
self.raw: Any = raw
|
|
43
43
|
|
|
44
44
|
def __repr__(self) -> str:
|
|
@@ -79,11 +79,11 @@ class RateLimitError(TranscodelyError):
|
|
|
79
79
|
self,
|
|
80
80
|
message: str,
|
|
81
81
|
*,
|
|
82
|
-
retry_after_ms:
|
|
82
|
+
retry_after_ms: int | None = None,
|
|
83
83
|
**kwargs: Any,
|
|
84
84
|
) -> None:
|
|
85
85
|
super().__init__(message, **kwargs)
|
|
86
|
-
self.retry_after_ms:
|
|
86
|
+
self.retry_after_ms: int | None = retry_after_ms
|
|
87
87
|
|
|
88
88
|
|
|
89
89
|
class InvalidRequestError(TranscodelyError):
|
transcodely/pagination.py
CHANGED
|
@@ -6,8 +6,9 @@ Every ``list`` method returns a :class:`Page` you can iterate manually or via
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
from collections.abc import Callable, Generator, Iterator
|
|
9
10
|
from dataclasses import dataclass
|
|
10
|
-
from typing import
|
|
11
|
+
from typing import Generic, TypeVar
|
|
11
12
|
|
|
12
13
|
T = TypeVar("T")
|
|
13
14
|
|
|
@@ -15,22 +16,22 @@ T = TypeVar("T")
|
|
|
15
16
|
@dataclass
|
|
16
17
|
class PageContents(Generic[T]):
|
|
17
18
|
items: list[T]
|
|
18
|
-
next_cursor:
|
|
19
|
+
next_cursor: str | None
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class Page(Generic[T]):
|
|
22
23
|
"""A single page of results plus an opt-in iterator across all pages."""
|
|
23
24
|
|
|
24
|
-
def __init__(self, fetcher: Callable[[
|
|
25
|
+
def __init__(self, fetcher: Callable[[str | None], PageContents[T]]) -> None:
|
|
25
26
|
self._fetcher = fetcher
|
|
26
|
-
self._first:
|
|
27
|
+
self._first: PageContents[T] | None = None
|
|
27
28
|
|
|
28
29
|
@property
|
|
29
30
|
def items(self) -> list[T]:
|
|
30
31
|
return self._first_page().items
|
|
31
32
|
|
|
32
33
|
@property
|
|
33
|
-
def next_cursor(self) ->
|
|
34
|
+
def next_cursor(self) -> str | None:
|
|
34
35
|
return self._first_page().next_cursor
|
|
35
36
|
|
|
36
37
|
def __iter__(self) -> Iterator[T]:
|
|
@@ -38,11 +39,10 @@ class Page(Generic[T]):
|
|
|
38
39
|
|
|
39
40
|
def auto_paging_iter(self) -> Generator[T, None, None]:
|
|
40
41
|
"""Yield every item, transparently fetching subsequent pages."""
|
|
41
|
-
cursor:
|
|
42
|
+
cursor: str | None = None
|
|
42
43
|
while True:
|
|
43
44
|
page = self._fetcher(cursor)
|
|
44
|
-
|
|
45
|
-
yield item
|
|
45
|
+
yield from page.items
|
|
46
46
|
if not page.next_cursor:
|
|
47
47
|
return
|
|
48
48
|
cursor = page.next_cursor
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
6
7
|
|
|
7
8
|
from google.protobuf import json_format
|
|
8
9
|
from google.protobuf.descriptor import EnumDescriptor
|
|
@@ -23,7 +24,7 @@ def fill_from_dict(msg: Message, data: Mapping[str, Any]) -> Message:
|
|
|
23
24
|
return msg
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
def resolve_enum(value:
|
|
27
|
+
def resolve_enum(value: int | str, enum_desc: EnumDescriptor) -> int:
|
|
27
28
|
"""Resolve an enum value from either the raw proto int or a string.
|
|
28
29
|
|
|
29
30
|
Strings may be the simplified lowercase wire form (``"standard"``,
|
|
@@ -54,9 +55,9 @@ def resolve_enum(value: Union[int, str], enum_desc: EnumDescriptor) -> int:
|
|
|
54
55
|
def assign_pagination(
|
|
55
56
|
req_pagination: Any,
|
|
56
57
|
*,
|
|
57
|
-
limit:
|
|
58
|
-
cursor:
|
|
59
|
-
offset:
|
|
58
|
+
limit: int | None = None,
|
|
59
|
+
cursor: str | None = None,
|
|
60
|
+
offset: int | None = None,
|
|
60
61
|
) -> None:
|
|
61
62
|
if limit is not None:
|
|
62
63
|
req_pagination.limit = limit
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Any
|
|
5
|
+
from typing import Any
|
|
6
6
|
|
|
7
7
|
from .._transport.transport import CallOptions, Transport
|
|
8
8
|
from ..pagination import Page, PageContents
|
|
@@ -21,17 +21,17 @@ class ApiKeys:
|
|
|
21
21
|
req = fill_from_dict(api_key_pb2.CreateAPIKeyRequest(), kwargs)
|
|
22
22
|
return self._t.unary(_SERVICE, "Create", req, api_key_pb2.CreateAPIKeyResponse())
|
|
23
23
|
|
|
24
|
-
def get(self, id: str, opts:
|
|
24
|
+
def get(self, id: str, opts: CallOptions | None = None) -> api_key_pb2.APIKey:
|
|
25
25
|
req = api_key_pb2.GetAPIKeyRequest(id=id)
|
|
26
26
|
return self._t.unary(_SERVICE, "Get", req, api_key_pb2.GetAPIKeyResponse(), opts).api_key
|
|
27
27
|
|
|
28
28
|
def list(
|
|
29
29
|
self,
|
|
30
30
|
*,
|
|
31
|
-
limit:
|
|
32
|
-
opts:
|
|
31
|
+
limit: int | None = None,
|
|
32
|
+
opts: CallOptions | None = None,
|
|
33
33
|
) -> Page[api_key_pb2.APIKey]:
|
|
34
|
-
def fetch(cursor:
|
|
34
|
+
def fetch(cursor: str | None) -> PageContents[api_key_pb2.APIKey]:
|
|
35
35
|
req = api_key_pb2.ListAPIKeysRequest()
|
|
36
36
|
assign_pagination(req.pagination, limit=limit, cursor=cursor)
|
|
37
37
|
res = self._t.unary(_SERVICE, "List", req, api_key_pb2.ListAPIKeysResponse(), opts)
|
|
@@ -41,7 +41,7 @@ class ApiKeys:
|
|
|
41
41
|
|
|
42
42
|
return Page(fetch)
|
|
43
43
|
|
|
44
|
-
def revoke(self, id: str, opts:
|
|
44
|
+
def revoke(self, id: str, opts: CallOptions | None = None) -> api_key_pb2.APIKey:
|
|
45
45
|
req = api_key_pb2.RevokeAPIKeyRequest(id=id)
|
|
46
46
|
return self._t.unary(
|
|
47
47
|
_SERVICE, "Revoke", req, api_key_pb2.RevokeAPIKeyResponse(), opts
|