v0.3.0 — entitlements catalog in PublicPoliciesResponse

Mirrors keysat 0014 + TS/Rust SDK 0.3.0. PublicPoliciesProduct
gains entitlements_catalog: list[EntitlementDef] with slug + name +
description. SDK consumers' in-app tier pickers can render display
names + tooltip descriptions instead of raw slugs. Empty list on
legacy products without a catalog. No breaking change.
This commit is contained in:
Keysat
2026-05-10 07:58:59 -05:00
parent 94654f6526
commit f2ea74d7e3
3 changed files with 28 additions and 2 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "keysat-licensing-client" name = "keysat-licensing-client"
version = "0.2.0" version = "0.3.0"
description = "Python client for Keysat — a self-hosted Bitcoin-paid software licensing server that runs on Start9. Verifies signed license keys offline and wraps the HTTP API for purchase, redemption, and revocation checks." description = "Python client for Keysat — a self-hosted Bitcoin-paid software licensing server that runs on Start9. Verifies signed license keys offline and wraps the HTTP API for purchase, redemption, and revocation checks."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
+3 -1
View File
@@ -67,6 +67,7 @@ try:
ValidateResponse, ValidateResponse,
ValidateOptions, ValidateOptions,
StartPurchaseOptions, StartPurchaseOptions,
EntitlementDef,
PublicPolicy, PublicPolicy,
PublicPoliciesProduct, PublicPoliciesProduct,
PublicPoliciesResponse, PublicPoliciesResponse,
@@ -81,6 +82,7 @@ try:
"ValidateResponse", "ValidateResponse",
"ValidateOptions", "ValidateOptions",
"StartPurchaseOptions", "StartPurchaseOptions",
"EntitlementDef",
"PublicPolicy", "PublicPolicy",
"PublicPoliciesProduct", "PublicPoliciesProduct",
"PublicPoliciesResponse", "PublicPoliciesResponse",
@@ -93,4 +95,4 @@ except ImportError:
# httpx not installed — that's fine, online client is optional. # httpx not installed — that's fine, online client is optional.
pass pass
__version__ = "0.2.0" __version__ = "0.3.0"
+24
View File
@@ -89,12 +89,27 @@ class PublicPolicy:
trial_days: int trial_days: int
@dataclass
class EntitlementDef:
"""One entry in a product's entitlements catalog (Keysat
migration 0014). Operator declares the closed list once per
product; policies pick from this list. Use ``name`` as the
human-readable label when rendering an in-app tier picker
(e.g. "AI summaries" instead of the raw ``ai_summaries`` slug).
"""
slug: str
name: str
description: str
@dataclass @dataclass
class PublicPoliciesProduct: class PublicPoliciesProduct:
slug: str slug: str
name: str name: str
description: str description: str
base_price_sats: int base_price_sats: int
entitlements_catalog: list[EntitlementDef]
@dataclass @dataclass
@@ -277,12 +292,21 @@ class Client:
raw = self._get(f"/v1/products/{product_slug}/policies") raw = self._get(f"/v1/products/{product_slug}/policies")
product = raw.get("product", {}) or {} product = raw.get("product", {}) or {}
policies_raw = raw.get("policies") or [] policies_raw = raw.get("policies") or []
catalog_raw = product.get("entitlements_catalog") or []
return PublicPoliciesResponse( return PublicPoliciesResponse(
product=PublicPoliciesProduct( product=PublicPoliciesProduct(
slug=product.get("slug", ""), slug=product.get("slug", ""),
name=product.get("name", ""), name=product.get("name", ""),
description=product.get("description", "") or "", description=product.get("description", "") or "",
base_price_sats=int(product.get("base_price_sats", 0)), base_price_sats=int(product.get("base_price_sats", 0)),
entitlements_catalog=[
EntitlementDef(
slug=c.get("slug", ""),
name=c.get("name", "") or c.get("slug", ""),
description=c.get("description", "") or "",
)
for c in catalog_raw
],
), ),
policies=[ policies=[
PublicPolicy( PublicPolicy(