decern 0.1.1__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.
decern/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Minimal, dependency-free Python client for the decern PDP (AuthZEN 1.0)."""
3
+
4
+ from .client import Client, Decision, DecernError
5
+
6
+ __all__ = ["Client", "Decision", "DecernError"]
7
+ __version__ = "0.1.0"
decern/client.py ADDED
@@ -0,0 +1,84 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """AuthZEN 1.0 Access Evaluation client for the decern PDP."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import urllib.error
8
+ import urllib.request
9
+ from dataclasses import dataclass, field
10
+ from typing import Any, Dict, List, Optional, Union
11
+
12
+ Entity = Dict[str, Any] # {"type": ..., "id": ...}
13
+ Action = Union[str, Dict[str, Any]]
14
+
15
+
16
+ class DecernError(Exception):
17
+ """Transport failure or non-2xx response from the PDP."""
18
+
19
+
20
+ @dataclass
21
+ class Decision:
22
+ allowed: bool
23
+ reasons: List[str] = field(default_factory=list)
24
+ errors: List[str] = field(default_factory=list)
25
+ context: Optional[Dict[str, Any]] = None
26
+
27
+
28
+ class Client:
29
+ """Client for a decern PDP speaking AuthZEN 1.0 Access Evaluation."""
30
+
31
+ def __init__(self, base_url: str = "http://127.0.0.1:8080", timeout: float = 5.0):
32
+ self.base_url = base_url.rstrip("/")
33
+ self.timeout = timeout
34
+
35
+ # --- HTTP ---------------------------------------------------------------
36
+
37
+ def _request(self, method: str, path: str, body: Optional[dict] = None) -> Any:
38
+ data = json.dumps(body).encode() if body is not None else None
39
+ headers = {"Content-Type": "application/json"} if data else {}
40
+ req = urllib.request.Request(
41
+ self.base_url + path, data=data, headers=headers, method=method
42
+ )
43
+ try:
44
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
45
+ raw = resp.read().decode()
46
+ except urllib.error.HTTPError as e:
47
+ raise DecernError(f"{method} {path} -> {e.code} {e.reason}") from e
48
+ except urllib.error.URLError as e:
49
+ raise DecernError(f"{method} {path} failed: {e.reason}") from e
50
+ return raw
51
+
52
+ # --- API ----------------------------------------------------------------
53
+
54
+ def evaluate(
55
+ self,
56
+ subject: Entity,
57
+ action: Action,
58
+ resource: Entity,
59
+ context: Optional[Dict[str, Any]] = None,
60
+ ) -> Decision:
61
+ act = {"name": action} if isinstance(action, str) else action
62
+ body: Dict[str, Any] = {"subject": subject, "action": act, "resource": resource}
63
+ if context is not None:
64
+ body["context"] = context
65
+ raw = self._request("POST", "/access/v1/evaluation", body)
66
+ resp = json.loads(raw)
67
+ ctx = resp.get("context") or None
68
+ reasons = list(ctx.get("reasons", [])) if ctx else []
69
+ errors = list(ctx.get("errors", [])) if ctx else []
70
+ return Decision(
71
+ allowed=bool(resp.get("decision", False)),
72
+ reasons=reasons,
73
+ errors=errors,
74
+ context=ctx,
75
+ )
76
+
77
+ def pubkey(self) -> str:
78
+ return json.loads(self._request("GET", "/pubkey"))["kid"]
79
+
80
+ def healthy(self) -> bool:
81
+ try:
82
+ return self._request("GET", "/healthz").strip() == "ok"
83
+ except DecernError:
84
+ return False
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: decern
3
+ Version: 0.1.1
4
+ Summary: Dependency-free Python client for the decern PDP (AuthZEN 1.0 Access Evaluation).
5
+ License-Expression: Apache-2.0
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+
9
+ # decern Python SDK
10
+
11
+ Dependency-free Python client for the decern PDP, speaking
12
+ [AuthZEN 1.0](https://openid.net/specs/authorization-api-1_0.html) Access Evaluation.
13
+ Standard library only — no `requests`/`httpx`.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ pip install .
19
+ # or
20
+ uv pip install .
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ Start the PDP:
26
+
27
+ ```sh
28
+ decern-serve
29
+ ```
30
+
31
+ Then:
32
+
33
+ ```python
34
+ from decern import Client
35
+
36
+ c = Client("http://127.0.0.1:8080")
37
+
38
+ d = c.evaluate(
39
+ subject={"type": "Principal", "id": "corp"},
40
+ action="Read", # or {"name": "Read"}
41
+ resource={"type": "Resource", "id": "claim1"},
42
+ context={"now": 100}, # optional; PDP injects `now` if omitted
43
+ )
44
+
45
+ print(d.allowed) # True / False
46
+ print(d.reasons) # e.g. ["policy0"] on allow
47
+ print(d.errors) # e.g. ["no_policy"] on deny
48
+
49
+ c.pubkey() # ed25519 public key id (hex)
50
+ c.healthy() # True if /healthz == "ok"
51
+ ```
52
+
53
+ ## Test
54
+
55
+ ```sh
56
+ cd sdks/python
57
+ python -m unittest discover -v
58
+ ```
@@ -0,0 +1,5 @@
1
+ decern/__init__.py,sha256=Gpcs5x6J6daMPwQokXnKdjwmCheE9W5_4A_Q110Ka0U,239
2
+ decern/client.py,sha256=_K4VmOWRE_yOPDgZ8rf9K9hqXkofY_MVcL5xn60s4oY,2925
3
+ decern-0.1.1.dist-info/METADATA,sha256=iVoqx9WBPKpH3666SB1pFPcsFPoPNCnQISb4rWTtiHY,1155
4
+ decern-0.1.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
5
+ decern-0.1.1.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