rightos-sdk 0.1.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.
rightos/__init__.py ADDED
@@ -0,0 +1,226 @@
1
+ """RightOS Python SDK (zero-dependency, urllib-based).
2
+
3
+ RightOS is privacy-preserving rights verification infrastructure:
4
+ digital QR tickets ("Right Tokens") for queues, reservations, EV charging,
5
+ and package pickup. It verifies that a valid right is present - never who
6
+ the person is.
7
+
8
+ API reference: https://rightos.i-s3.com/openapi.json
9
+
10
+ Example:
11
+ >>> from rightos import RightOS
12
+ >>> client = RightOS(api_key="rk_live_...")
13
+ >>> issued = client.issue_token(location_id="loc_...", title="Queue ticket")
14
+ >>> # Hand issued["walletUrl"] (QR page) to your customer.
15
+ >>> outcome = client.verify_token(issued["token"]["id"], issued["verificationCode"])
16
+ >>> outcome["result"]
17
+ 'success'
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import urllib.error
24
+ import urllib.request
25
+ from typing import Any, Optional
26
+
27
+ __all__ = ["RightOS", "RightOSError", "DEFAULT_BASE_URL"]
28
+ __version__ = "0.1.0"
29
+
30
+ DEFAULT_BASE_URL = "https://rightos.i-s3.com"
31
+
32
+
33
+ class RightOSError(Exception):
34
+ """Raised for any non-2xx API response.
35
+
36
+ Attributes:
37
+ status: HTTP status code.
38
+ code: Machine-readable error code (e.g. "invalid_api_key",
39
+ "policy_transfer_disabled", "transfer_limit_reached").
40
+ retry_after_sec: Seconds to wait before retrying (present on 429).
41
+ """
42
+
43
+ def __init__(self, status: int, code: str, retry_after_sec: Optional[int] = None):
44
+ super().__init__(f"RightOS API error {status}: {code}")
45
+ self.status = status
46
+ self.code = code
47
+ self.retry_after_sec = retry_after_sec
48
+
49
+
50
+ class RightOS:
51
+ """RightOS API client.
52
+
53
+ Operator methods require ``api_key``. Public methods (verify_token,
54
+ transfer_token, get_token, get_location_policy, list_plans,
55
+ register_organization) work without a key.
56
+ """
57
+
58
+ def __init__(self, api_key: Optional[str] = None, base_url: str = DEFAULT_BASE_URL, timeout: float = 15.0):
59
+ self.api_key = api_key
60
+ self.base_url = base_url.rstrip("/")
61
+ self.timeout = timeout
62
+
63
+ # ---------- internals ----------
64
+
65
+ def _request(self, method: str, path: str, body: Any = ...) -> Any:
66
+ headers = {"User-Agent": f"rightos-sdk-python/{__version__}"}
67
+ data = None
68
+ if body is not ...:
69
+ headers["Content-Type"] = "application/json"
70
+ data = json.dumps(body).encode("utf-8")
71
+ if self.api_key:
72
+ headers["x-rightos-key"] = self.api_key
73
+ req = urllib.request.Request(
74
+ f"{self.base_url}{path}", data=data, headers=headers, method=method
75
+ )
76
+ try:
77
+ with urllib.request.urlopen(req, timeout=self.timeout) as res:
78
+ return json.loads(res.read().decode("utf-8"))
79
+ except urllib.error.HTTPError as e:
80
+ code = "unknown_error"
81
+ try:
82
+ payload = json.loads(e.read().decode("utf-8"))
83
+ code = payload.get("error", code)
84
+ except Exception:
85
+ pass
86
+ retry_after = e.headers.get("Retry-After")
87
+ raise RightOSError(
88
+ e.code, code, int(retry_after) if retry_after else None
89
+ ) from None
90
+
91
+ # ---------- Public endpoints (no API key required) ----------
92
+
93
+ def list_plans(self) -> list[dict]:
94
+ """List pricing plans (globally uniform pricing)."""
95
+ return self._request("GET", "/api/rightos/plans")["plans"]
96
+
97
+ def get_token(self, token_id: str) -> dict:
98
+ """Get a Right Token (never includes the secret hash)."""
99
+ return self._request("GET", f"/api/rightos/tokens/{token_id}")["token"]
100
+
101
+ def verify_token(self, token_id: str, verification_code: str) -> dict:
102
+ """Verify a Right Token by its verificationCode.
103
+
104
+ Returns ``{"result": "success" | "failed" | "expired" | "cancelled"
105
+ | "already_used", "token": {...}}``. Rate limited: 10/min per
106
+ IP+token, 60/min per IP.
107
+ """
108
+ return self._request(
109
+ "POST",
110
+ f"/api/rightos/tokens/{token_id}/verify",
111
+ {"verificationCode": verification_code},
112
+ )
113
+
114
+ def transfer_token(self, token_id: str, current_verification_code: str) -> dict:
115
+ """Transfer a right (re-keying). Only the current holder can transfer.
116
+
117
+ Subject to the location policy - raises RightOSError with code
118
+ "policy_transfer_disabled" or "transfer_limit_reached" (HTTP 409).
119
+ The new verificationCode and walletUrl are returned exactly once.
120
+ """
121
+ return self._request(
122
+ "POST",
123
+ f"/api/rightos/tokens/{token_id}/transfer",
124
+ {"verificationCode": current_verification_code},
125
+ )
126
+
127
+ def get_location_policy(self, location_id: str) -> dict:
128
+ """Get a location's effective policy (public, for transparency)."""
129
+ return self._request("GET", f"/api/rightos/locations/{location_id}/policy")
130
+
131
+ def register_organization(
132
+ self, name: str, contact_email: str, plan_id: str = "free", country: str = "JP"
133
+ ) -> dict:
134
+ """Register an organization.
135
+
136
+ The returned apiKey is shown EXACTLY ONCE - store it securely.
137
+ Rate limited: 5/hour per IP.
138
+ """
139
+ return self._request(
140
+ "POST",
141
+ "/api/rightos/organizations",
142
+ {"name": name, "contactEmail": contact_email, "planId": plan_id, "country": country},
143
+ )
144
+
145
+ # ---------- Operator endpoints (API key required) ----------
146
+
147
+ def list_locations(self) -> list[dict]:
148
+ """List your organization's locations."""
149
+ return self._request("GET", "/api/rightos/locations")["locations"]
150
+
151
+ def create_location(
152
+ self,
153
+ name: str,
154
+ address: str = "",
155
+ type: str = "other",
156
+ timezone: str = "Asia/Tokyo",
157
+ ) -> dict:
158
+ """Create a location. Raises 402 when the plan's location limit is exceeded."""
159
+ return self._request(
160
+ "POST",
161
+ "/api/rightos/locations",
162
+ {"name": name, "address": address, "type": type, "timezone": timezone},
163
+ )["location"]
164
+
165
+ def set_location_policy(self, location_id: str, patch: Optional[dict]) -> dict:
166
+ """Override a location's policy (partial update).
167
+
168
+ Pass ``None`` to reset to the industry preset.
169
+ """
170
+ return self._request(
171
+ "PUT", f"/api/rightos/locations/{location_id}/policy", patch
172
+ )
173
+
174
+ def issue_token(
175
+ self,
176
+ location_id: str,
177
+ title: str,
178
+ description: str = "",
179
+ start_time: Optional[str] = None,
180
+ end_time: Optional[str] = None,
181
+ ) -> dict:
182
+ """Issue a Right Token.
183
+
184
+ The verificationCode and walletUrl are returned exactly once -
185
+ hand the walletUrl (QR page) to the end user. Raises 402 when the
186
+ plan's monthly token limit is exceeded.
187
+ """
188
+ body: dict[str, Any] = {"locationId": location_id, "title": title}
189
+ if description:
190
+ body["description"] = description
191
+ if start_time:
192
+ body["startTime"] = start_time
193
+ if end_time:
194
+ body["endTime"] = end_time
195
+ return self._request("POST", "/api/rightos/tokens/issue", body)
196
+
197
+ def use_token(self, token_id: str) -> dict:
198
+ """Mark a token as used (own organization only)."""
199
+ return self._request("POST", f"/api/rightos/tokens/{token_id}/use")["token"]
200
+
201
+ def cancel_token(self, token_id: str) -> dict:
202
+ """Cancel a token (own organization only)."""
203
+ return self._request("POST", f"/api/rightos/tokens/{token_id}/cancel")["token"]
204
+
205
+ def export_data(self) -> dict:
206
+ """Export all organization data as JSON (no lock-in; contains no secrets)."""
207
+ return self._request("GET", "/api/rightos/export")
208
+
209
+ def rotate_api_key(self) -> str:
210
+ """Re-issue the API key. The old key is invalidated immediately.
211
+
212
+ The new key is returned exactly once. This client switches to the
213
+ new key automatically.
214
+ """
215
+ new_key = self._request("POST", "/api/rightos/organizations/rotate-key")["apiKey"]
216
+ self.api_key = new_key
217
+ return new_key
218
+
219
+ def delete_organization(self, confirm_name: str) -> dict:
220
+ """Permanently delete the organization and all its data (irreversible).
221
+
222
+ ``confirm_name`` must exactly match the organization name.
223
+ """
224
+ return self._request(
225
+ "POST", "/api/rightos/organizations/delete", {"confirm": confirm_name}
226
+ )
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: rightos-sdk
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for RightOS - privacy-preserving rights verification API (digital QR tickets for queues, reservations, EV charging, package pickup).
5
+ Project-URL: Homepage, https://rightos.i-s3.com/software/rightos/docs
6
+ Project-URL: Repository, https://github.com/suomimasuda/rightos-sdk
7
+ Project-URL: API Spec, https://rightos.i-s3.com/openapi.json
8
+ Author: I-S3 Software Division
9
+ License: MIT
10
+ Keywords: digital-ticket,i-s3,qr,queue,rightos,verification
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+
18
+ # rightos-sdk — RightOS Python SDK
19
+
20
+ Official Python SDK for [RightOS](https://rightos.i-s3.com/software/rightos) — privacy-preserving rights verification infrastructure. Issue and verify digital QR tickets ("Right Tokens") for queues, reservations, EV charging, and package pickup, without ever collecting end users' names or phone numbers.
21
+
22
+ - Zero dependencies (standard library only), Python ≥ 3.9
23
+ - Machine-readable spec: [openapi.json](https://rightos.i-s3.com/openapi.json) · AI-agent docs: [llms-full.txt](https://rightos.i-s3.com/llms-full.txt)
24
+
25
+ > RightOS is not a taxi or ride-hailing service. It does not arrange vehicles, set fares, assign drivers, or broker dispatch.
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install rightos-sdk
31
+ ```
32
+
33
+ ## Quickstart (60 seconds)
34
+
35
+ ```python
36
+ from rightos import RightOS
37
+
38
+ # 1. Register once (the apiKey is returned EXACTLY ONCE — store it securely)
39
+ pub = RightOS()
40
+ reg = pub.register_organization(name="My Shop", contact_email="you@example.com", plan_id="free")
41
+ api_key = reg["apiKey"]
42
+
43
+ # 2. Operator client
44
+ client = RightOS(api_key=api_key)
45
+
46
+ # 3. Issue a digital QR ticket
47
+ location = client.list_locations()[0]
48
+ issued = client.issue_token(location_id=location["id"], title="Queue ticket")
49
+ print("Hand this to your customer:", issued["walletUrl"])
50
+
51
+ # 4. Verify on arrival (no API key needed)
52
+ outcome = RightOS().verify_token(issued["token"]["id"], issued["verificationCode"])
53
+ print(outcome["result"]) # "success"
54
+
55
+ # 5. Mark as used after service
56
+ client.use_token(issued["token"]["id"])
57
+ ```
58
+
59
+ ## Location policies (Policy Engine)
60
+
61
+ ```python
62
+ policy = client.get_location_policy(location["id"])["policy"]
63
+ # {"transferable": True, "maxTransfers": 3, "defaultValidityMinutes": 720, ...}
64
+
65
+ client.set_location_policy(location["id"], {"transferable": False}) # override
66
+ client.set_location_policy(location["id"], None) # reset to industry preset
67
+ ```
68
+
69
+ ## Error handling
70
+
71
+ ```python
72
+ from rightos import RightOS, RightOSError
73
+
74
+ try:
75
+ RightOS().transfer_token(token_id, code)
76
+ except RightOSError as e:
77
+ if e.code == "policy_transfer_disabled":
78
+ ... # this location forbids transfers
79
+ elif e.status == 429:
80
+ ... # rate limited; wait e.retry_after_sec
81
+ ```
82
+
83
+ Common codes: `missing_api_key` / `invalid_api_key` (401), plan limits (402), `policy_transfer_disabled` / `transfer_limit_reached` (409), `rate_limited` (429).
84
+
85
+ ## Try it against the live demo
86
+
87
+ ```python
88
+ demo = RightOS(api_key="rk_demo_00000000000000000000") # shared demo org
89
+ print(demo.list_locations())
90
+ ```
91
+
92
+ ## License
93
+
94
+ MIT © I-S3 Inc.
@@ -0,0 +1,4 @@
1
+ rightos/__init__.py,sha256=5jfdJpWpzk8FNadCoqoQO6s_muFu0ltzHdIXxl0gTN4,8459
2
+ rightos_sdk-0.1.0.dist-info/METADATA,sha256=8eOKbLj25FXhcoz-ytpyoDMmum40_8jrLblKTKso-ms,3313
3
+ rightos_sdk-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
4
+ rightos_sdk-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any