algovoi-keystone-httpx 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,8 @@
1
+ algovoi-keystone-httpx
2
+ Copyright 2026 AlgoVoi
3
+
4
+ This product includes software developed by AlgoVoi.
5
+ Licensed under the Apache License, Version 2.0.
6
+
7
+ Built on the AlgoVoi substrate: RFC 8785 (JCS) canonicalisation + SHA-256,
8
+ producing content-addressed execution_ref evidence recomputable offline.
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: algovoi-keystone-httpx
3
+ Version: 0.1.1
4
+ Summary: Bind outbound HTTP calls to the AlgoVoi keystone -- wrap any httpx.Client or requests.Session and every POST / PUT / PATCH / DELETE gets a content-addressed execution_ref (keystone-only edition, Apache-2.0)
5
+ Author-email: AlgoVoi <chopmob@gmail.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://docs.algovoi.co.uk/keystone-connectors
8
+ Project-URL: Documentation, https://docs.algovoi.co.uk/keystone-connectors
9
+ Project-URL: Repository, https://github.com/chopmob-cloud/AlgoVoi-Keystone-Connectors
10
+ Project-URL: Issues, https://github.com/chopmob-cloud/AlgoVoi-Keystone-Connectors/issues
11
+ Keywords: http,httpx,requests,outbound,webhook,client,keystone,execution_ref,jcs,rfc8785,algovoi
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: NOTICE
18
+ Requires-Dist: algovoi-execution-ref>=0.1.0
19
+ Dynamic: license-file
20
+
21
+ # algovoi-keystone-httpx
22
+
23
+ Bind **outbound** HTTP calls to the AlgoVoi keystone. The `asgi` connector binds the requests a
24
+ service *receives*; this one binds the calls a service *makes*. Wrap any sync `httpx.Client` or
25
+ `requests.Session` and every state-changing call (POST / PUT / PATCH / DELETE) is bound to the
26
+ `decision_ref` that authorised it, emitting a content-addressed `execution_ref` any party can
27
+ recompute offline:
28
+
29
+ ```
30
+ execution_ref = "sha256:" + SHA-256(JCS({ decision_ref, action_type, scope, outcome, executed_at_ms }))
31
+ ```
32
+
33
+ `action_type` is `http:<method>`; `scope` is the destination `host/path` (query string dropped for
34
+ stability). Outcome comes from the response status: 2xx/3xx COMMITTED, 4xx SKIPPED, 5xx/exception
35
+ FAILED. Reads (GET / HEAD / OPTIONS) pass through unbound. No httpx or requests dependency.
36
+
37
+ ```python
38
+ from algovoi_keystone_httpx import keystone_http_client
39
+
40
+ client = keystone_http_client(httpx.Client(), decision_ref=decision_ref)
41
+ client.post("https://api.partner.com/v1/pay", json=payload) # bound
42
+ ref = client.execution_ref
43
+ ```
44
+
45
+ Records emitted on `client.executions` drop straight into `algovoi-keystone-validate`.
46
+
47
+ Keystone-only edition, Apache-2.0. Built on the AlgoVoi substrate (RFC 8785 JCS + SHA-256).
@@ -0,0 +1,27 @@
1
+ # algovoi-keystone-httpx
2
+
3
+ Bind **outbound** HTTP calls to the AlgoVoi keystone. The `asgi` connector binds the requests a
4
+ service *receives*; this one binds the calls a service *makes*. Wrap any sync `httpx.Client` or
5
+ `requests.Session` and every state-changing call (POST / PUT / PATCH / DELETE) is bound to the
6
+ `decision_ref` that authorised it, emitting a content-addressed `execution_ref` any party can
7
+ recompute offline:
8
+
9
+ ```
10
+ execution_ref = "sha256:" + SHA-256(JCS({ decision_ref, action_type, scope, outcome, executed_at_ms }))
11
+ ```
12
+
13
+ `action_type` is `http:<method>`; `scope` is the destination `host/path` (query string dropped for
14
+ stability). Outcome comes from the response status: 2xx/3xx COMMITTED, 4xx SKIPPED, 5xx/exception
15
+ FAILED. Reads (GET / HEAD / OPTIONS) pass through unbound. No httpx or requests dependency.
16
+
17
+ ```python
18
+ from algovoi_keystone_httpx import keystone_http_client
19
+
20
+ client = keystone_http_client(httpx.Client(), decision_ref=decision_ref)
21
+ client.post("https://api.partner.com/v1/pay", json=payload) # bound
22
+ ref = client.execution_ref
23
+ ```
24
+
25
+ Records emitted on `client.executions` drop straight into `algovoi-keystone-validate`.
26
+
27
+ Keystone-only edition, Apache-2.0. Built on the AlgoVoi substrate (RFC 8785 JCS + SHA-256).
@@ -0,0 +1,23 @@
1
+ """algovoi-keystone-httpx -- bind outbound HTTP calls to the AlgoVoi keystone (httpx / requests)."""
2
+ from .adapter import (
3
+ EXECUTION_OUTCOMES,
4
+ KeystoneHTTPClient,
5
+ KeystoneHTTPError,
6
+ WRITE_METHODS,
7
+ keystone_http_client,
8
+ outcome_for_status,
9
+ request_execution_ref,
10
+ tamper_detected,
11
+ )
12
+
13
+ __all__ = [
14
+ "EXECUTION_OUTCOMES",
15
+ "KeystoneHTTPClient",
16
+ "KeystoneHTTPError",
17
+ "WRITE_METHODS",
18
+ "keystone_http_client",
19
+ "outcome_for_status",
20
+ "request_execution_ref",
21
+ "tamper_detected",
22
+ ]
23
+ __version__ = "0.1.0"
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: algovoi-keystone-httpx
3
+ Version: 0.1.1
4
+ Summary: Bind outbound HTTP calls to the AlgoVoi keystone -- wrap any httpx.Client or requests.Session and every POST / PUT / PATCH / DELETE gets a content-addressed execution_ref (keystone-only edition, Apache-2.0)
5
+ Author-email: AlgoVoi <chopmob@gmail.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://docs.algovoi.co.uk/keystone-connectors
8
+ Project-URL: Documentation, https://docs.algovoi.co.uk/keystone-connectors
9
+ Project-URL: Repository, https://github.com/chopmob-cloud/AlgoVoi-Keystone-Connectors
10
+ Project-URL: Issues, https://github.com/chopmob-cloud/AlgoVoi-Keystone-Connectors/issues
11
+ Keywords: http,httpx,requests,outbound,webhook,client,keystone,execution_ref,jcs,rfc8785,algovoi
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: NOTICE
18
+ Requires-Dist: algovoi-execution-ref>=0.1.0
19
+ Dynamic: license-file
20
+
21
+ # algovoi-keystone-httpx
22
+
23
+ Bind **outbound** HTTP calls to the AlgoVoi keystone. The `asgi` connector binds the requests a
24
+ service *receives*; this one binds the calls a service *makes*. Wrap any sync `httpx.Client` or
25
+ `requests.Session` and every state-changing call (POST / PUT / PATCH / DELETE) is bound to the
26
+ `decision_ref` that authorised it, emitting a content-addressed `execution_ref` any party can
27
+ recompute offline:
28
+
29
+ ```
30
+ execution_ref = "sha256:" + SHA-256(JCS({ decision_ref, action_type, scope, outcome, executed_at_ms }))
31
+ ```
32
+
33
+ `action_type` is `http:<method>`; `scope` is the destination `host/path` (query string dropped for
34
+ stability). Outcome comes from the response status: 2xx/3xx COMMITTED, 4xx SKIPPED, 5xx/exception
35
+ FAILED. Reads (GET / HEAD / OPTIONS) pass through unbound. No httpx or requests dependency.
36
+
37
+ ```python
38
+ from algovoi_keystone_httpx import keystone_http_client
39
+
40
+ client = keystone_http_client(httpx.Client(), decision_ref=decision_ref)
41
+ client.post("https://api.partner.com/v1/pay", json=payload) # bound
42
+ ref = client.execution_ref
43
+ ```
44
+
45
+ Records emitted on `client.executions` drop straight into `algovoi-keystone-validate`.
46
+
47
+ Keystone-only edition, Apache-2.0. Built on the AlgoVoi substrate (RFC 8785 JCS + SHA-256).
@@ -0,0 +1,11 @@
1
+ NOTICE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ algovoi_keystone_httpx/__init__.py
6
+ algovoi_keystone_httpx.egg-info/PKG-INFO
7
+ algovoi_keystone_httpx.egg-info/SOURCES.txt
8
+ algovoi_keystone_httpx.egg-info/dependency_links.txt
9
+ algovoi_keystone_httpx.egg-info/requires.txt
10
+ algovoi_keystone_httpx.egg-info/top_level.txt
11
+ tests/test_keystone_httpx.py
@@ -0,0 +1 @@
1
+ algovoi-execution-ref>=0.1.0
@@ -0,0 +1 @@
1
+ algovoi_keystone_httpx
@@ -0,0 +1,35 @@
1
+ [build-system]
2
+ requires = ["setuptools>=70", "Cython>=3.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "algovoi-keystone-httpx"
7
+ version = "0.1.1"
8
+ description = "Bind outbound HTTP calls to the AlgoVoi keystone -- wrap any httpx.Client or requests.Session and every POST / PUT / PATCH / DELETE gets a content-addressed execution_ref (keystone-only edition, Apache-2.0)"
9
+ readme = "README.md"
10
+ license = { text = "Apache-2.0" }
11
+ authors = [{ name = "AlgoVoi", email = "chopmob@gmail.com" }]
12
+ keywords = ["http", "httpx", "requests", "outbound", "webhook", "client", "keystone", "execution_ref", "jcs", "rfc8785", "algovoi"]
13
+ requires-python = ">=3.10"
14
+ dependencies = ["algovoi-execution-ref>=0.1.0"]
15
+ classifiers = [
16
+ "License :: OSI Approved :: Apache Software License",
17
+ "Programming Language :: Python :: 3",
18
+ "Topic :: Internet :: WWW/HTTP",
19
+ ]
20
+
21
+ [project.urls]
22
+ Homepage = "https://docs.algovoi.co.uk/keystone-connectors"
23
+ Documentation = "https://docs.algovoi.co.uk/keystone-connectors"
24
+ Repository = "https://github.com/chopmob-cloud/AlgoVoi-Keystone-Connectors"
25
+ Issues = "https://github.com/chopmob-cloud/AlgoVoi-Keystone-Connectors/issues"
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["."]
29
+ include = ["algovoi_keystone_httpx*"]
30
+
31
+ [tool.setuptools.package-data]
32
+ "algovoi_keystone_httpx" = ["*.so", "*.pyd"]
33
+
34
+ [tool.setuptools.exclude-package-data]
35
+ "algovoi_keystone_httpx" = ["*.c", "*.pyx", "*.pxd"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from setuptools import setup
2
+ from Cython.Build import cythonize
3
+ setup(ext_modules=cythonize("algovoi_keystone_httpx/adapter.pyx", build_dir="build", compiler_directives={"language_level": "3", "annotation_typing": False}))
@@ -0,0 +1,135 @@
1
+ """Tests for algovoi-keystone-httpx (keystone-only edition)."""
2
+ import pytest
3
+ from algovoi_execution_ref import execution_ref
4
+ from algovoi_keystone_httpx import (
5
+ EXECUTION_OUTCOMES, KeystoneHTTPError, keystone_http_client, outcome_for_status,
6
+ request_execution_ref, tamper_detected,
7
+ )
8
+
9
+ DR = "sha256:" + "a" * 64
10
+ TS = 1716460800000
11
+ URL = "https://api.partner.com/v1/pay?token=secret"
12
+ SCOPE = "api.partner.com/v1/pay"
13
+
14
+
15
+ class Resp:
16
+ def __init__(self, status_code):
17
+ self.status_code = status_code
18
+
19
+
20
+ class FakeClient:
21
+ """httpx.Client / requests.Session double. Returns a status set per URL; raises on 'boom'."""
22
+ def __init__(self):
23
+ self.calls = []
24
+
25
+ def post(self, url, *a, **k):
26
+ self.calls.append(("post", url))
27
+ if url == "boom":
28
+ raise RuntimeError("connection reset")
29
+ return Resp(201)
30
+
31
+ def put(self, url, *a, **k):
32
+ self.calls.append(("put", url))
33
+ return Resp(200)
34
+
35
+ def delete(self, url, *a, **k):
36
+ self.calls.append(("delete", url))
37
+ return Resp(404)
38
+
39
+ def get(self, url, *a, **k):
40
+ self.calls.append(("get", url))
41
+ return Resp(200) # read path, must pass through unbound
42
+
43
+ def request(self, method, url, *a, **k):
44
+ self.calls.append(("request", method, url))
45
+ return Resp(200)
46
+
47
+
48
+ def test_request_execution_ref_matches_primitive():
49
+ ref = request_execution_ref(decision_ref=DR, method="POST", url=URL, outcome="COMMITTED", executed_at_ms=TS)
50
+ assert ref == execution_ref(DR, "http:post", SCOPE, "COMMITTED", TS)
51
+ assert ref.startswith("sha256:") and len(ref) == 71
52
+
53
+
54
+ def test_scope_drops_query():
55
+ a = request_execution_ref(decision_ref=DR, method="POST", url="https://h/p?x=1", outcome="COMMITTED", executed_at_ms=TS)
56
+ b = request_execution_ref(decision_ref=DR, method="POST", url="https://h/p?x=2", outcome="COMMITTED", executed_at_ms=TS)
57
+ assert a == b # query string is not part of scope
58
+
59
+
60
+ def test_outcome_for_status():
61
+ assert outcome_for_status(201) == "COMMITTED"
62
+ assert outcome_for_status(302) == "COMMITTED"
63
+ assert outcome_for_status(404) == "SKIPPED"
64
+ assert outcome_for_status(500) == "FAILED"
65
+ assert outcome_for_status(0) == "FAILED"
66
+
67
+
68
+ def test_rejects_bad():
69
+ with pytest.raises(KeystoneHTTPError):
70
+ request_execution_ref(decision_ref=DR, method="POST", url=URL, outcome="DONE", executed_at_ms=TS)
71
+ with pytest.raises(KeystoneHTTPError):
72
+ request_execution_ref(decision_ref=DR, method="", url=URL, outcome="COMMITTED", executed_at_ms=TS)
73
+
74
+
75
+ def test_post_binds_committed():
76
+ c = keystone_http_client(FakeClient(), decision_ref=DR, clock_ms=lambda: TS)
77
+ c.post(URL, json={})
78
+ assert c.execution_ref == execution_ref(DR, "http:post", SCOPE, "COMMITTED", TS)
79
+ assert c.executions[0]["outcome"] == "COMMITTED"
80
+
81
+
82
+ def test_delete_4xx_is_skipped():
83
+ c = keystone_http_client(FakeClient(), decision_ref=DR, clock_ms=lambda: TS)
84
+ c.delete("https://api.partner.com/v1/pay")
85
+ assert c.execution_ref == execution_ref(DR, "http:delete", SCOPE, "SKIPPED", TS)
86
+
87
+
88
+ def test_request_write_binds():
89
+ c = keystone_http_client(FakeClient(), decision_ref=DR, clock_ms=lambda: TS)
90
+ c.request("PUT", "https://api.partner.com/v1/pay")
91
+ assert c.execution_ref == execution_ref(DR, "http:put", SCOPE, "COMMITTED", TS)
92
+
93
+
94
+ def test_request_read_passthrough_unbound():
95
+ c = keystone_http_client(FakeClient(), decision_ref=DR, clock_ms=lambda: TS)
96
+ r = c.request("GET", "https://api.partner.com/v1/pay")
97
+ assert r.status_code == 200
98
+ assert c.executions == [] # GET via request() is not bound
99
+
100
+
101
+ def test_get_passthrough_unbound():
102
+ c = keystone_http_client(FakeClient(), decision_ref=DR, clock_ms=lambda: TS)
103
+ c.get("https://api.partner.com/v1/pay")
104
+ assert c.executions == [] # reads are not bound
105
+
106
+
107
+ def test_failed_post_records_failed_and_reraises():
108
+ c = keystone_http_client(FakeClient(), decision_ref=DR, clock_ms=lambda: TS)
109
+ with pytest.raises(RuntimeError):
110
+ c.post("boom")
111
+ assert c.executions[-1]["outcome"] == "FAILED"
112
+ assert c.execution_ref == execution_ref(DR, "http:post", "boom", "FAILED", TS)
113
+
114
+
115
+ def test_rejects_bad_decision():
116
+ with pytest.raises(KeystoneHTTPError):
117
+ keystone_http_client(FakeClient(), decision_ref="")
118
+
119
+
120
+ def test_on_execution_callback():
121
+ seen = []
122
+ c = keystone_http_client(FakeClient(), decision_ref=DR, clock_ms=lambda: TS, on_execution=seen.append)
123
+ c.post(URL)
124
+ assert seen[0]["execution_ref"] == c.execution_ref
125
+
126
+
127
+ def test_tamper_detected():
128
+ ref = request_execution_ref(decision_ref=DR, method="POST", url=URL, outcome="COMMITTED", executed_at_ms=TS)
129
+ assert not tamper_detected(ref, decision_ref=DR, method="POST", url=URL, outcome="COMMITTED", executed_at_ms=TS)
130
+ assert tamper_detected(ref, decision_ref=DR, method="POST", url="https://api.partner.com/v1/refund", outcome="COMMITTED", executed_at_ms=TS)
131
+ assert tamper_detected("not-a-ref", decision_ref=DR, method="POST", url=URL, outcome="COMMITTED", executed_at_ms=TS)
132
+
133
+
134
+ def test_outcomes_constant():
135
+ assert EXECUTION_OUTCOMES == ("COMMITTED", "SKIPPED", "FAILED", "REVERSED")