qurl-python 0.2.1__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.
- layerv_qurl/__init__.py +161 -0
- layerv_qurl/_utils.py +1414 -0
- layerv_qurl/async_client.py +1512 -0
- layerv_qurl/client.py +1533 -0
- layerv_qurl/errors.py +152 -0
- layerv_qurl/langchain.py +159 -0
- layerv_qurl/py.typed +0 -0
- layerv_qurl/types.py +690 -0
- qurl_python-0.2.1.dist-info/METADATA +282 -0
- qurl_python-0.2.1.dist-info/RECORD +12 -0
- qurl_python-0.2.1.dist-info/WHEEL +4 -0
- qurl_python-0.2.1.dist-info/licenses/LICENSE +21 -0
layerv_qurl/types.py
ADDED
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
"""Type definitions for the qURL API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any, Literal, TypedDict
|
|
8
|
+
|
|
9
|
+
#: Valid resource-level status values. Resources only have two states per
|
|
10
|
+
#: the OpenAPI spec (``QurlData.status``) and the server code. Accepts known
|
|
11
|
+
#: values for IDE autocomplete, plus ``str`` for forward compatibility.
|
|
12
|
+
QURLStatus = Literal["active", "revoked"] | str
|
|
13
|
+
|
|
14
|
+
#: Valid per-token status values. Wider than :data:`QURLStatus` because
|
|
15
|
+
#: individual access tokens can additionally be ``consumed`` (one-time use)
|
|
16
|
+
#: or ``expired``, per the OpenAPI spec (``QurlSummary.status``).
|
|
17
|
+
TokenStatus = Literal["active", "consumed", "expired", "revoked"] | str
|
|
18
|
+
|
|
19
|
+
#: Valid subscription plan values. Matches the ``QuotaData.plan`` enum in
|
|
20
|
+
#: the OpenAPI spec (``[free, growth, enterprise]``). Accepts arbitrary
|
|
21
|
+
#: strings so the API can add new plans without a breaking SDK change.
|
|
22
|
+
QuotaPlan = Literal["free", "growth", "enterprise"] | str
|
|
23
|
+
|
|
24
|
+
#: Valid resource type values. Public callers primarily use ``url`` and
|
|
25
|
+
#: ``tunnel``; the API may also surface integration-owned ``transit`` rows.
|
|
26
|
+
ResourceType = Literal["url", "tunnel", "transit"] | str
|
|
27
|
+
|
|
28
|
+
WebhookEventType = (
|
|
29
|
+
Literal[
|
|
30
|
+
"qurl.created",
|
|
31
|
+
"qurl.expired",
|
|
32
|
+
"qurl.revoked",
|
|
33
|
+
"qurl.updated",
|
|
34
|
+
"resource.closed",
|
|
35
|
+
"qurl.accessed",
|
|
36
|
+
"qurl.access_denied",
|
|
37
|
+
"qurl.token_exhausted",
|
|
38
|
+
"quota.warning",
|
|
39
|
+
"quota.exceeded",
|
|
40
|
+
"token.minted",
|
|
41
|
+
"token.expired",
|
|
42
|
+
"domain.verified",
|
|
43
|
+
"domain.failed",
|
|
44
|
+
"domain.deleted",
|
|
45
|
+
]
|
|
46
|
+
| str
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse_dt(s: str | None) -> datetime | None:
|
|
51
|
+
"""Parse an ISO 8601 datetime string, handling Z suffix for Python 3.10 compat."""
|
|
52
|
+
if s is None:
|
|
53
|
+
return None
|
|
54
|
+
if s.endswith("Z"):
|
|
55
|
+
s = s[:-1] + "+00:00"
|
|
56
|
+
return datetime.fromisoformat(s)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class AIAgentPolicy:
|
|
61
|
+
"""Structured policy for controlling AI agent access."""
|
|
62
|
+
|
|
63
|
+
block_all: bool | None = None
|
|
64
|
+
deny_categories: list[str] | None = None
|
|
65
|
+
allow_categories: list[str] | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class AccessPolicy:
|
|
70
|
+
"""Access control policy for a qURL."""
|
|
71
|
+
|
|
72
|
+
ip_allowlist: list[str] | None = None
|
|
73
|
+
ip_denylist: list[str] | None = None
|
|
74
|
+
geo_allowlist: list[str] | None = None
|
|
75
|
+
geo_denylist: list[str] | None = None
|
|
76
|
+
user_agent_allow_regex: str | None = None
|
|
77
|
+
user_agent_deny_regex: str | None = None
|
|
78
|
+
ai_agent_policy: AIAgentPolicy | None = None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class AccessToken:
|
|
83
|
+
"""An individual access token within a qURL.
|
|
84
|
+
|
|
85
|
+
``status`` uses the wider :data:`TokenStatus` alias — tokens can be
|
|
86
|
+
``active``/``consumed``/``expired``/``revoked`` (per ``QurlSummary.status``
|
|
87
|
+
in the spec), while resources are only ``active``/``revoked``.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
qurl_id: str
|
|
91
|
+
status: TokenStatus
|
|
92
|
+
one_time_use: bool = False
|
|
93
|
+
max_sessions: int = 0
|
|
94
|
+
session_duration: int = 0
|
|
95
|
+
use_count: int = 0
|
|
96
|
+
label: str | None = None
|
|
97
|
+
qurl_site: str | None = None
|
|
98
|
+
access_policy: AccessPolicy | None = None
|
|
99
|
+
created_at: datetime | None = None
|
|
100
|
+
expires_at: datetime | None = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class QURL:
|
|
105
|
+
"""A qURL resource as returned by the API."""
|
|
106
|
+
|
|
107
|
+
resource_id: str
|
|
108
|
+
target_url: str | None
|
|
109
|
+
status: QURLStatus
|
|
110
|
+
created_at: datetime | None = None
|
|
111
|
+
expires_at: datetime | None = None
|
|
112
|
+
description: str | None = None
|
|
113
|
+
tags: list[str] = field(default_factory=list)
|
|
114
|
+
qurl_site: str | None = None
|
|
115
|
+
custom_domain: str | None = None
|
|
116
|
+
slug: str | None = None
|
|
117
|
+
qurl_count: int | None = None
|
|
118
|
+
access_tokens: list[AccessToken] | None = None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class CreateOutput:
|
|
123
|
+
"""Response from creating a qURL.
|
|
124
|
+
|
|
125
|
+
``resource_id`` identifies the resource container (grouped by target URL).
|
|
126
|
+
``qurl_id`` identifies the specific access token created (``q_`` prefix).
|
|
127
|
+
Multiple qURLs for the same target URL share one ``resource_id``.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
resource_id: str
|
|
131
|
+
qurl_link: str
|
|
132
|
+
qurl_site: str
|
|
133
|
+
expires_at: datetime | None = None
|
|
134
|
+
qurl_id: str | None = None
|
|
135
|
+
label: str | None = None
|
|
136
|
+
branded_domain: str | None = None
|
|
137
|
+
resource_type: ResourceType | None = None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass
|
|
141
|
+
class MintOutput:
|
|
142
|
+
"""Response from minting an access link."""
|
|
143
|
+
|
|
144
|
+
qurl_link: str
|
|
145
|
+
qurl_id: str | None = None
|
|
146
|
+
expires_at: datetime | None = None
|
|
147
|
+
branded_domain: str | None = None
|
|
148
|
+
resource_type: ResourceType | None = None
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@dataclass
|
|
152
|
+
class AccessGrant:
|
|
153
|
+
"""Details of the network access that was granted."""
|
|
154
|
+
|
|
155
|
+
expires_in: int
|
|
156
|
+
granted_at: datetime | None = None
|
|
157
|
+
src_ip: str = ""
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class ResolveOutput:
|
|
162
|
+
"""Response from headless resolution."""
|
|
163
|
+
|
|
164
|
+
target_url: str | None
|
|
165
|
+
resource_id: str
|
|
166
|
+
access_grant: AccessGrant | None = None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@dataclass
|
|
170
|
+
class ListOutput:
|
|
171
|
+
"""Response from listing qURLs."""
|
|
172
|
+
|
|
173
|
+
qurls: list[QURL] = field(default_factory=list)
|
|
174
|
+
next_cursor: str | None = None
|
|
175
|
+
has_more: bool = False
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@dataclass
|
|
179
|
+
class RateLimits:
|
|
180
|
+
"""Rate limit configuration."""
|
|
181
|
+
|
|
182
|
+
create_per_minute: int = 0
|
|
183
|
+
create_per_hour: int = 0
|
|
184
|
+
list_per_minute: int = 0
|
|
185
|
+
resolve_per_minute: int = 0
|
|
186
|
+
max_active_qurls: int = 0
|
|
187
|
+
max_tokens_per_qurl: int = 0
|
|
188
|
+
max_expiry_seconds: int = 0
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@dataclass
|
|
192
|
+
class Usage:
|
|
193
|
+
"""Usage statistics."""
|
|
194
|
+
|
|
195
|
+
qurls_created: int = 0
|
|
196
|
+
active_qurls: int = 0
|
|
197
|
+
# Changed from float=0.0 — callers must None-check before arithmetic.
|
|
198
|
+
active_qurls_percent: float | None = None
|
|
199
|
+
total_accesses: int = 0
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass
|
|
203
|
+
class Quota:
|
|
204
|
+
"""Quota and usage information.
|
|
205
|
+
|
|
206
|
+
``plan`` is typed via :data:`QuotaPlan` (Literal enum + ``str``
|
|
207
|
+
escape hatch for forward compat). Defaults to the sentinel
|
|
208
|
+
``"unknown"`` — only hit by tests/bootstrap paths, since the
|
|
209
|
+
``/v1/quota`` endpoint always returns a populated plan string.
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
plan: QuotaPlan = "unknown"
|
|
213
|
+
period_start: datetime | None = None
|
|
214
|
+
period_end: datetime | None = None
|
|
215
|
+
rate_limits: RateLimits | None = None
|
|
216
|
+
usage: Usage | None = None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass
|
|
220
|
+
class BatchItemError:
|
|
221
|
+
"""Error details for a failed batch item."""
|
|
222
|
+
|
|
223
|
+
code: str = ""
|
|
224
|
+
message: str = ""
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@dataclass
|
|
228
|
+
class BatchItemResult:
|
|
229
|
+
"""Result for a single item in a batch create."""
|
|
230
|
+
|
|
231
|
+
index: int = 0
|
|
232
|
+
success: bool = False
|
|
233
|
+
resource_id: str | None = None
|
|
234
|
+
qurl_link: str | None = None
|
|
235
|
+
qurl_site: str | None = None
|
|
236
|
+
branded_domain: str | None = None
|
|
237
|
+
expires_at: datetime | None = None
|
|
238
|
+
error: BatchItemError | None = None
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@dataclass
|
|
242
|
+
class BatchCreateOutput:
|
|
243
|
+
"""Response from batch creating qURLs."""
|
|
244
|
+
|
|
245
|
+
succeeded: int = 0
|
|
246
|
+
failed: int = 0
|
|
247
|
+
results: list[BatchItemResult] = field(default_factory=list)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@dataclass
|
|
251
|
+
class Resource:
|
|
252
|
+
"""Resource container returned by the resource-management API."""
|
|
253
|
+
|
|
254
|
+
resource_id: str
|
|
255
|
+
status: QURLStatus
|
|
256
|
+
resource_type: ResourceType | None = None
|
|
257
|
+
target_url: str | None = None
|
|
258
|
+
knock_resource_id: str | None = None
|
|
259
|
+
description: str | None = None
|
|
260
|
+
tags: list[str] = field(default_factory=list)
|
|
261
|
+
custom_domain: str | None = None
|
|
262
|
+
alias: str | None = None
|
|
263
|
+
slug: str | None = None
|
|
264
|
+
preserve_host: bool = False
|
|
265
|
+
session_duration_cap: int | None = None
|
|
266
|
+
qurl_count: int | None = None
|
|
267
|
+
created_at: datetime | None = None
|
|
268
|
+
expires_at: datetime | None = None
|
|
269
|
+
tombstoned_at: datetime | None = None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@dataclass
|
|
273
|
+
class ResourceDetail:
|
|
274
|
+
"""Detailed resource response with a bounded qURL token preview."""
|
|
275
|
+
|
|
276
|
+
resource: Resource
|
|
277
|
+
qurls: list[AccessToken] = field(default_factory=list)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@dataclass
|
|
281
|
+
class ResourceListOutput:
|
|
282
|
+
"""Response from listing resources."""
|
|
283
|
+
|
|
284
|
+
resources: list[Resource] = field(default_factory=list)
|
|
285
|
+
next_cursor: str | None = None
|
|
286
|
+
has_more: bool = False
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@dataclass
|
|
290
|
+
class Session:
|
|
291
|
+
"""Active access session for a resource."""
|
|
292
|
+
|
|
293
|
+
session_id: str
|
|
294
|
+
qurl_id: str | None = None
|
|
295
|
+
src_ip: str | None = None
|
|
296
|
+
user_agent: str | None = None
|
|
297
|
+
created_at: datetime | None = None
|
|
298
|
+
last_seen_at: datetime | None = None
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@dataclass
|
|
302
|
+
class SessionListOutput:
|
|
303
|
+
"""Response from listing active sessions."""
|
|
304
|
+
|
|
305
|
+
sessions: list[Session] = field(default_factory=list)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@dataclass
|
|
309
|
+
class SessionTerminateOutput:
|
|
310
|
+
"""Response from terminating all active sessions on a resource."""
|
|
311
|
+
|
|
312
|
+
terminated: int = 0
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@dataclass
|
|
316
|
+
class DNSRecord:
|
|
317
|
+
"""DNS record required for custom-domain setup."""
|
|
318
|
+
|
|
319
|
+
type: str = ""
|
|
320
|
+
name: str = ""
|
|
321
|
+
value: str = ""
|
|
322
|
+
verified: bool = False
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@dataclass
|
|
326
|
+
class Domain:
|
|
327
|
+
"""Custom-domain registration state."""
|
|
328
|
+
|
|
329
|
+
domain: str
|
|
330
|
+
status: str
|
|
331
|
+
# Verification tokens are public DNS values, so repr keeps them visible.
|
|
332
|
+
verification_token: str | None = None
|
|
333
|
+
token_expires_at: datetime | None = None
|
|
334
|
+
acme_cname_target: str | None = None
|
|
335
|
+
created_at: datetime | None = None
|
|
336
|
+
verified_at: datetime | None = None
|
|
337
|
+
activated_at: datetime | None = None
|
|
338
|
+
ready_for_qurls: bool = False
|
|
339
|
+
dns_records: list[DNSRecord] = field(default_factory=list)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@dataclass
|
|
343
|
+
class DomainListOutput:
|
|
344
|
+
"""Response from listing custom domains."""
|
|
345
|
+
|
|
346
|
+
domains: list[Domain] = field(default_factory=list)
|
|
347
|
+
next_cursor: str | None = None
|
|
348
|
+
has_more: bool = False
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@dataclass
|
|
352
|
+
class CheckDetail:
|
|
353
|
+
"""DNS verification check detail."""
|
|
354
|
+
|
|
355
|
+
verified: bool
|
|
356
|
+
error: str | None = None
|
|
357
|
+
found: str | None = None
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@dataclass
|
|
361
|
+
class DomainVerifyOutput:
|
|
362
|
+
"""Response from triggering custom-domain verification."""
|
|
363
|
+
|
|
364
|
+
domain: str
|
|
365
|
+
status: str
|
|
366
|
+
checks: dict[str, CheckDetail] = field(default_factory=dict)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@dataclass
|
|
370
|
+
class Webhook:
|
|
371
|
+
"""Webhook subscription."""
|
|
372
|
+
|
|
373
|
+
webhook_id: str
|
|
374
|
+
url: str
|
|
375
|
+
events: list[WebhookEventType]
|
|
376
|
+
owner_id: str | None = None
|
|
377
|
+
status: str | None = None
|
|
378
|
+
description: str | None = None
|
|
379
|
+
created_at: datetime | None = None
|
|
380
|
+
updated_at: datetime | None = None
|
|
381
|
+
failure_count: int = 0
|
|
382
|
+
last_delivery_success: bool | None = None
|
|
383
|
+
last_delivery_time: int | None = None
|
|
384
|
+
secret: str | None = field(default=None, repr=False)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@dataclass
|
|
388
|
+
class WebhookListOutput:
|
|
389
|
+
"""Response from listing webhooks."""
|
|
390
|
+
|
|
391
|
+
webhooks: list[Webhook] = field(default_factory=list)
|
|
392
|
+
next_cursor: str | None = None
|
|
393
|
+
has_more: bool = False
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
@dataclass
|
|
397
|
+
class WebhookDelivery:
|
|
398
|
+
"""Webhook delivery attempt."""
|
|
399
|
+
|
|
400
|
+
delivery_id: str
|
|
401
|
+
webhook_id: str | None = None
|
|
402
|
+
event_type: WebhookEventType | None = None
|
|
403
|
+
status: str | None = None
|
|
404
|
+
response_code: int | None = None
|
|
405
|
+
response_body: str | None = None
|
|
406
|
+
error_message: str | None = None
|
|
407
|
+
duration_ms: int | None = None
|
|
408
|
+
retry_count: int = 0
|
|
409
|
+
created_at: datetime | None = None
|
|
410
|
+
completed_at: datetime | None = None
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@dataclass
|
|
414
|
+
class WebhookDeliveryListOutput:
|
|
415
|
+
"""Response from listing webhook deliveries."""
|
|
416
|
+
|
|
417
|
+
deliveries: list[WebhookDelivery] = field(default_factory=list)
|
|
418
|
+
next_cursor: str | None = None
|
|
419
|
+
has_more: bool = False
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@dataclass
|
|
423
|
+
class WebhookEventTypeInfo:
|
|
424
|
+
"""Webhook event catalog entry."""
|
|
425
|
+
|
|
426
|
+
type: WebhookEventType
|
|
427
|
+
category: str | None = None
|
|
428
|
+
description: str | None = None
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@dataclass
|
|
432
|
+
class WebhookEventTypesOutput:
|
|
433
|
+
"""Response from listing supported webhook event types."""
|
|
434
|
+
|
|
435
|
+
events: list[WebhookEventTypeInfo] = field(default_factory=list)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@dataclass
|
|
439
|
+
class APIKey:
|
|
440
|
+
"""API key metadata.
|
|
441
|
+
|
|
442
|
+
``api_key`` is populated only on create responses.
|
|
443
|
+
"""
|
|
444
|
+
|
|
445
|
+
key_id: str
|
|
446
|
+
key_prefix: str
|
|
447
|
+
name: str
|
|
448
|
+
scopes: list[str] = field(default_factory=list)
|
|
449
|
+
status: str | None = None
|
|
450
|
+
created_at: datetime | None = None
|
|
451
|
+
updated_at: datetime | None = None
|
|
452
|
+
last_used_at: datetime | None = None
|
|
453
|
+
expires_at: datetime | None = None
|
|
454
|
+
purpose: str | None = None
|
|
455
|
+
tunnel_slug: str | None = None
|
|
456
|
+
api_key: str | None = field(default=None, repr=False)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
@dataclass
|
|
460
|
+
class APIKeyListOutput:
|
|
461
|
+
"""Response from listing API keys."""
|
|
462
|
+
|
|
463
|
+
api_keys: list[APIKey] = field(default_factory=list)
|
|
464
|
+
next_cursor: str | None = None
|
|
465
|
+
has_more: bool = False
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@dataclass
|
|
469
|
+
class RedeemAccessCodeOutput:
|
|
470
|
+
"""Response from redeeming a public access code."""
|
|
471
|
+
|
|
472
|
+
redirect_url: str
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@dataclass
|
|
476
|
+
class AccessCode:
|
|
477
|
+
"""Access-code metadata.
|
|
478
|
+
|
|
479
|
+
``code`` is populated only on create responses.
|
|
480
|
+
"""
|
|
481
|
+
|
|
482
|
+
access_code_id: str
|
|
483
|
+
resource_id: str
|
|
484
|
+
name: str | None = None
|
|
485
|
+
status: str | None = None
|
|
486
|
+
max_uses: int = 0
|
|
487
|
+
use_count: int = 0
|
|
488
|
+
created_at: datetime | None = None
|
|
489
|
+
expires_at: datetime | None = None
|
|
490
|
+
code: str | None = field(default=None, repr=False)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@dataclass
|
|
494
|
+
class AccessCodeListOutput:
|
|
495
|
+
"""Response from listing access codes."""
|
|
496
|
+
|
|
497
|
+
access_codes: list[AccessCode] = field(default_factory=list)
|
|
498
|
+
next_cursor: str | None = None
|
|
499
|
+
has_more: bool = False
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
@dataclass
|
|
503
|
+
class UsageCostEstimate:
|
|
504
|
+
"""Estimated usage cost for the current billing period."""
|
|
505
|
+
|
|
506
|
+
currency: str
|
|
507
|
+
amount_cents: int
|
|
508
|
+
description: str
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
@dataclass
|
|
512
|
+
class CurrentPeriodUsage:
|
|
513
|
+
"""Current billing-period usage summary."""
|
|
514
|
+
|
|
515
|
+
tier: QuotaPlan
|
|
516
|
+
period_start: datetime | None
|
|
517
|
+
period_end: datetime | None
|
|
518
|
+
qurls_created: int
|
|
519
|
+
active_qurls: int
|
|
520
|
+
cost_estimate: UsageCostEstimate | None = None
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
@dataclass
|
|
524
|
+
class UsageDailyEntry:
|
|
525
|
+
"""Daily qURL creation count."""
|
|
526
|
+
|
|
527
|
+
date: str
|
|
528
|
+
qurls_created: int
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
@dataclass
|
|
532
|
+
class DailyUsage:
|
|
533
|
+
"""Daily usage breakdown for the current billing period."""
|
|
534
|
+
|
|
535
|
+
tier: QuotaPlan
|
|
536
|
+
period_start: datetime | None
|
|
537
|
+
period_end: datetime | None
|
|
538
|
+
daily: list[UsageDailyEntry] = field(default_factory=list)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
@dataclass
|
|
542
|
+
class Customer:
|
|
543
|
+
"""Customer profile and billing settings."""
|
|
544
|
+
|
|
545
|
+
tier: QuotaPlan
|
|
546
|
+
spending_cap_cents: int
|
|
547
|
+
current_period_usage_count: int
|
|
548
|
+
frozen: bool
|
|
549
|
+
frozen_reason: str | None = None
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
@dataclass
|
|
553
|
+
class CheckoutSession:
|
|
554
|
+
"""Stripe checkout session URL."""
|
|
555
|
+
|
|
556
|
+
url: str
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
@dataclass
|
|
560
|
+
class PortalSession:
|
|
561
|
+
"""Stripe billing portal session URL."""
|
|
562
|
+
|
|
563
|
+
url: str
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
@dataclass
|
|
567
|
+
class Invoice:
|
|
568
|
+
"""Billing invoice summary."""
|
|
569
|
+
|
|
570
|
+
id: str
|
|
571
|
+
amount_cents: int
|
|
572
|
+
status: str
|
|
573
|
+
created_at: datetime | None = None
|
|
574
|
+
pdf_url: str | None = None
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
@dataclass
|
|
578
|
+
class InvoiceListOutput:
|
|
579
|
+
"""Response from listing billing invoices."""
|
|
580
|
+
|
|
581
|
+
invoices: list[Invoice] = field(default_factory=list)
|
|
582
|
+
next_cursor: str | None = None
|
|
583
|
+
has_more: bool = False
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
@dataclass
|
|
587
|
+
class ConnectorInstallationStats:
|
|
588
|
+
"""Connector installation activity counters."""
|
|
589
|
+
|
|
590
|
+
resources: int = 0
|
|
591
|
+
qurls: int = 0
|
|
592
|
+
accesses_24h: int = 0
|
|
593
|
+
accesses_7d: int = 0
|
|
594
|
+
errors_24h: int = 0
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
@dataclass
|
|
598
|
+
class ConnectorInstallationCapabilities:
|
|
599
|
+
"""Connector installation capability flags."""
|
|
600
|
+
|
|
601
|
+
configure: bool = False
|
|
602
|
+
disconnect: bool = False
|
|
603
|
+
reauth: bool = False
|
|
604
|
+
view_activity: bool = False
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
@dataclass
|
|
608
|
+
class ConnectorInstallation:
|
|
609
|
+
"""Normalized connector installation summary."""
|
|
610
|
+
|
|
611
|
+
installation_id: str
|
|
612
|
+
plugin_id: str
|
|
613
|
+
label: str
|
|
614
|
+
subject_kind: str
|
|
615
|
+
subject_display_name: str
|
|
616
|
+
status: str
|
|
617
|
+
installed_at: datetime | None = None
|
|
618
|
+
last_activity_at: datetime | None = None
|
|
619
|
+
stats: ConnectorInstallationStats | None = None
|
|
620
|
+
capabilities: ConnectorInstallationCapabilities | None = None
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
@dataclass
|
|
624
|
+
class ConnectorInstallationListOutput:
|
|
625
|
+
"""Response from listing connector installations."""
|
|
626
|
+
|
|
627
|
+
installations: list[ConnectorInstallation] = field(default_factory=list)
|
|
628
|
+
next_cursor: str | None = None
|
|
629
|
+
has_more: bool = False
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
@dataclass
|
|
633
|
+
class NHPServerPeerInfo:
|
|
634
|
+
"""NHP server peer info returned to connector agents."""
|
|
635
|
+
|
|
636
|
+
public_key_b64: str
|
|
637
|
+
host: str
|
|
638
|
+
port: int
|
|
639
|
+
expire_time: int
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
@dataclass
|
|
643
|
+
class AgentBootstrapOutput:
|
|
644
|
+
"""Response from connector agent bootstrap."""
|
|
645
|
+
|
|
646
|
+
agent_id: str
|
|
647
|
+
registered_at: datetime | None
|
|
648
|
+
nhp_server_peer: NHPServerPeerInfo
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
# ---- batch_create input shape -------------------------------------------
|
|
652
|
+
# TypedDicts for :meth:`QURLClient.batch_create` items. Split into a
|
|
653
|
+
# required-fields base class and an `total=False` subclass so callers on
|
|
654
|
+
# Python 3.10 don't need ``typing.Required`` (added in 3.11) or a
|
|
655
|
+
# ``typing_extensions`` dependency. Fields mirror the corresponding
|
|
656
|
+
# keyword arguments on :meth:`QURLClient.create` one-for-one; the
|
|
657
|
+
# single-create endpoint and the batch endpoint share the same
|
|
658
|
+
# ``CreateQurlRequest`` schema in the OpenAPI spec.
|
|
659
|
+
#
|
|
660
|
+
# The runtime behavior is unchanged — ``batch_create`` still iterates
|
|
661
|
+
# items as plain dicts and passes them through ``build_body`` /
|
|
662
|
+
# ``_serialize_value``. The TypedDict is purely for IDE autocomplete and
|
|
663
|
+
# static type checking.
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
class _BatchCreateItemRequired(TypedDict):
|
|
667
|
+
target_url: str
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
class BatchCreateItem(_BatchCreateItemRequired, total=False):
|
|
671
|
+
"""Input shape for a single item in :meth:`QURLClient.batch_create`.
|
|
672
|
+
|
|
673
|
+
``target_url`` is required; every other field is optional and mirrors
|
|
674
|
+
the corresponding keyword argument on :meth:`QURLClient.create`.
|
|
675
|
+
|
|
676
|
+
``access_policy`` accepts either an :class:`AccessPolicy` dataclass
|
|
677
|
+
(recommended for type safety and IDE autocomplete) or a plain
|
|
678
|
+
``dict[str, Any]`` (for callers who prefer dicts or are working from
|
|
679
|
+
dynamic config). Both forms are converted to the same JSON body at
|
|
680
|
+
request time via ``_serialize_value``.
|
|
681
|
+
"""
|
|
682
|
+
|
|
683
|
+
type: str
|
|
684
|
+
expires_in: str
|
|
685
|
+
label: str
|
|
686
|
+
one_time_use: bool
|
|
687
|
+
max_sessions: int
|
|
688
|
+
session_duration: str
|
|
689
|
+
access_policy: AccessPolicy | dict[str, Any]
|
|
690
|
+
custom_domain: str
|