transcodely 0.3.2__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 +21 -21
- transcodely/_codec/json_codec.py +2 -3
- transcodely/_transport/error_mapping.py +8 -7
- transcodely/_transport/transport.py +26 -26
- transcodely/client.py +24 -23
- 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 +13 -15
- transcodely/resources/billing.py +5 -7
- transcodely/resources/events.py +10 -10
- transcodely/resources/health.py +1 -3
- transcodely/resources/jobs.py +29 -28
- transcodely/resources/memberships.py +7 -7
- transcodely/resources/organizations.py +6 -6
- transcodely/resources/origins.py +6 -6
- transcodely/resources/presets.py +7 -7
- transcodely/resources/users.py +7 -7
- transcodely/resources/videos.py +9 -8
- transcodely/resources/webhook_endpoints.py +32 -31
- transcodely/streaming.py +3 -2
- transcodely/types.py +219 -227
- transcodely/v1/job_pb2.py +52 -52
- transcodely/v1/job_pb2.pyi +4 -2
- transcodely/v1/subtitles_pb2.py +6 -6
- transcodely/v1/subtitles_pb2.pyi +6 -2
- transcodely/v1/thumbnails_pb2.py +6 -6
- transcodely/v1/thumbnails_pb2.pyi +4 -2
- transcodely/version.py +1 -1
- transcodely/webhooks/construct_event.py +5 -4
- transcodely/webhooks/signature.py +6 -6
- transcodely/webhooks/types.py +6 -5
- {transcodely-0.3.2.dist-info → transcodely-0.3.3.dist-info}/METADATA +2 -2
- {transcodely-0.3.2.dist-info → transcodely-0.3.3.dist-info}/RECORD +37 -37
- {transcodely-0.3.2.dist-info → transcodely-0.3.3.dist-info}/WHEEL +0 -0
- {transcodely-0.3.2.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`.
|
|
@@ -112,9 +99,27 @@ from .types import (
|
|
|
112
99
|
WebhookDelivery,
|
|
113
100
|
WebhookEndpoint,
|
|
114
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
|
+
)
|
|
115
115
|
|
|
116
116
|
__all__ = [
|
|
117
117
|
"API_VERSION",
|
|
118
|
+
"DEFAULT_BASE_URL",
|
|
119
|
+
"DEFAULT_TOLERANCE_SECONDS",
|
|
120
|
+
"EVENT_ID_HEADER",
|
|
121
|
+
"SDK_VERSION",
|
|
122
|
+
"SIGNATURE_HEADER",
|
|
118
123
|
"APIConnectionError",
|
|
119
124
|
"APIError",
|
|
120
125
|
"APIKey",
|
|
@@ -126,14 +131,10 @@ __all__ = [
|
|
|
126
131
|
"ChapterResult",
|
|
127
132
|
"ClipConfig",
|
|
128
133
|
"ConflictError",
|
|
129
|
-
"construct_event",
|
|
130
134
|
"ContentAwareConfig",
|
|
131
135
|
"DASHConfig",
|
|
132
|
-
"DEFAULT_BASE_URL",
|
|
133
|
-
"DEFAULT_TOLERANCE_SECONDS",
|
|
134
136
|
"DRMConfig",
|
|
135
137
|
"Event",
|
|
136
|
-
"EVENT_ID_HEADER",
|
|
137
138
|
"EventRequest",
|
|
138
139
|
"EventType",
|
|
139
140
|
"FieldViolation",
|
|
@@ -163,8 +164,6 @@ __all__ = [
|
|
|
163
164
|
"PresetVariant",
|
|
164
165
|
"RateLimitError",
|
|
165
166
|
"Resolution",
|
|
166
|
-
"SDK_VERSION",
|
|
167
|
-
"SIGNATURE_HEADER",
|
|
168
167
|
"StreamingConfig",
|
|
169
168
|
"SubtitleResult",
|
|
170
169
|
"SubtitleTrack",
|
|
@@ -174,7 +173,6 @@ __all__ = [
|
|
|
174
173
|
"TranscodelyError",
|
|
175
174
|
"User",
|
|
176
175
|
"UserWithOrganizations",
|
|
177
|
-
"verify_signature",
|
|
178
176
|
"Video",
|
|
179
177
|
"VideoCodec",
|
|
180
178
|
"VideoRendition",
|
|
@@ -189,7 +187,9 @@ __all__ = [
|
|
|
189
187
|
"WebhookError",
|
|
190
188
|
"WebhookEvent",
|
|
191
189
|
"WebhookPayloadError",
|
|
192
|
-
"Webhooks",
|
|
193
190
|
"WebhookSignatureError",
|
|
194
191
|
"WebhookTimestampError",
|
|
192
|
+
"Webhooks",
|
|
193
|
+
"construct_event",
|
|
194
|
+
"verify_signature",
|
|
195
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,14 +73,14 @@ class Transport:
|
|
|
71
73
|
self,
|
|
72
74
|
api_key: str,
|
|
73
75
|
*,
|
|
74
|
-
organization_id:
|
|
75
|
-
base_url:
|
|
76
|
+
organization_id: str | None = None,
|
|
77
|
+
base_url: str | None = None,
|
|
76
78
|
timeout: float = 30.0,
|
|
77
79
|
max_retries: int = 3,
|
|
78
|
-
api_version:
|
|
79
|
-
default_headers:
|
|
80
|
-
http_client:
|
|
81
|
-
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,
|
|
82
84
|
) -> None:
|
|
83
85
|
if not api_key:
|
|
84
86
|
raise ValueError("Transcodely: api_key is required")
|
|
@@ -93,16 +95,16 @@ class Transport:
|
|
|
93
95
|
self._owns_client = http_client is None
|
|
94
96
|
self._logger = logger
|
|
95
97
|
#: ID of the most recent successful or failed request, Stripe-style.
|
|
96
|
-
self.last_request_id:
|
|
98
|
+
self.last_request_id: str | None = None
|
|
97
99
|
|
|
98
100
|
def close(self) -> None:
|
|
99
101
|
if self._owns_client:
|
|
100
102
|
self._client.close()
|
|
101
103
|
|
|
102
|
-
def __enter__(self) ->
|
|
104
|
+
def __enter__(self) -> Self:
|
|
103
105
|
return self
|
|
104
106
|
|
|
105
|
-
def __exit__(self, *args:
|
|
107
|
+
def __exit__(self, *args: object) -> None:
|
|
106
108
|
self.close()
|
|
107
109
|
|
|
108
110
|
# ---------- unary ----------
|
|
@@ -113,7 +115,7 @@ class Transport:
|
|
|
113
115
|
method_name: str,
|
|
114
116
|
request: Message,
|
|
115
117
|
response: TRes,
|
|
116
|
-
opts:
|
|
118
|
+
opts: CallOptions | None = None,
|
|
117
119
|
) -> TRes:
|
|
118
120
|
opts = opts or CallOptions()
|
|
119
121
|
url = f"{self.base_url}/{service_name}/{method_name}"
|
|
@@ -163,7 +165,7 @@ class Transport:
|
|
|
163
165
|
method_name: str,
|
|
164
166
|
request: Message,
|
|
165
167
|
response_factory: Callable[[], TRes],
|
|
166
|
-
opts:
|
|
168
|
+
opts: CallOptions | None = None,
|
|
167
169
|
) -> Generator[TRes, None, None]:
|
|
168
170
|
opts = opts or CallOptions()
|
|
169
171
|
url = f"{self.base_url}/{service_name}/{method_name}"
|
|
@@ -209,7 +211,7 @@ class Transport:
|
|
|
209
211
|
self,
|
|
210
212
|
opts: CallOptions,
|
|
211
213
|
content_type: str,
|
|
212
|
-
idempotency_key:
|
|
214
|
+
idempotency_key: str | None = None,
|
|
213
215
|
) -> dict[str, str]:
|
|
214
216
|
headers = {
|
|
215
217
|
"content-type": content_type,
|
|
@@ -232,7 +234,7 @@ class Transport:
|
|
|
232
234
|
if self._logger is not None:
|
|
233
235
|
try:
|
|
234
236
|
self._logger(event)
|
|
235
|
-
except Exception:
|
|
237
|
+
except Exception: # noqa: BLE001, S110 — a caller's logger must never break requests
|
|
236
238
|
pass
|
|
237
239
|
|
|
238
240
|
def _with_retry(
|
|
@@ -243,7 +245,7 @@ class Transport:
|
|
|
243
245
|
fn: Callable[[int], TRes],
|
|
244
246
|
) -> TRes:
|
|
245
247
|
max_retries = opts.max_retries if opts.max_retries is not None else self.max_retries
|
|
246
|
-
last_error:
|
|
248
|
+
last_error: BaseException | None = None
|
|
247
249
|
for attempt in range(1, max_retries + 2):
|
|
248
250
|
try:
|
|
249
251
|
return fn(attempt)
|
|
@@ -264,9 +266,7 @@ def _is_retryable(exc: BaseException) -> bool:
|
|
|
264
266
|
return True
|
|
265
267
|
if exc.__class__.__name__ == "APIConnectionError":
|
|
266
268
|
return True
|
|
267
|
-
|
|
268
|
-
return True
|
|
269
|
-
return False
|
|
269
|
+
return exc.http_status is not None and exc.http_status >= 500
|
|
270
270
|
|
|
271
271
|
|
|
272
272
|
def _sleep_backoff(attempt: int, exc: BaseException) -> None:
|
|
@@ -312,5 +312,5 @@ def _try_json(data: bytes) -> Any:
|
|
|
312
312
|
import json
|
|
313
313
|
|
|
314
314
|
return json.loads(data.decode("utf-8"))
|
|
315
|
-
except
|
|
315
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
316
316
|
return data.decode("utf-8", errors="replace")
|
transcodely/client.py
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
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
|
|
@@ -38,14 +39,14 @@ class Transcodely:
|
|
|
38
39
|
self,
|
|
39
40
|
api_key: str,
|
|
40
41
|
*,
|
|
41
|
-
organization_id:
|
|
42
|
-
base_url:
|
|
42
|
+
organization_id: str | None = None,
|
|
43
|
+
base_url: str | None = None,
|
|
43
44
|
timeout: float = 30.0,
|
|
44
45
|
max_retries: int = 3,
|
|
45
|
-
api_version:
|
|
46
|
-
default_headers:
|
|
47
|
-
http_client:
|
|
48
|
-
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,
|
|
49
50
|
) -> None:
|
|
50
51
|
self._transport = Transport(
|
|
51
52
|
api_key,
|
|
@@ -58,31 +59,31 @@ class Transcodely:
|
|
|
58
59
|
http_client=http_client,
|
|
59
60
|
logger=logger,
|
|
60
61
|
)
|
|
61
|
-
self._jobs:
|
|
62
|
-
self._videos:
|
|
63
|
-
self._presets:
|
|
64
|
-
self._origins:
|
|
65
|
-
self._apps:
|
|
66
|
-
self._api_keys:
|
|
67
|
-
self._organizations:
|
|
68
|
-
self._memberships:
|
|
69
|
-
self._users:
|
|
70
|
-
self._health:
|
|
71
|
-
self._webhook_endpoints:
|
|
72
|
-
self._events:
|
|
73
|
-
self._billing:
|
|
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
|
|
74
75
|
|
|
75
76
|
def close(self) -> None:
|
|
76
77
|
self._transport.close()
|
|
77
78
|
|
|
78
|
-
def __enter__(self) ->
|
|
79
|
+
def __enter__(self) -> Self:
|
|
79
80
|
return self
|
|
80
81
|
|
|
81
|
-
def __exit__(self, *args:
|
|
82
|
+
def __exit__(self, *args: object) -> None:
|
|
82
83
|
self.close()
|
|
83
84
|
|
|
84
85
|
@property
|
|
85
|
-
def last_request_id(self) ->
|
|
86
|
+
def last_request_id(self) -> str | None:
|
|
86
87
|
"""Stripe-style: ID of the most recent successful or failed request."""
|
|
87
88
|
return self._transport.last_request_id
|
|
88
89
|
|
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
|
transcodely/resources/apps.py
CHANGED
|
@@ -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
|
|
@@ -20,7 +20,7 @@ class Apps:
|
|
|
20
20
|
req = fill_from_dict(app_pb2.CreateAppRequest(), kwargs)
|
|
21
21
|
return self._t.unary(_SERVICE, "Create", req, app_pb2.CreateAppResponse()).app
|
|
22
22
|
|
|
23
|
-
def get(self, id: str, opts:
|
|
23
|
+
def get(self, id: str, opts: CallOptions | None = None) -> app_pb2.App:
|
|
24
24
|
req = app_pb2.GetAppRequest(id=id)
|
|
25
25
|
return self._t.unary(_SERVICE, "Get", req, app_pb2.GetAppResponse(), opts).app
|
|
26
26
|
|
|
@@ -31,10 +31,10 @@ class Apps:
|
|
|
31
31
|
def list(
|
|
32
32
|
self,
|
|
33
33
|
*,
|
|
34
|
-
org_id:
|
|
35
|
-
limit:
|
|
36
|
-
include_archived:
|
|
37
|
-
opts:
|
|
34
|
+
org_id: str | None = None,
|
|
35
|
+
limit: int | None = None,
|
|
36
|
+
include_archived: bool | None = None,
|
|
37
|
+
opts: CallOptions | None = None,
|
|
38
38
|
) -> Page[app_pb2.App]:
|
|
39
39
|
"""List apps in an organization.
|
|
40
40
|
|
|
@@ -42,7 +42,7 @@ class Apps:
|
|
|
42
42
|
by the API. Pass ``include_archived=True`` to include archived apps.
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
|
-
def fetch(cursor:
|
|
45
|
+
def fetch(cursor: str | None) -> PageContents[app_pb2.App]:
|
|
46
46
|
req = app_pb2.ListAppsRequest()
|
|
47
47
|
if org_id is not None:
|
|
48
48
|
req.org_id = org_id
|
|
@@ -56,7 +56,7 @@ class Apps:
|
|
|
56
56
|
|
|
57
57
|
return Page(fetch)
|
|
58
58
|
|
|
59
|
-
def archive(self, id: str, opts:
|
|
59
|
+
def archive(self, id: str, opts: CallOptions | None = None) -> app_pb2.App:
|
|
60
60
|
req = app_pb2.ArchiveAppRequest(id=id)
|
|
61
61
|
return self._t.unary(_SERVICE, "Archive", req, app_pb2.ArchiveAppResponse(), opts).app
|
|
62
62
|
|
|
@@ -73,8 +73,8 @@ class Apps:
|
|
|
73
73
|
def update_spend_limit(
|
|
74
74
|
self,
|
|
75
75
|
app_id: str,
|
|
76
|
-
monthly_spend_limit_eur:
|
|
77
|
-
opts:
|
|
76
|
+
monthly_spend_limit_eur: float | None = None,
|
|
77
|
+
opts: CallOptions | None = None,
|
|
78
78
|
) -> app_pb2.App:
|
|
79
79
|
"""Set or clear an app's monthly transcoding spend cap.
|
|
80
80
|
|
|
@@ -92,7 +92,7 @@ class Apps:
|
|
|
92
92
|
).app
|
|
93
93
|
|
|
94
94
|
def set_spend_limit(
|
|
95
|
-
self, app_id: str, monthly_spend_limit_eur: float, opts:
|
|
95
|
+
self, app_id: str, monthly_spend_limit_eur: float, opts: CallOptions | None = None
|
|
96
96
|
) -> app_pb2.App:
|
|
97
97
|
"""Set the app's monthly transcoding spend cap in EUR (must be > 0).
|
|
98
98
|
|
|
@@ -102,13 +102,11 @@ class Apps:
|
|
|
102
102
|
"""
|
|
103
103
|
return self.update_spend_limit(app_id, monthly_spend_limit_eur, opts)
|
|
104
104
|
|
|
105
|
-
def clear_spend_limit(self, app_id: str, opts:
|
|
105
|
+
def clear_spend_limit(self, app_id: str, opts: CallOptions | None = None) -> app_pb2.App:
|
|
106
106
|
"""Clear the app's monthly spend cap, returning it to unlimited (the default)."""
|
|
107
107
|
return self.update_spend_limit(app_id, None, opts)
|
|
108
108
|
|
|
109
|
-
def get_spend(
|
|
110
|
-
self, app_id: str, opts: Optional[CallOptions] = None
|
|
111
|
-
) -> app_pb2.GetSpendResponse:
|
|
109
|
+
def get_spend(self, app_id: str, opts: CallOptions | None = None) -> app_pb2.GetSpendResponse:
|
|
112
110
|
"""Get the app's current-period transcoding spend against its limit.
|
|
113
111
|
|
|
114
112
|
Returns the billing-period bounds, EUR spent so far, the cap (if set), and
|