qurl-python 0.2.2__py3-none-any.whl → 0.4.0__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 CHANGED
@@ -31,7 +31,6 @@ from layerv_qurl.types import (
31
31
  AccessGrant,
32
32
  AccessPolicy,
33
33
  AccessToken,
34
- AgentBootstrapOutput,
35
34
  AIAgentPolicy,
36
35
  APIKey,
37
36
  APIKeyListOutput,
@@ -57,7 +56,6 @@ from layerv_qurl.types import (
57
56
  InvoiceListOutput,
58
57
  ListOutput,
59
58
  MintOutput,
60
- NHPServerPeerInfo,
61
59
  Portal,
62
60
  PortalSession,
63
61
  Quota,
@@ -108,7 +106,6 @@ __all__ = [
108
106
  "AccessCode",
109
107
  "AccessCodeListOutput",
110
108
  "AIAgentPolicy",
111
- "AgentBootstrapOutput",
112
109
  "APIKey",
113
110
  "APIKeyListOutput",
114
111
  "BatchCreateItem",
@@ -130,7 +127,6 @@ __all__ = [
130
127
  "DomainVerifyOutput",
131
128
  "Invoice",
132
129
  "InvoiceListOutput",
133
- "NHPServerPeerInfo",
134
130
  "Portal",
135
131
  "PortalSession",
136
132
  "QuotaPlan",
layerv_qurl/_utils.py CHANGED
@@ -43,7 +43,6 @@ from layerv_qurl.types import (
43
43
  AccessGrant,
44
44
  AccessPolicy,
45
45
  AccessToken,
46
- AgentBootstrapOutput,
47
46
  AIAgentPolicy,
48
47
  APIKey,
49
48
  APIKeyListOutput,
@@ -68,7 +67,6 @@ from layerv_qurl.types import (
68
67
  InvoiceListOutput,
69
68
  ListOutput,
70
69
  MintOutput,
71
- NHPServerPeerInfo,
72
70
  Portal,
73
71
  PortalSession,
74
72
  Quota,
@@ -991,9 +989,10 @@ def _parse_rate_limits(data: dict[str, Any] | None) -> RateLimits | None:
991
989
  create_per_hour=data.get("create_per_hour", 0),
992
990
  list_per_minute=data.get("list_per_minute", 0),
993
991
  resolve_per_minute=data.get("resolve_per_minute", 0),
994
- max_active_qurls=data.get("max_active_qurls", 0),
995
- max_tokens_per_qurl=data.get("max_tokens_per_qurl", 0),
992
+ max_active_qurls=data.get("max_active_qurls"),
996
993
  max_expiry_seconds=data.get("max_expiry_seconds", 0),
994
+ max_active_resources=data.get("max_active_resources"),
995
+ max_data_transfer_bytes=data.get("max_data_transfer_bytes"),
997
996
  )
998
997
 
999
998
 
@@ -1002,11 +1001,14 @@ def _parse_usage_block(data: dict[str, Any] | None) -> Usage | None:
1002
1001
  return None
1003
1002
  return Usage(
1004
1003
  qurls_created=data.get("qurls_created", 0),
1005
- active_qurls=data.get("active_qurls", 0),
1004
+ active_qurls=data.get("active_qurls"),
1006
1005
  # Nullable per the API spec — the field is null when
1007
1006
  # max_active_qurls is unlimited.
1008
1007
  active_qurls_percent=data.get("active_qurls_percent"),
1009
1008
  total_accesses=data.get("total_accesses", 0),
1009
+ active_resources=data.get("active_resources"),
1010
+ active_resources_percent=data.get("active_resources_percent"),
1011
+ data_transfer_bytes=data.get("data_transfer_bytes"),
1010
1012
  )
1011
1013
 
1012
1014
 
@@ -1292,8 +1294,10 @@ def parse_current_period_usage(data: dict[str, Any]) -> CurrentPeriodUsage:
1292
1294
  period_start=_parse_dt(data.get("period_start")),
1293
1295
  period_end=_parse_dt(data.get("period_end")),
1294
1296
  qurls_created=data.get("qurls_created", 0),
1295
- active_qurls=data.get("active_qurls", 0),
1297
+ active_qurls=data.get("active_qurls"),
1296
1298
  cost_estimate=_parse_usage_cost(data.get("cost_estimate")),
1299
+ active_resources=data.get("active_resources"),
1300
+ data_transfer_bytes=data.get("data_transfer_bytes"),
1297
1301
  )
1298
1302
 
1299
1303
 
@@ -1439,22 +1443,6 @@ def parse_connector_installation_list_output(
1439
1443
  )
1440
1444
 
1441
1445
 
1442
- def parse_agent_bootstrap_output(data: dict[str, Any]) -> AgentBootstrapOutput:
1443
- """Parse connector agent bootstrap output."""
1444
- raw_peer = data.get("nhp_server_peer")
1445
- peer = raw_peer if isinstance(raw_peer, dict) else {}
1446
- return AgentBootstrapOutput(
1447
- agent_id=data.get("agent_id", ""),
1448
- registered_at=_parse_dt(data.get("registered_at")),
1449
- nhp_server_peer=NHPServerPeerInfo(
1450
- public_key_b64=peer.get("public_key_b64", ""),
1451
- host=peer.get("host", ""),
1452
- port=peer.get("port", 0),
1453
- expire_time=peer.get("expire_time", 0),
1454
- ),
1455
- )
1456
-
1457
-
1458
1446
  def _validate_batch_create_shape(data: Any) -> None:
1459
1447
  """Defense-in-depth structural check for batch_create responses.
1460
1448
 
@@ -35,7 +35,6 @@ from layerv_qurl._utils import (
35
35
  parse_access_code,
36
36
  parse_access_code_list_output,
37
37
  parse_access_token,
38
- parse_agent_bootstrap_output,
39
38
  parse_api_key,
40
39
  parse_api_key_list_output,
41
40
  parse_batch_create_output,
@@ -101,7 +100,6 @@ if TYPE_CHECKING:
101
100
  AccessCodeListOutput,
102
101
  AccessPolicy,
103
102
  AccessToken,
104
- AgentBootstrapOutput,
105
103
  APIKey,
106
104
  APIKeyListOutput,
107
105
  BatchCreateItem,
@@ -1516,33 +1514,6 @@ class AsyncQURLClient:
1516
1514
  )
1517
1515
  return parse_connector_installation_list_output(data, meta)
1518
1516
 
1519
- async def bootstrap_agent(
1520
- self,
1521
- *,
1522
- public_key: str,
1523
- agent_id: str | None = None,
1524
- hostname: str | None = None,
1525
- version: str | None = None,
1526
- idempotency_key: str | None = None,
1527
- ) -> AgentBootstrapOutput:
1528
- """Bootstrap a LayerV qURL Connector agent."""
1529
- validate_required_string(public_key, "public_key")
1530
- body = build_body(
1531
- {
1532
- "public_key": public_key,
1533
- "agent_id": agent_id,
1534
- "hostname": hostname,
1535
- "version": version,
1536
- }
1537
- )
1538
- resp = await self._request(
1539
- "POST",
1540
- "/v1/agent/bootstrap",
1541
- body=body,
1542
- headers=idempotency_headers(idempotency_key),
1543
- )
1544
- return parse_agent_bootstrap_output(resp)
1545
-
1546
1517
  # --- Internal HTTP plumbing ---
1547
1518
 
1548
1519
  async def _request(
layerv_qurl/client.py CHANGED
@@ -36,7 +36,6 @@ from layerv_qurl._utils import (
36
36
  parse_access_code,
37
37
  parse_access_code_list_output,
38
38
  parse_access_token,
39
- parse_agent_bootstrap_output,
40
39
  parse_api_key,
41
40
  parse_api_key_list_output,
42
41
  parse_batch_create_output,
@@ -101,7 +100,6 @@ if TYPE_CHECKING:
101
100
  AccessCodeListOutput,
102
101
  AccessPolicy,
103
102
  AccessToken,
104
- AgentBootstrapOutput,
105
103
  APIKey,
106
104
  APIKeyListOutput,
107
105
  BatchCreateItem,
@@ -1649,33 +1647,6 @@ class QURLClient:
1649
1647
  )
1650
1648
  return parse_connector_installation_list_output(data, meta)
1651
1649
 
1652
- def bootstrap_agent(
1653
- self,
1654
- *,
1655
- public_key: str,
1656
- agent_id: str | None = None,
1657
- hostname: str | None = None,
1658
- version: str | None = None,
1659
- idempotency_key: str | None = None,
1660
- ) -> AgentBootstrapOutput:
1661
- """Bootstrap a LayerV qURL Connector agent."""
1662
- validate_required_string(public_key, "public_key")
1663
- body = build_body(
1664
- {
1665
- "public_key": public_key,
1666
- "agent_id": agent_id,
1667
- "hostname": hostname,
1668
- "version": version,
1669
- }
1670
- )
1671
- resp = self._request(
1672
- "POST",
1673
- "/v1/agent/bootstrap",
1674
- body=body,
1675
- headers=idempotency_headers(idempotency_key),
1676
- )
1677
- return parse_agent_bootstrap_output(resp)
1678
-
1679
1650
  # --- Internal HTTP plumbing ---
1680
1651
 
1681
1652
  def _request(
layerv_qurl/types.py CHANGED
@@ -211,15 +211,18 @@ class ListOutput:
211
211
 
212
212
  @dataclass
213
213
  class RateLimits:
214
- """Rate limit configuration."""
214
+ """Rate limits and plan allowances."""
215
215
 
216
216
  create_per_minute: int = 0
217
217
  create_per_hour: int = 0
218
218
  list_per_minute: int = 0
219
219
  resolve_per_minute: int = 0
220
- max_active_qurls: int = 0
221
- max_tokens_per_qurl: int = 0
220
+ # Deprecated alias for max_active_resources, not a qURL-count cap.
221
+ max_active_qurls: int | None = None
222
222
  max_expiry_seconds: int = 0
223
+ # Missing/null limits remain unknown; -1 is the unlimited sentinel.
224
+ max_active_resources: int | None = None
225
+ max_data_transfer_bytes: int | None = None
223
226
 
224
227
 
225
228
  @dataclass
@@ -227,10 +230,15 @@ class Usage:
227
230
  """Usage statistics."""
228
231
 
229
232
  qurls_created: int = 0
230
- active_qurls: int = 0
233
+ # Deprecated alias for active_resources, not a count of qURLs.
234
+ active_qurls: int | None = None
231
235
  # Changed from float=0.0 — callers must None-check before arithmetic.
232
236
  active_qurls_percent: float | None = None
233
237
  total_accesses: int = 0
238
+ active_resources: int | None = None
239
+ active_resources_percent: float | None = None
240
+ # Measured bytes this UTC calendar month; None is distinct from zero.
241
+ data_transfer_bytes: int | None = None
234
242
 
235
243
 
236
244
  @dataclass
@@ -550,8 +558,12 @@ class CurrentPeriodUsage:
550
558
  period_start: datetime | None
551
559
  period_end: datetime | None
552
560
  qurls_created: int
553
- active_qurls: int
561
+ # Deprecated alias for active_resources, not a count of qURLs.
562
+ active_qurls: int | None
554
563
  cost_estimate: UsageCostEstimate | None = None
564
+ active_resources: int | None = None
565
+ # Measured bytes this UTC calendar month, independent of the billing period.
566
+ data_transfer_bytes: int | None = None
555
567
 
556
568
 
557
569
  @dataclass
@@ -663,25 +675,6 @@ class ConnectorInstallationListOutput:
663
675
  has_more: bool = False
664
676
 
665
677
 
666
- @dataclass
667
- class NHPServerPeerInfo:
668
- """NHP server peer info returned to connector agents."""
669
-
670
- public_key_b64: str
671
- host: str
672
- port: int
673
- expire_time: int
674
-
675
-
676
- @dataclass
677
- class AgentBootstrapOutput:
678
- """Response from connector agent bootstrap."""
679
-
680
- agent_id: str
681
- registered_at: datetime | None
682
- nhp_server_peer: NHPServerPeerInfo
683
-
684
-
685
678
  # ---- batch_create input shape -------------------------------------------
686
679
  # TypedDicts for :meth:`QURLClient.batch_create` items. Split into a
687
680
  # required-fields base class and an `total=False` subclass so callers on
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: qurl-python
3
- Version: 0.2.2
3
+ Version: 0.4.0
4
4
  Summary: Python SDK for the qURL™ API — secure, time-limited access links. Quantum URL is how you enter the hidden layer of the internet.
5
5
  Project-URL: Homepage, https://github.com/layervai/qurl-python
6
6
  Project-URL: Repository, https://github.com/layervai/qurl-python
@@ -25,6 +25,7 @@ Requires-Dist: langchain-core<2,>=0.3; extra == 'dev'
25
25
  Requires-Dist: mypy>=1.14; extra == 'dev'
26
26
  Requires-Dist: pytest-asyncio>=0.25; extra == 'dev'
27
27
  Requires-Dist: pytest>=8; extra == 'dev'
28
+ Requires-Dist: qurl-conformance<0.13,>=0.1.2; extra == 'dev'
28
29
  Requires-Dist: respx>=0.22; extra == 'dev'
29
30
  Requires-Dist: ruff>=0.11; extra == 'dev'
30
31
  Provides-Extra: langchain
@@ -331,10 +332,31 @@ All error classes inherit from `QURLError`, so `except QURLError` catches everyt
331
332
  ```python
332
333
  quota = client.get_quota()
333
334
  print(f"Plan: {quota.plan}")
334
- print(f"Active qURLs: {quota.usage.active_qurls}")
335
- print(f"Rate limit: {quota.rate_limits.create_per_minute}/min")
335
+ usage = quota.usage
336
+ active = usage.active_resources if usage is not None else None
337
+ if active is None and usage is not None:
338
+ active = usage.active_qurls
339
+ print("Active protected resources:", active if active is not None else "unavailable")
340
+ if quota.rate_limits is not None:
341
+ print(f"Rate limit: {quota.rate_limits.create_per_minute}/min")
336
342
  ```
337
343
 
344
+ Use `max_active_resources`, `active_resources`, and `active_resources_percent`
345
+ for resource quotas. `max_data_transfer_bytes` is the monthly byte allowance;
346
+ `data_transfer_bytes` is measured usage, available on both quota usage and
347
+ current-period responses. This usage always covers the current UTC calendar
348
+ month, independently of a paid account's billing period. Missing or null values
349
+ remain `None`, distinct from a reported zero. The [service OpenAPI spec](https://github.com/layervai/qurl-service/blob/01d7c47361b5f72f890428f1a53bd04e8d7f67cc/api/openapi.yaml#L5796)
350
+ defines `-1` as unlimited for `max_active_resources` and its legacy alias
351
+ `max_active_qurls`, as well as `max_data_transfer_bytes`.
352
+
353
+ **Migration:** the deprecated `max_active_qurls` and `active_qurls` fields still
354
+ represent protected resources. Missing values now return `None` instead of
355
+ `0`; check `is not None` before arithmetic or displaying a count. Canonical
356
+ and legacy fields preserve their own wire values, so an older response that
357
+ omits canonical fields leaves those fields `None`. Prefer the canonical fields
358
+ when available.
359
+
338
360
  JWT-authenticated dashboard endpoints are also available for usage, customer
339
361
  settings, billing sessions, invoices, and API-key management. API-key auth
340
362
  continues to work for normal qURL, resource, domain, webhook, connector, and
@@ -0,0 +1,12 @@
1
+ layerv_qurl/__init__.py,sha256=1PMfphJ3oAJm0VygtRg3lH2uH0NP1ruzkpJeErJJZkk,3795
2
+ layerv_qurl/_utils.py,sha256=UUhH4jsxv6KxDA_iSl3r-JETDl5Bqic0d2OOtM7gLkM,65742
3
+ layerv_qurl/async_client.py,sha256=2ap4aPZ0FfThnqjA2-P5bkyWpTYYqXC0yyu2D09QRFs,64703
4
+ layerv_qurl/client.py,sha256=w43jvXKUOyOS_Uta2ve7VfLz8VcM8ESMlwfO-QvFEFE,70582
5
+ layerv_qurl/errors.py,sha256=7vyBEsJnN-e7804hil9KRwEgmUv5QW-DUGTf6FdSaV0,4932
6
+ layerv_qurl/langchain.py,sha256=MJdow63lc6DXfL-riyu-rYC3cMiEKIOY9UZUvY4hEUQ,4695
7
+ layerv_qurl/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ layerv_qurl/types.py,sha256=Rb4Wu1ER_1U4pNES7A__TR_k7FPj3Y_6EmHOijGZC1w,18872
9
+ qurl_python-0.4.0.dist-info/METADATA,sha256=dURx4UtZTHY3pdYLXEm96FlY3Ab7Ltyn2f9wqmhUkJg,14560
10
+ qurl_python-0.4.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
11
+ qurl_python-0.4.0.dist-info/licenses/LICENSE,sha256=cg6W6pLqMQFf89yPfz5ClnVSrNEeoTh3NOEQyH9CaZw,1069
12
+ qurl_python-0.4.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.30.1
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,12 +0,0 @@
1
- layerv_qurl/__init__.py,sha256=nrIMkVysd1vLBj9p1TvFpz8JQFCA5al-lhCLyzIiiMk,3897
2
- layerv_qurl/_utils.py,sha256=OdZWAKOwewxzlSEeimgmv2r9o8feQWU-idwGziJg-bI,66046
3
- layerv_qurl/async_client.py,sha256=05wIEqQTPsTB8y8hYXXM0jP8EIrXb1pF4nEHcVp7Ntk,65599
4
- layerv_qurl/client.py,sha256=yvH-RlAx19LYCghEQpV0uam-DcF5lvloW3UKTUkQaRo,71466
5
- layerv_qurl/errors.py,sha256=7vyBEsJnN-e7804hil9KRwEgmUv5QW-DUGTf6FdSaV0,4932
6
- layerv_qurl/langchain.py,sha256=MJdow63lc6DXfL-riyu-rYC3cMiEKIOY9UZUvY4hEUQ,4695
7
- layerv_qurl/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- layerv_qurl/types.py,sha256=HhUFPS3TVxttXFyHGtUMvOPNLh7AZWQq6Uueq6o0pho,18490
9
- qurl_python-0.2.2.dist-info/METADATA,sha256=qDL7LzO0BFobOEWSrLnPMzKLGYZcgfm9XKgl0cC_o00,13161
10
- qurl_python-0.2.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
11
- qurl_python-0.2.2.dist-info/licenses/LICENSE,sha256=cg6W6pLqMQFf89yPfz5ClnVSrNEeoTh3NOEQyH9CaZw,1069
12
- qurl_python-0.2.2.dist-info/RECORD,,