transcodely 0.3.7__py3-none-any.whl → 0.3.9__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 +8 -0
- transcodely/client.py +9 -0
- transcodely/resources/billing.py +131 -3
- transcodely/resources/events.py +12 -2
- transcodely/resources/ingest_rules.py +183 -0
- transcodely/resources/webhook_endpoints.py +12 -2
- transcodely/types.py +119 -5
- transcodely/v1/api_key_pb2.py +19 -19
- transcodely/v1/app_pb2.py +64 -46
- transcodely/v1/app_pb2.pyi +36 -2
- transcodely/v1/billing_pb2.py +59 -31
- transcodely/v1/billing_pb2.pyi +148 -2
- transcodely/v1/common_pb2.py +5 -1
- transcodely/v1/common_pb2.pyi +30 -0
- transcodely/v1/content_aware_pb2.py +2 -2
- transcodely/v1/ingest_rule_pb2.py +157 -0
- transcodely/v1/ingest_rule_pb2.pyi +305 -0
- transcodely/v1/job_pb2.py +85 -71
- transcodely/v1/job_pb2.pyi +102 -2
- transcodely/v1/organization_pb2.py +26 -26
- transcodely/v1/organization_pb2.pyi +4 -2
- transcodely/v1/streaming_pb2.py +9 -9
- transcodely/v1/subtitles_pb2.py +15 -15
- transcodely/v1/video_pb2.py +89 -89
- transcodely/v1/video_pb2.pyi +6 -2
- transcodely/v1/webhook_pb2.py +4 -4
- transcodely/version.py +1 -1
- {transcodely-0.3.7.dist-info → transcodely-0.3.9.dist-info}/METADATA +169 -6
- {transcodely-0.3.7.dist-info → transcodely-0.3.9.dist-info}/RECORD +31 -28
- {transcodely-0.3.7.dist-info → transcodely-0.3.9.dist-info}/WHEEL +0 -0
- {transcodely-0.3.7.dist-info → transcodely-0.3.9.dist-info}/licenses/LICENSE +0 -0
transcodely/__init__.py
CHANGED
|
@@ -67,6 +67,7 @@ from .types import (
|
|
|
67
67
|
DRMConfig,
|
|
68
68
|
HDRConfig,
|
|
69
69
|
HLSConfig,
|
|
70
|
+
IngestRule,
|
|
70
71
|
Invoice,
|
|
71
72
|
InvoiceLineItem,
|
|
72
73
|
InvoiceLineType,
|
|
@@ -85,6 +86,9 @@ from .types import (
|
|
|
85
86
|
Preset,
|
|
86
87
|
PresetVariant,
|
|
87
88
|
Resolution,
|
|
89
|
+
StorageEvent,
|
|
90
|
+
StorageEventSource,
|
|
91
|
+
StorageEventStatus,
|
|
88
92
|
StreamingConfig,
|
|
89
93
|
SubtitleResult,
|
|
90
94
|
SubtitleTrack,
|
|
@@ -149,6 +153,7 @@ __all__ = [
|
|
|
149
153
|
"FieldViolation",
|
|
150
154
|
"HDRConfig",
|
|
151
155
|
"HLSConfig",
|
|
156
|
+
"IngestRule",
|
|
152
157
|
"InvalidRequestError",
|
|
153
158
|
"Invoice",
|
|
154
159
|
"InvoiceLineItem",
|
|
@@ -174,6 +179,9 @@ __all__ = [
|
|
|
174
179
|
"PresetVariant",
|
|
175
180
|
"RateLimitError",
|
|
176
181
|
"Resolution",
|
|
182
|
+
"StorageEvent",
|
|
183
|
+
"StorageEventSource",
|
|
184
|
+
"StorageEventStatus",
|
|
177
185
|
"StreamingConfig",
|
|
178
186
|
"SubtitleResult",
|
|
179
187
|
"SubtitleTrack",
|
transcodely/client.py
CHANGED
|
@@ -13,6 +13,7 @@ from .resources.apps import Apps
|
|
|
13
13
|
from .resources.billing import Billing
|
|
14
14
|
from .resources.events import Events
|
|
15
15
|
from .resources.health import Health
|
|
16
|
+
from .resources.ingest_rules import IngestRules
|
|
16
17
|
from .resources.jobs import Jobs
|
|
17
18
|
from .resources.memberships import Memberships
|
|
18
19
|
from .resources.organizations import Organizations
|
|
@@ -63,6 +64,7 @@ class Transcodely:
|
|
|
63
64
|
self._videos: Videos | None = None
|
|
64
65
|
self._presets: Presets | None = None
|
|
65
66
|
self._origins: Origins | None = None
|
|
67
|
+
self._ingest_rules: IngestRules | None = None
|
|
66
68
|
self._apps: Apps | None = None
|
|
67
69
|
self._api_keys: ApiKeys | None = None
|
|
68
70
|
self._organizations: Organizations | None = None
|
|
@@ -111,6 +113,13 @@ class Transcodely:
|
|
|
111
113
|
self._origins = Origins(self._transport)
|
|
112
114
|
return self._origins
|
|
113
115
|
|
|
116
|
+
@property
|
|
117
|
+
def ingest_rules(self) -> IngestRules:
|
|
118
|
+
"""Ingest rules: watch a bucket, transcode what lands in it."""
|
|
119
|
+
if self._ingest_rules is None:
|
|
120
|
+
self._ingest_rules = IngestRules(self._transport)
|
|
121
|
+
return self._ingest_rules
|
|
122
|
+
|
|
114
123
|
@property
|
|
115
124
|
def apps(self) -> Apps:
|
|
116
125
|
if self._apps is None:
|
transcodely/resources/billing.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Billing resource — an organization's invoices,
|
|
1
|
+
"""Billing resource — an organization's invoices, budget and outstanding balance."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -25,10 +25,13 @@ class Billing:
|
|
|
25
25
|
upcoming = client.billing.retrieve_upcoming()
|
|
26
26
|
|
|
27
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.
|
|
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.
|
|
29
31
|
|
|
30
32
|
Monetary amounts are integer minor units (cents) of the invoice currency:
|
|
31
|
-
``total_cents == 1250`` in EUR is 12.50 EUR.
|
|
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.
|
|
32
35
|
"""
|
|
33
36
|
|
|
34
37
|
def __init__(self, transport: Transport) -> None:
|
|
@@ -113,6 +116,131 @@ class Billing:
|
|
|
113
116
|
opts,
|
|
114
117
|
).profile
|
|
115
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
|
+
|
|
116
244
|
def create_portal_session(
|
|
117
245
|
self, opts: CallOptions | None = None
|
|
118
246
|
) -> billing_pb2.BillingPortalSession:
|
transcodely/resources/events.py
CHANGED
|
@@ -52,15 +52,25 @@ class Events:
|
|
|
52
52
|
def list(
|
|
53
53
|
self,
|
|
54
54
|
*,
|
|
55
|
-
app_id: str,
|
|
55
|
+
app_id: str | None = None,
|
|
56
56
|
type: str | None = None,
|
|
57
57
|
created_after: datetime | None = None,
|
|
58
58
|
created_before: datetime | None = None,
|
|
59
59
|
limit: int | None = None,
|
|
60
60
|
opts: CallOptions | None = None,
|
|
61
61
|
) -> Page[Event]:
|
|
62
|
+
"""List events, newest first.
|
|
63
|
+
|
|
64
|
+
``app_id`` is an optional filter. An API-key caller may omit it and get
|
|
65
|
+
their key's own app; passing a different app's ID is refused with
|
|
66
|
+
``permission_denied``. A portal user who omits it gets the events of
|
|
67
|
+
every app in the organization.
|
|
68
|
+
"""
|
|
69
|
+
|
|
62
70
|
def fetch(cursor: str | None) -> PageContents[Event]:
|
|
63
|
-
req = webhook_pb2.ListEventsRequest(
|
|
71
|
+
req = webhook_pb2.ListEventsRequest()
|
|
72
|
+
if app_id is not None:
|
|
73
|
+
req.app_id = app_id
|
|
64
74
|
if type is not None:
|
|
65
75
|
req.type = type
|
|
66
76
|
if created_after is not None:
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Ingest rule resource — watch a bucket, transcode what lands in it.
|
|
2
|
+
|
|
3
|
+
An ingest rule is a standing instruction on one readable storage origin: when
|
|
4
|
+
an object matching its filters appears, create the job its action describes.
|
|
5
|
+
Your storage provider posts object-created events to the rule's
|
|
6
|
+
``endpoint_url``, authenticated with the rule's secret, so no server of yours is
|
|
7
|
+
in the path. Amazon S3 via SNS, Google Cloud Storage via a Pub/Sub push
|
|
8
|
+
subscription, Supabase Storage via a database webhook, and a generic shape for
|
|
9
|
+
anything else are all recognised from the payload.
|
|
10
|
+
|
|
11
|
+
Duplicate deliveries are absorbed: an object is identified by
|
|
12
|
+
(rule, bucket, key, etag) and produces exactly one job.
|
|
13
|
+
|
|
14
|
+
The inbound endpoint itself is not part of this SDK — your storage provider
|
|
15
|
+
calls it, not you.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any, cast
|
|
21
|
+
|
|
22
|
+
from .._transport.transport import CallOptions, Transport
|
|
23
|
+
from ..pagination import Page, PageContents
|
|
24
|
+
from ..v1 import ingest_rule_pb2
|
|
25
|
+
from ._helpers import assign_pagination, fill_from_dict, resolve_enum
|
|
26
|
+
|
|
27
|
+
_SERVICE = "transcodely.v1.IngestRuleService"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class IngestRules:
|
|
31
|
+
def __init__(self, transport: Transport) -> None:
|
|
32
|
+
self._t = transport
|
|
33
|
+
|
|
34
|
+
def create(self, **kwargs: Any) -> ingest_rule_pb2.CreateIngestRuleResponse:
|
|
35
|
+
"""Create a rule on a readable origin.
|
|
36
|
+
|
|
37
|
+
The whole response is returned because ``secret`` rides on it: the
|
|
38
|
+
inbound secret is shown once, here, and cannot be read back. Store it
|
|
39
|
+
wherever the event sender will read it from.
|
|
40
|
+
"""
|
|
41
|
+
req = fill_from_dict(ingest_rule_pb2.CreateIngestRuleRequest(), kwargs)
|
|
42
|
+
return self._t.unary(_SERVICE, "Create", req, ingest_rule_pb2.CreateIngestRuleResponse())
|
|
43
|
+
|
|
44
|
+
def get(self, id: str, opts: CallOptions | None = None) -> ingest_rule_pb2.IngestRule:
|
|
45
|
+
"""Fetch a rule by ID (``ing_*``).
|
|
46
|
+
|
|
47
|
+
The secret is never returned again — the rule carries only
|
|
48
|
+
``secret_prefix`` and ``secret_hint``.
|
|
49
|
+
"""
|
|
50
|
+
req = ingest_rule_pb2.GetIngestRuleRequest(id=id)
|
|
51
|
+
return self._t.unary(
|
|
52
|
+
_SERVICE, "Get", req, ingest_rule_pb2.GetIngestRuleResponse(), opts
|
|
53
|
+
).rule
|
|
54
|
+
|
|
55
|
+
def list(
|
|
56
|
+
self,
|
|
57
|
+
*,
|
|
58
|
+
origin_id: str | None = None,
|
|
59
|
+
enabled: bool | None = None,
|
|
60
|
+
app_id: str | None = None,
|
|
61
|
+
limit: int | None = None,
|
|
62
|
+
opts: CallOptions | None = None,
|
|
63
|
+
) -> Page[ingest_rule_pb2.IngestRule]:
|
|
64
|
+
"""Rules in scope, newest first."""
|
|
65
|
+
|
|
66
|
+
def fetch(cursor: str | None) -> PageContents[ingest_rule_pb2.IngestRule]:
|
|
67
|
+
req = ingest_rule_pb2.ListIngestRulesRequest()
|
|
68
|
+
if origin_id is not None:
|
|
69
|
+
req.origin_id = origin_id
|
|
70
|
+
if enabled is not None:
|
|
71
|
+
req.enabled = enabled
|
|
72
|
+
if app_id is not None:
|
|
73
|
+
req.app_id = app_id
|
|
74
|
+
assign_pagination(req.pagination, limit=limit, cursor=cursor)
|
|
75
|
+
res = self._t.unary(
|
|
76
|
+
_SERVICE, "List", req, ingest_rule_pb2.ListIngestRulesResponse(), opts
|
|
77
|
+
)
|
|
78
|
+
return PageContents(
|
|
79
|
+
items=list(res.rules), next_cursor=res.pagination.next_cursor or None
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return Page(fetch)
|
|
83
|
+
|
|
84
|
+
def update(self, **kwargs: Any) -> ingest_rule_pb2.UpdateIngestRuleResponse:
|
|
85
|
+
"""Update name, enabled state, filters or action, optionally rotating the secret.
|
|
86
|
+
|
|
87
|
+
The update MERGES: it applies only what it carries, down to the
|
|
88
|
+
individual filters and the individual parts of the action. Narrowing a
|
|
89
|
+
rule to a new prefix is ``filters={"prefix": "raw/"}`` and nothing else
|
|
90
|
+
— the suffix, content-type and size filters are untouched.
|
|
91
|
+
|
|
92
|
+
Removing something rather than changing it takes the two clear flags.
|
|
93
|
+
``clear_filters=True`` empties the filter set before ``filters`` is
|
|
94
|
+
applied, so on its own it widens the rule to everything in the bucket.
|
|
95
|
+
``clear_action=True`` replaces the action outright, and ``action`` must
|
|
96
|
+
then be complete — at least one output and exactly one destination.
|
|
97
|
+
That is the only way to drop an action's thumbnails or metadata, since
|
|
98
|
+
a repeated or map field sent empty reads as "not sent". For the same
|
|
99
|
+
reason ``managed=False`` does not turn managed storage off; it leaves
|
|
100
|
+
the destination alone, so send ``output_origin_id`` instead.
|
|
101
|
+
|
|
102
|
+
The whole response is returned: a rotation puts the new secret on it
|
|
103
|
+
(once, and the previous secret keeps working for 24 hours), and
|
|
104
|
+
switching a paused rule back on reports how many deliveries it declined
|
|
105
|
+
while off in ``events_skipped_while_disabled``.
|
|
106
|
+
"""
|
|
107
|
+
req = fill_from_dict(ingest_rule_pb2.UpdateIngestRuleRequest(), kwargs)
|
|
108
|
+
return self._t.unary(_SERVICE, "Update", req, ingest_rule_pb2.UpdateIngestRuleResponse())
|
|
109
|
+
|
|
110
|
+
def delete(self, id: str, opts: CallOptions | None = None) -> ingest_rule_pb2.IngestRule:
|
|
111
|
+
"""Delete a rule; its endpoint stops accepting events immediately.
|
|
112
|
+
|
|
113
|
+
The events it already received are kept, and the rule as it stood at
|
|
114
|
+
deletion is returned.
|
|
115
|
+
"""
|
|
116
|
+
req = ingest_rule_pb2.DeleteIngestRuleRequest(id=id)
|
|
117
|
+
return self._t.unary(
|
|
118
|
+
_SERVICE, "Delete", req, ingest_rule_pb2.DeleteIngestRuleResponse(), opts
|
|
119
|
+
).rule
|
|
120
|
+
|
|
121
|
+
def list_events(
|
|
122
|
+
self,
|
|
123
|
+
*,
|
|
124
|
+
rule_id: str | None = None,
|
|
125
|
+
status: int | str | None = None,
|
|
126
|
+
app_id: str | None = None,
|
|
127
|
+
limit: int | None = None,
|
|
128
|
+
opts: CallOptions | None = None,
|
|
129
|
+
) -> Page[ingest_rule_pb2.StorageEvent]:
|
|
130
|
+
"""Every delivery received and what came of it, newest first.
|
|
131
|
+
|
|
132
|
+
``status`` accepts the simplified wire string (``"skipped"``) as well as
|
|
133
|
+
the generated constant.
|
|
134
|
+
"""
|
|
135
|
+
resolved_status = (
|
|
136
|
+
None
|
|
137
|
+
if status is None
|
|
138
|
+
else resolve_enum(status, ingest_rule_pb2.StorageEventStatus.DESCRIPTOR)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def fetch(cursor: str | None) -> PageContents[ingest_rule_pb2.StorageEvent]:
|
|
142
|
+
req = ingest_rule_pb2.ListIngestEventsRequest()
|
|
143
|
+
if rule_id is not None:
|
|
144
|
+
req.rule_id = rule_id
|
|
145
|
+
if resolved_status is not None:
|
|
146
|
+
# resolve_enum returns a plain int; the field is typed as the enum.
|
|
147
|
+
req.status = cast("ingest_rule_pb2.StorageEventStatus", resolved_status)
|
|
148
|
+
if app_id is not None:
|
|
149
|
+
req.app_id = app_id
|
|
150
|
+
assign_pagination(req.pagination, limit=limit, cursor=cursor)
|
|
151
|
+
res = self._t.unary(
|
|
152
|
+
_SERVICE, "ListEvents", req, ingest_rule_pb2.ListIngestEventsResponse(), opts
|
|
153
|
+
)
|
|
154
|
+
return PageContents(
|
|
155
|
+
items=list(res.events), next_cursor=res.pagination.next_cursor or None
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return Page(fetch)
|
|
159
|
+
|
|
160
|
+
def test(self, **kwargs: Any) -> ingest_rule_pb2.TestIngestRuleResponse:
|
|
161
|
+
"""Dry-run an object key against a rule. Nothing is stored, no job is created.
|
|
162
|
+
|
|
163
|
+
Reports whether the filters match and, when they do, the exact job
|
|
164
|
+
request the rule would submit. Supply ``etag`` when you want the
|
|
165
|
+
preview to name the idempotency key a real delivery would carry — the
|
|
166
|
+
key is derived from it.
|
|
167
|
+
"""
|
|
168
|
+
req = fill_from_dict(ingest_rule_pb2.TestIngestRuleRequest(), kwargs)
|
|
169
|
+
return self._t.unary(_SERVICE, "Test", req, ingest_rule_pb2.TestIngestRuleResponse())
|
|
170
|
+
|
|
171
|
+
def replay_event(
|
|
172
|
+
self, event_id: str, opts: CallOptions | None = None
|
|
173
|
+
) -> ingest_rule_pb2.StorageEvent:
|
|
174
|
+
"""Re-queue a skipped or refused event (``sev_*``).
|
|
175
|
+
|
|
176
|
+
Deduplication is permanent: re-sending the event, or re-uploading the
|
|
177
|
+
same bytes, is absorbed and produces nothing. The event is reset rather
|
|
178
|
+
than duplicated, so it keeps its id and its history.
|
|
179
|
+
"""
|
|
180
|
+
req = ingest_rule_pb2.ReplayIngestEventRequest(event_id=event_id)
|
|
181
|
+
return self._t.unary(
|
|
182
|
+
_SERVICE, "ReplayEvent", req, ingest_rule_pb2.ReplayIngestEventResponse(), opts
|
|
183
|
+
).event
|
|
@@ -126,12 +126,22 @@ class WebhookEndpoints:
|
|
|
126
126
|
def list(
|
|
127
127
|
self,
|
|
128
128
|
*,
|
|
129
|
-
app_id: str,
|
|
129
|
+
app_id: str | None = None,
|
|
130
130
|
limit: int | None = None,
|
|
131
131
|
opts: CallOptions | None = None,
|
|
132
132
|
) -> Page[webhook_pb2.WebhookEndpoint]:
|
|
133
|
+
"""List webhook endpoints, newest first.
|
|
134
|
+
|
|
135
|
+
``app_id`` is an optional filter. An API-key caller may omit it and get
|
|
136
|
+
their key's own app; passing a different app's ID is refused with
|
|
137
|
+
``permission_denied``. A portal user who omits it gets the endpoints of
|
|
138
|
+
every app in the organization.
|
|
139
|
+
"""
|
|
140
|
+
|
|
133
141
|
def fetch(cursor: str | None) -> PageContents[webhook_pb2.WebhookEndpoint]:
|
|
134
|
-
req = webhook_pb2.ListWebhookEndpointsRequest(
|
|
142
|
+
req = webhook_pb2.ListWebhookEndpointsRequest()
|
|
143
|
+
if app_id is not None:
|
|
144
|
+
req.app_id = app_id
|
|
135
145
|
pagination = common_pb2.PaginationRequest()
|
|
136
146
|
if limit is not None:
|
|
137
147
|
pagination.limit = limit
|