transcodely 0.3.6__tar.gz → 0.3.8__tar.gz
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-0.3.6 → transcodely-0.3.8}/PKG-INFO +53 -2
- {transcodely-0.3.6 → transcodely-0.3.8}/README.md +51 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/pyproject.toml +1 -1
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/__init__.py +10 -0
- transcodely-0.3.8/src/transcodely/resources/billing.py +266 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/types.py +71 -0
- transcodely-0.3.8/src/transcodely/v1/app_pb2.py +129 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/app_pb2.pyi +36 -2
- transcodely-0.3.8/src/transcodely/v1/billing_pb2.py +109 -0
- transcodely-0.3.8/src/transcodely/v1/billing_pb2.pyi +356 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/common_pb2.py +7 -1
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/common_pb2.pyi +43 -0
- transcodely-0.3.8/src/transcodely/v1/job_pb2.py +236 -0
- transcodely-0.3.8/src/transcodely/v1/organization_pb2.py +81 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/organization_pb2.pyi +22 -2
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/streaming_pb2.py +9 -9
- transcodely-0.3.8/src/transcodely/v1/subtitles_pb2.py +75 -0
- transcodely-0.3.8/src/transcodely/v1/video_pb2.py +210 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/video_pb2.pyi +6 -2
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/version.py +1 -1
- transcodely-0.3.6/src/transcodely/resources/billing.py +0 -90
- transcodely-0.3.6/src/transcodely/v1/app_pb2.py +0 -111
- transcodely-0.3.6/src/transcodely/v1/billing_pb2.py +0 -65
- transcodely-0.3.6/src/transcodely/v1/billing_pb2.pyi +0 -131
- transcodely-0.3.6/src/transcodely/v1/job_pb2.py +0 -232
- transcodely-0.3.6/src/transcodely/v1/organization_pb2.py +0 -81
- transcodely-0.3.6/src/transcodely/v1/subtitles_pb2.py +0 -75
- transcodely-0.3.6/src/transcodely/v1/video_pb2.py +0 -210
- {transcodely-0.3.6 → transcodely-0.3.8}/.gitignore +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/LICENSE +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/buf/__init__.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/buf/validate/__init__.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/buf/validate/validate_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/buf/validate/validate_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_codec/__init__.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_codec/json_codec.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_transport/__init__.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_transport/error_mapping.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_transport/headers.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/_transport/transport.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/client.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/errors.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/pagination.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/py.typed +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/__init__.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/_helpers.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/api_keys.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/apps.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/events.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/health.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/jobs.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/memberships.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/organizations.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/origins.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/presets.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/users.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/videos.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/resources/webhook_endpoints.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/streaming.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/api_key_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/api_key_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_av1_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_av1_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_h264_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_h264_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_h265_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_h265_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_vp9_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/codec_vp9_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/content_aware_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/content_aware_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/drm_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/drm_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/hdr_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/hdr_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/health_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/health_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/job_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/media_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/media_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/membership_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/membership_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/origin_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/origin_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/preset_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/preset_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/streaming_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/subtitles_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/thumbnails_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/thumbnails_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/user_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/user_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/watermark_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/watermark_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/webhook_pb2.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/v1/webhook_pb2.pyi +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/webhooks/__init__.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/webhooks/construct_event.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/webhooks/signature.py +0 -0
- {transcodely-0.3.6 → transcodely-0.3.8}/src/transcodely/webhooks/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: transcodely
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: Official Python SDK for the Transcodely video transcoding API
|
|
5
5
|
Project-URL: Homepage, https://transcodely.com
|
|
6
6
|
Project-URL: Documentation, https://transcodely.com/docs
|
|
@@ -122,6 +122,9 @@ client.origins # create / get / list / update / validate / archive
|
|
|
122
122
|
client.apps # create / get / list / update / archive / enable_hosting
|
|
123
123
|
client.api_keys # create / get / list / revoke
|
|
124
124
|
client.organizations # create / get / list / update / check_slug
|
|
125
|
+
client.billing # list_invoices / retrieve / retrieve_upcoming / retrieve_profile / create_portal_session
|
|
126
|
+
# + retrieve_budget / set_budget / clear_budget
|
|
127
|
+
# + retrieve_outstanding_balance / settle_outstanding_balance
|
|
125
128
|
client.memberships # list / get / update_role / remove
|
|
126
129
|
client.users # get_me / get / list / update_me
|
|
127
130
|
client.health # check
|
|
@@ -266,6 +269,54 @@ r2 = {
|
|
|
266
269
|
|
|
267
270
|
Provide either `account_id` or `endpoint`, never both. `jurisdiction` only applies when `account_id` is set.
|
|
268
271
|
|
|
272
|
+
## Billing
|
|
273
|
+
|
|
274
|
+
Billing settles a whole organization, so `client.billing` needs a dashboard session token for an
|
|
275
|
+
organization **owner** plus the organization it is for — an API key is scoped to one app and is
|
|
276
|
+
rejected outright:
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
client = Transcodely("tk_session_token", organization_id="org_f6g7h8i9j0")
|
|
280
|
+
|
|
281
|
+
client.billing.retrieve_upcoming() # what the current period has accrued
|
|
282
|
+
client.billing.list_invoices(limit=12)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Three numbers live here and they are not interchangeable:
|
|
286
|
+
|
|
287
|
+
| Call | What it answers | Enforces? |
|
|
288
|
+
|---|---|---|
|
|
289
|
+
| `billing.retrieve_upcoming()` | What the **current period** has accrued | No |
|
|
290
|
+
| `billing.retrieve_budget()` | Spend against the org's own monthly budget | No — emails only |
|
|
291
|
+
| `billing.retrieve_outstanding_balance()` | What is **unsettled**, incl. earlier periods | Yes, at the hard stop |
|
|
292
|
+
| `apps.set_spend_limit(app_id, eur)` | One app's monthly cap | Yes, at 100% |
|
|
293
|
+
|
|
294
|
+
Budgets notify; limits block:
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
client.billing.set_budget(500.0) # emails at 50% / 80% / 100%, restricts nothing
|
|
298
|
+
client.billing.clear_budget() # turns the alerts off
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The outstanding balance is the platform's own exposure control. Reminder emails go out at 80%,
|
|
302
|
+
100%, 125%, 150% and 175% of `threshold_cents` and restrict nothing; only at `hard_stop_cents`
|
|
303
|
+
(twice the threshold) are **new** jobs refused with code `outstanding_balance_exceeded` — queued
|
|
304
|
+
and running work still finishes and videos keep playing. Paying lifts the block on the next
|
|
305
|
+
request:
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
balance = client.billing.retrieve_outstanding_balance()
|
|
309
|
+
if balance.blocked and balance.settlement_available:
|
|
310
|
+
result = client.billing.settle_outstanding_balance()
|
|
311
|
+
print(result.settlement.invoice_id, result.balance.outstanding_cents) # → 0
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Check `settlement_available` before offering a "pay now" action: settlement raises
|
|
315
|
+
`PreconditionError` with code `settlement_unavailable` where the rail is switched off, and
|
|
316
|
+
`nothing_outstanding` when there is nothing to pay. Job creates refused for money reasons carry
|
|
317
|
+
`billing_past_due` (a statement is unpaid) or `outstanding_balance_exceeded` (too much unbilled
|
|
318
|
+
usage) — neither clears by retrying, so back-off is the wrong response to both.
|
|
319
|
+
|
|
269
320
|
## Errors
|
|
270
321
|
|
|
271
322
|
All exceptions inherit from `TranscodelyError`:
|
|
@@ -85,6 +85,9 @@ client.origins # create / get / list / update / validate / archive
|
|
|
85
85
|
client.apps # create / get / list / update / archive / enable_hosting
|
|
86
86
|
client.api_keys # create / get / list / revoke
|
|
87
87
|
client.organizations # create / get / list / update / check_slug
|
|
88
|
+
client.billing # list_invoices / retrieve / retrieve_upcoming / retrieve_profile / create_portal_session
|
|
89
|
+
# + retrieve_budget / set_budget / clear_budget
|
|
90
|
+
# + retrieve_outstanding_balance / settle_outstanding_balance
|
|
88
91
|
client.memberships # list / get / update_role / remove
|
|
89
92
|
client.users # get_me / get / list / update_me
|
|
90
93
|
client.health # check
|
|
@@ -229,6 +232,54 @@ r2 = {
|
|
|
229
232
|
|
|
230
233
|
Provide either `account_id` or `endpoint`, never both. `jurisdiction` only applies when `account_id` is set.
|
|
231
234
|
|
|
235
|
+
## Billing
|
|
236
|
+
|
|
237
|
+
Billing settles a whole organization, so `client.billing` needs a dashboard session token for an
|
|
238
|
+
organization **owner** plus the organization it is for — an API key is scoped to one app and is
|
|
239
|
+
rejected outright:
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
client = Transcodely("tk_session_token", organization_id="org_f6g7h8i9j0")
|
|
243
|
+
|
|
244
|
+
client.billing.retrieve_upcoming() # what the current period has accrued
|
|
245
|
+
client.billing.list_invoices(limit=12)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Three numbers live here and they are not interchangeable:
|
|
249
|
+
|
|
250
|
+
| Call | What it answers | Enforces? |
|
|
251
|
+
|---|---|---|
|
|
252
|
+
| `billing.retrieve_upcoming()` | What the **current period** has accrued | No |
|
|
253
|
+
| `billing.retrieve_budget()` | Spend against the org's own monthly budget | No — emails only |
|
|
254
|
+
| `billing.retrieve_outstanding_balance()` | What is **unsettled**, incl. earlier periods | Yes, at the hard stop |
|
|
255
|
+
| `apps.set_spend_limit(app_id, eur)` | One app's monthly cap | Yes, at 100% |
|
|
256
|
+
|
|
257
|
+
Budgets notify; limits block:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
client.billing.set_budget(500.0) # emails at 50% / 80% / 100%, restricts nothing
|
|
261
|
+
client.billing.clear_budget() # turns the alerts off
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
The outstanding balance is the platform's own exposure control. Reminder emails go out at 80%,
|
|
265
|
+
100%, 125%, 150% and 175% of `threshold_cents` and restrict nothing; only at `hard_stop_cents`
|
|
266
|
+
(twice the threshold) are **new** jobs refused with code `outstanding_balance_exceeded` — queued
|
|
267
|
+
and running work still finishes and videos keep playing. Paying lifts the block on the next
|
|
268
|
+
request:
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
balance = client.billing.retrieve_outstanding_balance()
|
|
272
|
+
if balance.blocked and balance.settlement_available:
|
|
273
|
+
result = client.billing.settle_outstanding_balance()
|
|
274
|
+
print(result.settlement.invoice_id, result.balance.outstanding_cents) # → 0
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Check `settlement_available` before offering a "pay now" action: settlement raises
|
|
278
|
+
`PreconditionError` with code `settlement_unavailable` where the rail is switched off, and
|
|
279
|
+
`nothing_outstanding` when there is nothing to pay. Job creates refused for money reasons carry
|
|
280
|
+
`billing_past_due` (a statement is unpaid) or `outstanding_balance_exceeded` (too much unbilled
|
|
281
|
+
usage) — neither clears by retrying, so back-off is the wrong response to both.
|
|
282
|
+
|
|
232
283
|
## Errors
|
|
233
284
|
|
|
234
285
|
All exceptions inherit from `TranscodelyError`:
|
|
@@ -55,6 +55,10 @@ from .types import (
|
|
|
55
55
|
App,
|
|
56
56
|
AudioTrackConfig,
|
|
57
57
|
AutoABRConfig,
|
|
58
|
+
BillingPaymentMethod,
|
|
59
|
+
BillingPortalSession,
|
|
60
|
+
BillingProfile,
|
|
61
|
+
BillingStanding,
|
|
58
62
|
ChapterPoint,
|
|
59
63
|
ChapterResult,
|
|
60
64
|
ClipConfig,
|
|
@@ -77,6 +81,7 @@ from .types import (
|
|
|
77
81
|
OutputFormat,
|
|
78
82
|
OutputSpec,
|
|
79
83
|
OutputStatus,
|
|
84
|
+
PaymentMethodState,
|
|
80
85
|
Preset,
|
|
81
86
|
PresetVariant,
|
|
82
87
|
Resolution,
|
|
@@ -127,6 +132,10 @@ __all__ = [
|
|
|
127
132
|
"AudioTrackConfig",
|
|
128
133
|
"AuthenticationError",
|
|
129
134
|
"AutoABRConfig",
|
|
135
|
+
"BillingPaymentMethod",
|
|
136
|
+
"BillingPortalSession",
|
|
137
|
+
"BillingProfile",
|
|
138
|
+
"BillingStanding",
|
|
130
139
|
"ChapterPoint",
|
|
131
140
|
"ChapterResult",
|
|
132
141
|
"ClipConfig",
|
|
@@ -158,6 +167,7 @@ __all__ = [
|
|
|
158
167
|
"OutputStatus",
|
|
159
168
|
"Page",
|
|
160
169
|
"PageContents",
|
|
170
|
+
"PaymentMethodState",
|
|
161
171
|
"PermissionError",
|
|
162
172
|
"PreconditionError",
|
|
163
173
|
"Preset",
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Billing resource — an organization's invoices, budget and outstanding balance."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .._transport.transport import CallOptions, Transport
|
|
6
|
+
from ..pagination import Page, PageContents
|
|
7
|
+
from ..v1 import billing_pb2
|
|
8
|
+
from ._helpers import assign_pagination
|
|
9
|
+
|
|
10
|
+
_SERVICE = "transcodely.v1.BillingService"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Billing:
|
|
14
|
+
"""An organization's billing statements.
|
|
15
|
+
|
|
16
|
+
Unlike every other resource, billing settles a whole organization rather
|
|
17
|
+
than a single app, so it is **not** available to API-key callers: a key is
|
|
18
|
+
scoped to one app, and there is no app-scoped subset of an invoice worth
|
|
19
|
+
serving. An API key gets a :class:`~transcodely.errors.PermissionError`.
|
|
20
|
+
|
|
21
|
+
Reading invoices needs a dashboard session token for an organization
|
|
22
|
+
**owner**, plus the organization the request is for::
|
|
23
|
+
|
|
24
|
+
client = Transcodely(session_token, organization_id="org_f6g7h8i9j0")
|
|
25
|
+
upcoming = client.billing.retrieve_upcoming()
|
|
26
|
+
|
|
27
|
+
Invoices are generated automatically when a period closes. There is no API
|
|
28
|
+
to create, edit, or delete one — a statement records what happened. The one
|
|
29
|
+
exception is :meth:`settle_outstanding_balance`, which closes the period
|
|
30
|
+
early and produces an ordinary statement for what is owed so far.
|
|
31
|
+
|
|
32
|
+
Monetary amounts are integer minor units (cents) of the invoice currency:
|
|
33
|
+
``total_cents == 1250`` in EUR is 12.50 EUR. The budget is the exception —
|
|
34
|
+
it is stated in whole EUR as a float, matching how a customer types it.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, transport: Transport) -> None:
|
|
38
|
+
self._t = transport
|
|
39
|
+
|
|
40
|
+
def list_invoices(
|
|
41
|
+
self,
|
|
42
|
+
*,
|
|
43
|
+
limit: int | None = None,
|
|
44
|
+
opts: CallOptions | None = None,
|
|
45
|
+
) -> Page[billing_pb2.Invoice]:
|
|
46
|
+
"""Page through finalized invoices, newest period first.
|
|
47
|
+
|
|
48
|
+
Line items are omitted here — use :meth:`retrieve` for one invoice's
|
|
49
|
+
breakdown. A statement still being generated for a just-ended period is
|
|
50
|
+
never returned; for the period currently accruing use
|
|
51
|
+
:meth:`retrieve_upcoming`.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def fetch(cursor: str | None) -> PageContents[billing_pb2.Invoice]:
|
|
55
|
+
req = billing_pb2.ListInvoicesRequest()
|
|
56
|
+
assign_pagination(req.pagination, limit=limit, cursor=cursor)
|
|
57
|
+
res = self._t.unary(
|
|
58
|
+
_SERVICE, "ListInvoices", req, billing_pb2.ListInvoicesResponse(), opts
|
|
59
|
+
)
|
|
60
|
+
return PageContents(
|
|
61
|
+
items=list(res.invoices),
|
|
62
|
+
next_cursor=res.pagination.next_cursor or None,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return Page(fetch)
|
|
66
|
+
|
|
67
|
+
def retrieve(self, invoice_id: str, opts: CallOptions | None = None) -> billing_pb2.Invoice:
|
|
68
|
+
"""Retrieve one invoice by ID (``inv_*``), including its line items."""
|
|
69
|
+
req = billing_pb2.GetInvoiceRequest(id=invoice_id)
|
|
70
|
+
return self._t.unary(
|
|
71
|
+
_SERVICE,
|
|
72
|
+
"GetInvoice",
|
|
73
|
+
req,
|
|
74
|
+
billing_pb2.GetInvoiceResponse(),
|
|
75
|
+
opts,
|
|
76
|
+
).invoice
|
|
77
|
+
|
|
78
|
+
def retrieve_upcoming(self, opts: CallOptions | None = None) -> billing_pb2.Invoice:
|
|
79
|
+
"""Retrieve the statement for the period currently accruing.
|
|
80
|
+
|
|
81
|
+
Computed live from settled jobs rather than stored, so its ``id`` is
|
|
82
|
+
empty, its status is ``draft``, and its totals move as jobs finish. Jobs
|
|
83
|
+
still running are not included at any price — a job is billed only once
|
|
84
|
+
it settles.
|
|
85
|
+
"""
|
|
86
|
+
req = billing_pb2.GetUpcomingInvoiceRequest()
|
|
87
|
+
return self._t.unary(
|
|
88
|
+
_SERVICE,
|
|
89
|
+
"GetUpcomingInvoice",
|
|
90
|
+
req,
|
|
91
|
+
billing_pb2.GetUpcomingInvoiceResponse(),
|
|
92
|
+
opts,
|
|
93
|
+
).invoice
|
|
94
|
+
|
|
95
|
+
def retrieve_profile(self, opts: CallOptions | None = None) -> billing_pb2.BillingProfile:
|
|
96
|
+
"""Retrieve the organization's payment standing.
|
|
97
|
+
|
|
98
|
+
Reports whether the payment provider holds a chargeable method, and
|
|
99
|
+
whatever it will say about that method for display. Read-only and
|
|
100
|
+
side-effect free — it never creates provider resources. An organization
|
|
101
|
+
that has never touched billing reports ``PAYMENT_METHOD_STATE_NONE``
|
|
102
|
+
and no payment methods.
|
|
103
|
+
|
|
104
|
+
``payment_method_state`` is the only reliable signal. A method's
|
|
105
|
+
``brand`` and ``last4`` are frequently absent even for a working card,
|
|
106
|
+
because the provider does not always expose card metadata; render such
|
|
107
|
+
a method as "Card on file" rather than treating the missing digits as
|
|
108
|
+
an error.
|
|
109
|
+
"""
|
|
110
|
+
req = billing_pb2.GetBillingProfileRequest()
|
|
111
|
+
return self._t.unary(
|
|
112
|
+
_SERVICE,
|
|
113
|
+
"GetBillingProfile",
|
|
114
|
+
req,
|
|
115
|
+
billing_pb2.GetBillingProfileResponse(),
|
|
116
|
+
opts,
|
|
117
|
+
).profile
|
|
118
|
+
|
|
119
|
+
def retrieve_budget(self, opts: CallOptions | None = None) -> billing_pb2.Budget:
|
|
120
|
+
"""Retrieve the organization's monthly budget and the spend against it.
|
|
121
|
+
|
|
122
|
+
A budget is the organization's own telemetry and it NEVER enforces
|
|
123
|
+
anything: crossing 100% sends an email and changes nothing else. The
|
|
124
|
+
hard cap is the per-app spend limit
|
|
125
|
+
(:meth:`~transcodely.resources.apps.Apps.set_spend_limit`), which does
|
|
126
|
+
refuse new jobs. Limits block; budgets notify.
|
|
127
|
+
|
|
128
|
+
Always returns a budget: an organization that has never set one gets
|
|
129
|
+
``amount_eur`` absent with ``spent_eur`` still populated, so the current
|
|
130
|
+
period's spend can be shown before a budget exists.
|
|
131
|
+
"""
|
|
132
|
+
req = billing_pb2.GetBudgetRequest()
|
|
133
|
+
return self._t.unary(
|
|
134
|
+
_SERVICE,
|
|
135
|
+
"GetBudget",
|
|
136
|
+
req,
|
|
137
|
+
billing_pb2.GetBudgetResponse(),
|
|
138
|
+
opts,
|
|
139
|
+
).budget
|
|
140
|
+
|
|
141
|
+
def update_budget(
|
|
142
|
+
self,
|
|
143
|
+
amount_eur: float | None = None,
|
|
144
|
+
opts: CallOptions | None = None,
|
|
145
|
+
) -> billing_pb2.Budget:
|
|
146
|
+
"""Set or clear the organization's monthly budget.
|
|
147
|
+
|
|
148
|
+
Pass ``amount_eur`` (must be > 0) to set the budget, or leave it
|
|
149
|
+
``None`` to clear it and stop the alert emails. :meth:`set_budget` and
|
|
150
|
+
:meth:`clear_budget` are the ergonomic shorthands.
|
|
151
|
+
|
|
152
|
+
Moving the amount never re-arms an alert step already sent this period —
|
|
153
|
+
read ``notified_steps`` on the returned budget to see which have fired.
|
|
154
|
+
"""
|
|
155
|
+
req = billing_pb2.UpdateBudgetRequest()
|
|
156
|
+
if amount_eur is not None:
|
|
157
|
+
# Setting the optional field marks presence; leaving it unset omits
|
|
158
|
+
# it from the request, which the server reads as "clear the budget".
|
|
159
|
+
req.amount_eur = amount_eur
|
|
160
|
+
return self._t.unary(
|
|
161
|
+
_SERVICE,
|
|
162
|
+
"UpdateBudget",
|
|
163
|
+
req,
|
|
164
|
+
billing_pb2.UpdateBudgetResponse(),
|
|
165
|
+
opts,
|
|
166
|
+
).budget
|
|
167
|
+
|
|
168
|
+
def set_budget(self, amount_eur: float, opts: CallOptions | None = None) -> billing_pb2.Budget:
|
|
169
|
+
"""Set the organization's monthly budget in EUR (must be > 0).
|
|
170
|
+
|
|
171
|
+
Alert emails go out as spend crosses each step in ``alert_steps`` (50%,
|
|
172
|
+
80% and 100% today). Nothing is restricted at any of them.
|
|
173
|
+
"""
|
|
174
|
+
return self.update_budget(amount_eur, opts)
|
|
175
|
+
|
|
176
|
+
def clear_budget(self, opts: CallOptions | None = None) -> billing_pb2.Budget:
|
|
177
|
+
"""Clear the monthly budget, turning the alert emails off (the default)."""
|
|
178
|
+
return self.update_budget(None, opts)
|
|
179
|
+
|
|
180
|
+
def retrieve_outstanding_balance(
|
|
181
|
+
self, opts: CallOptions | None = None
|
|
182
|
+
) -> billing_pb2.OutstandingBalance:
|
|
183
|
+
"""Retrieve the unsettled balance and the threshold it is measured against.
|
|
184
|
+
|
|
185
|
+
Usage that has been accrued and not yet captured onto a statement,
|
|
186
|
+
across every app in the organization. It is a different number from
|
|
187
|
+
:meth:`retrieve_upcoming` (which is what the *current period* accrued,
|
|
188
|
+
while this also carries anything an earlier period left uncaptured) and
|
|
189
|
+
from ``Budget.spent_eur`` (which keeps counting after an invoice is
|
|
190
|
+
issued, while this drops to zero when one is).
|
|
191
|
+
|
|
192
|
+
Reminder emails go out at 80%, 100%, 125%, 150% and 175% of
|
|
193
|
+
``threshold_cents`` and restrict nothing. Only at ``hard_stop_cents`` —
|
|
194
|
+
twice the threshold — are new jobs refused, with error code
|
|
195
|
+
``outstanding_balance_exceeded``; work already queued or running still
|
|
196
|
+
finishes and videos keep playing. ``blocked`` reads the same numbers the
|
|
197
|
+
admission gate reads.
|
|
198
|
+
|
|
199
|
+
Always returns a balance: an organization that owes nothing gets one
|
|
200
|
+
with ``outstanding_cents`` 0 and its threshold still populated.
|
|
201
|
+
"""
|
|
202
|
+
req = billing_pb2.GetOutstandingBalanceRequest()
|
|
203
|
+
return self._t.unary(
|
|
204
|
+
_SERVICE,
|
|
205
|
+
"GetOutstandingBalance",
|
|
206
|
+
req,
|
|
207
|
+
billing_pb2.GetOutstandingBalanceResponse(),
|
|
208
|
+
opts,
|
|
209
|
+
).balance
|
|
210
|
+
|
|
211
|
+
def settle_outstanding_balance(
|
|
212
|
+
self, opts: CallOptions | None = None
|
|
213
|
+
) -> billing_pb2.SettleOutstandingBalanceResponse:
|
|
214
|
+
"""Pay the outstanding balance now, without waiting for the period to end.
|
|
215
|
+
|
|
216
|
+
Closes the current period at this instant and produces a real statement
|
|
217
|
+
for everything owed, which the payment provider then charges. On success
|
|
218
|
+
the balance is zero, any admission block is lifted immediately, and the
|
|
219
|
+
trust tier re-evaluates once the statement is paid.
|
|
220
|
+
|
|
221
|
+
Takes no amount on purpose: the figure is whatever the ledger says at
|
|
222
|
+
the instant the settlement runs, so a stale page cannot pay less than is
|
|
223
|
+
owed and leave the difference looking settled.
|
|
224
|
+
|
|
225
|
+
Returns both the :class:`~transcodely.types.Settlement` and the balance
|
|
226
|
+
as it now stands, so a caller that just paid can render the new state
|
|
227
|
+
without a second request.
|
|
228
|
+
|
|
229
|
+
Raises :class:`~transcodely.errors.PreconditionError` with code
|
|
230
|
+
``settlement_unavailable`` when the deployment has mid-cycle settlement
|
|
231
|
+
switched off — check ``settlement_available`` on the balance before
|
|
232
|
+
offering a "pay now" action — and ``nothing_outstanding`` when there is
|
|
233
|
+
nothing to pay.
|
|
234
|
+
"""
|
|
235
|
+
req = billing_pb2.SettleOutstandingBalanceRequest()
|
|
236
|
+
return self._t.unary(
|
|
237
|
+
_SERVICE,
|
|
238
|
+
"SettleOutstandingBalance",
|
|
239
|
+
req,
|
|
240
|
+
billing_pb2.SettleOutstandingBalanceResponse(),
|
|
241
|
+
opts,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def create_portal_session(
|
|
245
|
+
self, opts: CallOptions | None = None
|
|
246
|
+
) -> billing_pb2.BillingPortalSession:
|
|
247
|
+
"""Create a session for the payment provider's hosted billing portal.
|
|
248
|
+
|
|
249
|
+
The portal is where a payment method is added or replaced and where
|
|
250
|
+
receipts live — card details never touch this SDK.
|
|
251
|
+
|
|
252
|
+
The first call for an organization also links it to the payment
|
|
253
|
+
provider, so the returned portal is already attached to this
|
|
254
|
+
organization's billing account. Safe to call repeatedly.
|
|
255
|
+
|
|
256
|
+
The session is single-use and expires; request a fresh one per visit
|
|
257
|
+
rather than storing the URL.
|
|
258
|
+
"""
|
|
259
|
+
req = billing_pb2.CreateBillingPortalSessionRequest()
|
|
260
|
+
return self._t.unary(
|
|
261
|
+
_SERVICE,
|
|
262
|
+
"CreateBillingPortalSession",
|
|
263
|
+
req,
|
|
264
|
+
billing_pb2.CreateBillingPortalSessionResponse(),
|
|
265
|
+
opts,
|
|
266
|
+
).session
|
|
@@ -61,9 +61,34 @@ from transcodely.v1.app_pb2 import (
|
|
|
61
61
|
# token for an organization owner plus ``organization_id`` — an API key is
|
|
62
62
|
# scoped to one app, while an invoice settles a whole organization, so API keys
|
|
63
63
|
# are rejected.
|
|
64
|
+
#
|
|
65
|
+
# ``BillingProfile`` reports whether the payment provider holds a chargeable
|
|
66
|
+
# method; a portal session is the only place a card is added or replaced, so
|
|
67
|
+
# card details never reach this SDK. Branch on ``PaymentMethodState``, not on
|
|
68
|
+
# the card fields — ``brand`` and ``last4`` are frequently absent even for a
|
|
69
|
+
# working card.
|
|
70
|
+
#
|
|
71
|
+
# ``Budget`` and ``OutstandingBalance`` are three different numbers away from
|
|
72
|
+
# each other and from an invoice: a budget notifies and never enforces, an
|
|
73
|
+
# upcoming invoice is what the CURRENT PERIOD has accrued, and an outstanding
|
|
74
|
+
# balance is what is UNSETTLED — the one that decides whether new jobs are
|
|
75
|
+
# admitted.
|
|
64
76
|
from transcodely.v1.billing_pb2 import (
|
|
77
|
+
BillingPaymentMethod,
|
|
78
|
+
BillingPortalSession,
|
|
79
|
+
BillingProfile,
|
|
80
|
+
Budget,
|
|
81
|
+
CreateBillingPortalSessionRequest,
|
|
82
|
+
CreateBillingPortalSessionResponse,
|
|
83
|
+
ExposureThresholdSource,
|
|
84
|
+
GetBillingProfileRequest,
|
|
85
|
+
GetBillingProfileResponse,
|
|
86
|
+
GetBudgetRequest,
|
|
87
|
+
GetBudgetResponse,
|
|
65
88
|
GetInvoiceRequest,
|
|
66
89
|
GetInvoiceResponse,
|
|
90
|
+
GetOutstandingBalanceRequest,
|
|
91
|
+
GetOutstandingBalanceResponse,
|
|
67
92
|
GetUpcomingInvoiceRequest,
|
|
68
93
|
GetUpcomingInvoiceResponse,
|
|
69
94
|
Invoice,
|
|
@@ -72,13 +97,36 @@ from transcodely.v1.billing_pb2 import (
|
|
|
72
97
|
InvoiceStatus,
|
|
73
98
|
ListInvoicesRequest,
|
|
74
99
|
ListInvoicesResponse,
|
|
100
|
+
OutstandingBalance,
|
|
101
|
+
PaymentMethodState,
|
|
102
|
+
Settlement,
|
|
103
|
+
SettleOutstandingBalanceRequest,
|
|
104
|
+
SettleOutstandingBalanceResponse,
|
|
105
|
+
TrustTier,
|
|
106
|
+
UpdateBudgetRequest,
|
|
107
|
+
UpdateBudgetResponse,
|
|
75
108
|
)
|
|
76
109
|
from transcodely.v1.codec_av1_pb2 import AV1Options
|
|
77
110
|
from transcodely.v1.codec_h264_pb2 import H264Options
|
|
78
111
|
from transcodely.v1.codec_h265_pb2 import H265Options
|
|
79
112
|
from transcodely.v1.codec_vp9_pb2 import VP9Options
|
|
113
|
+
|
|
114
|
+
# ``BillingStanding`` is ``BillingProfile.standing`` — derived from the
|
|
115
|
+
# organization's billing facts rather than assigned, so there is nothing to set.
|
|
116
|
+
# ``FREE`` and ``DELINQUENT`` both resolve usage limits to the free tier's, but
|
|
117
|
+
# ``DELINQUENT`` still bills; neither is a suspension. An organization exempt
|
|
118
|
+
# from the payment-method requirement always reports ``ACTIVE``.
|
|
119
|
+
#
|
|
120
|
+
# ``BillingTreatment`` is ``Organization.billing_treatment`` — orthogonal to
|
|
121
|
+
# standing, and assigned rather than derived: standing is a fact about payment
|
|
122
|
+
# health, treatment is the decision about whether automation may act on it. It
|
|
123
|
+
# is populated on admin reads only. ``DunningStage`` belongs to the same wave
|
|
124
|
+
# but reaches no message this SDK can receive, so it stays unexported; reach it
|
|
125
|
+
# at ``transcodely.v1.common_pb2.DunningStage`` if you are decoding admin JSON.
|
|
80
126
|
from transcodely.v1.common_pb2 import (
|
|
81
127
|
AudioCodec,
|
|
128
|
+
BillingStanding,
|
|
129
|
+
BillingTreatment,
|
|
82
130
|
BitrateMode,
|
|
83
131
|
Container,
|
|
84
132
|
ContentType,
|
|
@@ -388,7 +436,13 @@ __all__ = [
|
|
|
388
436
|
"AutoABRConfig",
|
|
389
437
|
"AutoProfileDefaults",
|
|
390
438
|
"BYOKConfig",
|
|
439
|
+
"BillingPaymentMethod",
|
|
440
|
+
"BillingPortalSession",
|
|
441
|
+
"BillingProfile",
|
|
442
|
+
"BillingStanding",
|
|
443
|
+
"BillingTreatment",
|
|
391
444
|
"BitrateMode",
|
|
445
|
+
"Budget",
|
|
392
446
|
"BurnInStyle",
|
|
393
447
|
"CancelJobRequest",
|
|
394
448
|
"CancelJobResponse",
|
|
@@ -414,6 +468,8 @@ __all__ = [
|
|
|
414
468
|
"CreateAPIKeyResponse",
|
|
415
469
|
"CreateAppRequest",
|
|
416
470
|
"CreateAppResponse",
|
|
471
|
+
"CreateBillingPortalSessionRequest",
|
|
472
|
+
"CreateBillingPortalSessionResponse",
|
|
417
473
|
"CreateFromUrlRequest",
|
|
418
474
|
"CreateFromUrlResponse",
|
|
419
475
|
"CreateJobRequest",
|
|
@@ -450,6 +506,7 @@ __all__ = [
|
|
|
450
506
|
"EventRequest",
|
|
451
507
|
"EventType",
|
|
452
508
|
"ExecutionTiming",
|
|
509
|
+
"ExposureThresholdSource",
|
|
453
510
|
"GOPAlignmentMode",
|
|
454
511
|
"GcsCredentials",
|
|
455
512
|
"GcsOriginConfig",
|
|
@@ -457,6 +514,10 @@ __all__ = [
|
|
|
457
514
|
"GetAPIKeyResponse",
|
|
458
515
|
"GetAppRequest",
|
|
459
516
|
"GetAppResponse",
|
|
517
|
+
"GetBillingProfileRequest",
|
|
518
|
+
"GetBillingProfileResponse",
|
|
519
|
+
"GetBudgetRequest",
|
|
520
|
+
"GetBudgetResponse",
|
|
460
521
|
"GetEndpointHealthRequest",
|
|
461
522
|
"GetEndpointHealthResponse",
|
|
462
523
|
"GetInvoiceRequest",
|
|
@@ -471,6 +532,8 @@ __all__ = [
|
|
|
471
532
|
"GetOrganizationResponse",
|
|
472
533
|
"GetOriginRequest",
|
|
473
534
|
"GetOriginResponse",
|
|
535
|
+
"GetOutstandingBalanceRequest",
|
|
536
|
+
"GetOutstandingBalanceResponse",
|
|
474
537
|
"GetPresetBySlugRequest",
|
|
475
538
|
"GetPresetBySlugResponse",
|
|
476
539
|
"GetPresetRequest",
|
|
@@ -557,8 +620,10 @@ __all__ = [
|
|
|
557
620
|
"OutputSpec",
|
|
558
621
|
"OutputStatus",
|
|
559
622
|
"OutputVariantResult",
|
|
623
|
+
"OutstandingBalance",
|
|
560
624
|
"PaginationRequest",
|
|
561
625
|
"PaginationResponse",
|
|
626
|
+
"PaymentMethodState",
|
|
562
627
|
"Preset",
|
|
563
628
|
"PresetVariant",
|
|
564
629
|
"PricingSnapshot",
|
|
@@ -581,6 +646,9 @@ __all__ = [
|
|
|
581
646
|
"SegmentConfig",
|
|
582
647
|
"SendTestWebhookRequest",
|
|
583
648
|
"SendTestWebhookResponse",
|
|
649
|
+
"SettleOutstandingBalanceRequest",
|
|
650
|
+
"SettleOutstandingBalanceResponse",
|
|
651
|
+
"Settlement",
|
|
584
652
|
"StreamingConfig",
|
|
585
653
|
"SubtitleFormat",
|
|
586
654
|
"SubtitleOperation",
|
|
@@ -593,8 +661,11 @@ __all__ = [
|
|
|
593
661
|
"ThumbnailSpec",
|
|
594
662
|
"ToneMapping",
|
|
595
663
|
"TopVideo",
|
|
664
|
+
"TrustTier",
|
|
596
665
|
"UpdateAppRequest",
|
|
597
666
|
"UpdateAppResponse",
|
|
667
|
+
"UpdateBudgetRequest",
|
|
668
|
+
"UpdateBudgetResponse",
|
|
598
669
|
"UpdateHostingConfigRequest",
|
|
599
670
|
"UpdateHostingConfigResponse",
|
|
600
671
|
"UpdateMeRequest",
|