opensolvency 0.1.1__tar.gz

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.
@@ -0,0 +1,15 @@
1
+ node_modules/
2
+ dist/
3
+ *.log
4
+ .env
5
+ .DS_Store
6
+ opensolvency.db
7
+ opensolvency.db-*
8
+ clients/rust/target/
9
+ clients/rust/Cargo.lock
10
+ clients/c/example
11
+ clients/c/example.exe
12
+ __pycache__/
13
+ *.pyc
14
+ clients/python/dist/
15
+ *.egg-info/
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: opensolvency
3
+ Version: 0.1.1
4
+ Summary: Python client for the OpenSolvency governance gate (REST over the HTTP ingress)
5
+ Author: General Liquidity
6
+ License: MIT
7
+ Keywords: agentic-payments,governance,opensolvency
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+
11
+ # opensolvency (Python client)
12
+
13
+ A thin REST client for the [OpenSolvency](https://github.com/general-liquidity/opensolvency)
14
+ governance gate. Point it at a running `opensolvency serve`; every payment it submits
15
+ runs through the same gate (auto-execute inside a mandate, park for operator approval,
16
+ or block). A `blocked` outcome is a normal result, not an error. Standard library
17
+ only - no dependencies.
18
+
19
+ ```python
20
+ from opensolvency import OpenSolvencyClient
21
+
22
+ os = OpenSolvencyClient("http://127.0.0.1:8787", token="...")
23
+ res = os.pay(payee="tesco", payee_class="groceries", amount=8000, # minor-units
24
+ rationale="the weekly grocery shop")
25
+ print(res["outcome"]) # settled | pending | blocked | failed
26
+ print(os.status(), os.ready())
27
+ ```
28
+
29
+ Idempotency keys are generated per `pay()` (override with `idempotency_key=`). The
30
+ full-feature surface is the TypeScript SDK (`@general-liquidity/opensolvency`); this
31
+ is for Python hosts that talk to a running ingress.
32
+
33
+ License: MIT.
@@ -0,0 +1,23 @@
1
+ # opensolvency (Python client)
2
+
3
+ A thin REST client for the [OpenSolvency](https://github.com/general-liquidity/opensolvency)
4
+ governance gate. Point it at a running `opensolvency serve`; every payment it submits
5
+ runs through the same gate (auto-execute inside a mandate, park for operator approval,
6
+ or block). A `blocked` outcome is a normal result, not an error. Standard library
7
+ only - no dependencies.
8
+
9
+ ```python
10
+ from opensolvency import OpenSolvencyClient
11
+
12
+ os = OpenSolvencyClient("http://127.0.0.1:8787", token="...")
13
+ res = os.pay(payee="tesco", payee_class="groceries", amount=8000, # minor-units
14
+ rationale="the weekly grocery shop")
15
+ print(res["outcome"]) # settled | pending | blocked | failed
16
+ print(os.status(), os.ready())
17
+ ```
18
+
19
+ Idempotency keys are generated per `pay()` (override with `idempotency_key=`). The
20
+ full-feature surface is the TypeScript SDK (`@general-liquidity/opensolvency`); this
21
+ is for Python hosts that talk to a running ingress.
22
+
23
+ License: MIT.
@@ -0,0 +1,92 @@
1
+ """OpenSolvency Python client — a thin REST client over the HTTP ingress.
2
+
3
+ The ingress runs the SAME gate as everything else, so this client adds no
4
+ authority: a payment it submits is auto-executed inside a mandate, parked for
5
+ operator approval, or blocked. Standard-library only (no dependencies).
6
+
7
+ from opensolvency import OpenSolvencyClient
8
+
9
+ os = OpenSolvencyClient("http://127.0.0.1:8787", token="...")
10
+ result = os.pay(payee="tesco", payee_class="groceries", amount=8000,
11
+ rationale="the weekly grocery shop")
12
+ print(result["outcome"]) # "settled" | "pending" | "blocked" | "failed"
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ import uuid
19
+ import urllib.error
20
+ import urllib.request
21
+ from typing import Any, Optional
22
+
23
+ __all__ = ["OpenSolvencyClient", "OpenSolvencyError"]
24
+
25
+
26
+ class OpenSolvencyError(Exception):
27
+ """Raised on a transport error (not on a gate 'blocked' — that's a normal result)."""
28
+
29
+
30
+ class OpenSolvencyClient:
31
+ def __init__(self, base_url: str = "http://127.0.0.1:8787", token: Optional[str] = None):
32
+ self.base_url = base_url.rstrip("/")
33
+ self.token = token
34
+
35
+ def _request(self, method: str, path: str, body: Any = None, headers: Optional[dict] = None):
36
+ data = json.dumps(body).encode() if body is not None else None
37
+ h = {"content-type": "application/json"}
38
+ if self.token:
39
+ h["authorization"] = f"Bearer {self.token}"
40
+ if headers:
41
+ h.update(headers)
42
+ req = urllib.request.Request(self.base_url + path, data=data, method=method, headers=h)
43
+ try:
44
+ with urllib.request.urlopen(req) as resp:
45
+ payload = resp.read()
46
+ return resp.status, (json.loads(payload) if payload else None)
47
+ except urllib.error.HTTPError as e:
48
+ payload = e.read()
49
+ # 403/401/413/429 carry a JSON body too — return it, don't raise.
50
+ return e.code, (json.loads(payload) if payload else None)
51
+ except urllib.error.URLError as e:
52
+ raise OpenSolvencyError(f"transport error: {e}") from e
53
+
54
+ def pay(
55
+ self,
56
+ payee: str,
57
+ payee_class: str,
58
+ amount: int,
59
+ currency: str = "GBP",
60
+ rail: str = "card",
61
+ rationale: str = "",
62
+ idempotency_key: Optional[str] = None,
63
+ ) -> dict:
64
+ """Submit a payment intent. Returns the gate result
65
+ {intentId, outcome, reasons, receiptId, verified}. `amount` is minor-units."""
66
+ headers = {"idempotency-key": idempotency_key or str(uuid.uuid4())}
67
+ _, body = self._request(
68
+ "POST",
69
+ "/payment-intent",
70
+ {
71
+ "payee": payee,
72
+ "payeeClass": payee_class,
73
+ "amount": amount,
74
+ "currency": currency,
75
+ "rail": rail,
76
+ "rationale": rationale,
77
+ },
78
+ headers,
79
+ )
80
+ return body or {}
81
+
82
+ def status(self) -> dict:
83
+ """Kill-switch / circuit-breaker state."""
84
+ return self._request("GET", "/status")[1] or {}
85
+
86
+ def ready(self) -> dict:
87
+ """Readiness probe."""
88
+ return self._request("GET", "/ready")[1] or {}
89
+
90
+ def openapi(self) -> dict:
91
+ """The served OpenAPI 3.1 document."""
92
+ return self._request("GET", "/openapi.json")[1] or {}
@@ -0,0 +1,17 @@
1
+ [project]
2
+ name = "opensolvency"
3
+ version = "0.1.1"
4
+ description = "Python client for the OpenSolvency governance gate (REST over the HTTP ingress)"
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "General Liquidity" }]
9
+ keywords = ["agentic-payments", "governance", "opensolvency"]
10
+ dependencies = []
11
+
12
+ [build-system]
13
+ requires = ["hatchling"]
14
+ build-backend = "hatchling.build"
15
+
16
+ [tool.hatch.build.targets.wheel]
17
+ only-include = ["opensolvency.py"]