renderplan 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.
- renderplan/__init__.py +77 -0
- renderplan/__main__.py +3 -0
- renderplan/_toml.py +82 -0
- renderplan/_version.py +24 -0
- renderplan/cli.py +71 -0
- renderplan/codec.py +688 -0
- renderplan/errors.py +10 -0
- renderplan/identity.py +80 -0
- renderplan/model.py +937 -0
- renderplan/py.typed +0 -0
- renderplan/version.py +13 -0
- renderplan-0.1.0.dist-info/METADATA +160 -0
- renderplan-0.1.0.dist-info/RECORD +17 -0
- renderplan-0.1.0.dist-info/WHEEL +5 -0
- renderplan-0.1.0.dist-info/entry_points.txt +2 -0
- renderplan-0.1.0.dist-info/licenses/LICENSE +201 -0
- renderplan-0.1.0.dist-info/top_level.txt +1 -0
renderplan/identity.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import math
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .errors import ValidationError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _plain(value: Any) -> Any:
|
|
13
|
+
if hasattr(value, "identity_payload"):
|
|
14
|
+
return _plain(value.identity_payload())
|
|
15
|
+
if isinstance(value, Mapping):
|
|
16
|
+
return {
|
|
17
|
+
str(key): _plain(item)
|
|
18
|
+
for key, item in sorted(value.items(), key=lambda kv: str(kv[0]))
|
|
19
|
+
}
|
|
20
|
+
if isinstance(value, tuple):
|
|
21
|
+
return [_plain(item) for item in value]
|
|
22
|
+
if isinstance(value, list):
|
|
23
|
+
return [_plain(item) for item in value]
|
|
24
|
+
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
25
|
+
return value
|
|
26
|
+
raise TypeError(f"Unsupported canonical identity value: {type(value).__name__}")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def canonical_json_bytes(value: Any) -> bytes:
|
|
30
|
+
return json.dumps(
|
|
31
|
+
_plain(value),
|
|
32
|
+
sort_keys=True,
|
|
33
|
+
separators=(",", ":"),
|
|
34
|
+
ensure_ascii=False,
|
|
35
|
+
allow_nan=False,
|
|
36
|
+
).encode("utf-8")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def sha256_id(value: Any) -> str:
|
|
40
|
+
return "sha256:" + hashlib.sha256(canonical_json_bytes(value)).hexdigest()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def require_sha256_id(value: str, field: str) -> str:
|
|
44
|
+
if not isinstance(value, str) or not value.startswith("sha256:") or len(value) != 71:
|
|
45
|
+
raise ValidationError(f"{field} must be a sha256:<64 hex> identity")
|
|
46
|
+
try:
|
|
47
|
+
int(value[7:], 16)
|
|
48
|
+
except ValueError as exc:
|
|
49
|
+
raise ValidationError(f"{field} must contain lowercase/uppercase hexadecimal") from exc
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def require_nonempty(value: str, field: str) -> str:
|
|
54
|
+
if not isinstance(value, str) or not value.strip():
|
|
55
|
+
raise ValidationError(f"{field} must be a non-empty string")
|
|
56
|
+
return value
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def require_toml_value(value: Any, field: str = "value") -> Any:
|
|
60
|
+
"""Validate the small JSON/TOML-safe subset used for opaque binding/options data."""
|
|
61
|
+
if isinstance(value, (bool, str)):
|
|
62
|
+
return value
|
|
63
|
+
if isinstance(value, int):
|
|
64
|
+
return value
|
|
65
|
+
if isinstance(value, float):
|
|
66
|
+
if not math.isfinite(value):
|
|
67
|
+
raise ValidationError(f"{field} must not contain NaN or infinity")
|
|
68
|
+
return value
|
|
69
|
+
if isinstance(value, Mapping):
|
|
70
|
+
for key, item in value.items():
|
|
71
|
+
if not isinstance(key, str):
|
|
72
|
+
raise ValidationError(f"{field} keys must be strings")
|
|
73
|
+
require_nonempty(key, f"{field} key")
|
|
74
|
+
require_toml_value(item, f"{field}.{key}")
|
|
75
|
+
return value
|
|
76
|
+
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
|
|
77
|
+
for index, item in enumerate(value):
|
|
78
|
+
require_toml_value(item, f"{field}[{index}]")
|
|
79
|
+
return value
|
|
80
|
+
raise ValidationError(f"{field} contains unsupported TOML value {type(value).__name__}")
|