kanonak-codec 0.1.0__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,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: kanonak-codec
3
+ Version: 0.1.0
4
+ Summary: Kanonak codec runtime (Python port): build the canonical input model from typed nodes + an embedded schema, content-address via kanonak-canonical, and (de)serialize the normalized-JSON wire form. Referenced by generated typed SDKs.
5
+ Author: Kanonak Maintainers
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://kanonak.org
8
+ Project-URL: Repository, https://github.com/kanonak-protocol/runtime
9
+ Project-URL: Issues, https://github.com/kanonak-protocol/runtime/issues
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: kanonak-canonical>=0.1.0
13
+
14
+ # kanonak-codec
15
+
16
+ The generic Kanonak codec runtime (Python): builds the canonical input model from typed nodes +
17
+ an embedded schema, content-addresses it via `kanonak-canonical`, and (de)serializes the
18
+ normalized-JSON wire form. Referenced by generated typed SDKs.
19
+
20
+ Source & issues: https://github.com/kanonak-protocol/runtime
@@ -0,0 +1,7 @@
1
+ # kanonak-codec
2
+
3
+ The generic Kanonak codec runtime (Python): builds the canonical input model from typed nodes +
4
+ an embedded schema, content-addresses it via `kanonak-canonical`, and (de)serializes the
5
+ normalized-JSON wire form. Referenced by generated typed SDKs.
6
+
7
+ Source & issues: https://github.com/kanonak-protocol/runtime
@@ -0,0 +1,156 @@
1
+ """kanonak-codec — the generic, ontology-independent codec runtime (Python port).
2
+
3
+ Given a ``CodecSchema`` (the per-package metadata a generated SDK embeds) and a
4
+ set of typed nodes, it builds the canonical input model and content-addresses it
5
+ via ``kanonak-canonical`` (the same content-form the TypeScript reference and the
6
+ ``kanonak hash`` CLI produce). It also (de)serializes the normalized-JSON wire
7
+ form. Self-contained: carriers come from the schema's datatype URIs, and the
8
+ resolved foundation URIs are embedded by the generator, so hashing needs no
9
+ runtime ontology resolution.
10
+
11
+ A node is a plain ``dict`` (the ``$``-envelope plus alias-collapsed local-name
12
+ fields); a generated Pydantic model serializes to one via ``model_dump``.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Any, Dict, List
18
+
19
+ from kanonak_canonical import (
20
+ KList,
21
+ Package,
22
+ RawScalar,
23
+ Reference,
24
+ Statement,
25
+ Subject,
26
+ TypedScalar,
27
+ canonical_form as _canonical_form,
28
+ canonical_hash as _canonical_hash,
29
+ carrier_of,
30
+ )
31
+
32
+ ENVELOPE_KEYS = {"$type", "$id", "$contentHash", "$version", "$extra"}
33
+
34
+
35
+ def _lexical(value: Any) -> str:
36
+ """The raw lexical token of a scalar — the input the canonical form normalizes."""
37
+ if isinstance(value, bool):
38
+ return "true" if value else "false"
39
+ if isinstance(value, str):
40
+ return value
41
+ return str(value)
42
+
43
+
44
+ def _value(prop: Dict[str, Any], raw: Any):
45
+ if prop["kind"] == "object":
46
+ if isinstance(raw, dict) and "$ref" in raw:
47
+ return Reference(raw["$ref"])
48
+ raise ValueError(
49
+ "Embedded object values are not yet supported by the codec runtime; "
50
+ "pass a reference ({'$ref': ...})."
51
+ )
52
+ carrier = carrier_of(prop["datatype"])
53
+ if carrier is None:
54
+ return RawScalar(_lexical(raw))
55
+ return TypedScalar(carrier, _lexical(raw))
56
+
57
+
58
+ def _statements(node: Dict[str, Any], schema: Dict[str, Any]) -> List[Statement]:
59
+ type_uri = node.get("$type")
60
+ if not type_uri:
61
+ raise ValueError("node is missing $type")
62
+ cls = schema["classes"].get(type_uri)
63
+ if cls is None:
64
+ raise ValueError(f"no schema for type {type_uri}")
65
+
66
+ statements: List[Statement] = [Statement(schema["typePredicate"], Reference(type_uri))]
67
+ for key, raw in node.items():
68
+ if key in ENVELOPE_KEYS or raw is None:
69
+ continue
70
+ prop = cls["props"].get(key)
71
+ if prop is None:
72
+ statements.append(Statement(key, RawScalar(_lexical(raw))))
73
+ continue
74
+ if isinstance(raw, list):
75
+ statements.append(Statement(prop["predicate"], KList([_value(prop, x) for x in raw])))
76
+ else:
77
+ statements.append(Statement(prop["predicate"], _value(prop, raw)))
78
+
79
+ extra = node.get("$extra")
80
+ if extra:
81
+ for predicate, raw in extra.items():
82
+ if raw is None:
83
+ continue
84
+ statements.append(Statement(predicate, RawScalar(_lexical(raw))))
85
+ return statements
86
+
87
+
88
+ def build_package(nodes: List[Dict[str, Any]], schema: Dict[str, Any], pkg: Dict[str, Any]) -> Package:
89
+ """Build the canonical input model: a subject per node + the synthesized
90
+ package-wrapper subject (raw label + ``Package`` type), exactly the subject
91
+ set ``kanonak hash`` produces for the equivalent authored package."""
92
+ subjects: List[Subject] = []
93
+ for node in nodes:
94
+ if not node.get("$id"):
95
+ raise ValueError("node is missing $id")
96
+ subjects.append(Subject(node["$id"], _statements(node, schema)))
97
+
98
+ pkg_uri = f"{pkg['publisher']}/{pkg['packageName']}@{pkg['version']}/{pkg['packageName']}"
99
+ pkg_statements: List[Statement] = []
100
+ if pkg.get("label") is not None:
101
+ pkg_statements.append(Statement(schema["labelPredicate"], RawScalar(pkg["label"])))
102
+ pkg_statements.append(Statement(schema["typePredicate"], Reference(schema["packageTypeUri"])))
103
+ subjects.append(Subject(pkg_uri, pkg_statements))
104
+ return Package(subjects)
105
+
106
+
107
+ def canonical_form(nodes: List[Dict[str, Any]], schema: Dict[str, Any], pkg: Dict[str, Any]) -> str:
108
+ """The canonical form (the ``{subjects:[...]}`` JSON) of a package from nodes."""
109
+ return _canonical_form(build_package(nodes, schema, pkg))
110
+
111
+
112
+ def content_hash(nodes: List[Dict[str, Any]], schema: Dict[str, Any], pkg: Dict[str, Any]) -> str:
113
+ """The ``sha256:`` content hash of a package from nodes — matches ``kanonak hash``."""
114
+ return _canonical_hash(build_package(nodes, schema, pkg))
115
+
116
+
117
+ def serialize(node: Dict[str, Any]) -> Dict[str, Any]:
118
+ """Serialize a typed node to its normalized-JSON wire form. ``$extra`` entries
119
+ ride as sibling fields after the modeled ones; a modeled field wins a name
120
+ collision (``[JsonExtensionData]`` semantics)."""
121
+ out: Dict[str, Any] = {}
122
+ for key, value in node.items():
123
+ if key == "$extra" or value is None:
124
+ continue
125
+ out[key] = value
126
+ extra = node.get("$extra")
127
+ if extra:
128
+ for key, value in extra.items():
129
+ if value is not None and key not in out:
130
+ out[key] = value
131
+ return out
132
+
133
+
134
+ def deserialize(json_obj: Dict[str, Any], schema: Dict[str, Any]) -> Dict[str, Any]:
135
+ """Parse normalized JSON into a typed node. ``$``-envelope keys and fields
136
+ modeled on the node's ``$type`` stay top-level; every other key is collected
137
+ into ``$extra`` so a strongly-typed consumer round-trips it losslessly."""
138
+ type_uri = json_obj.get("$type")
139
+ if not isinstance(type_uri, str):
140
+ raise ValueError("Cannot deserialize: missing string $type")
141
+ cls = schema["classes"].get(type_uri)
142
+ if cls is None:
143
+ raise ValueError(f"Cannot deserialize: no schema for type {type_uri}")
144
+
145
+ node: Dict[str, Any] = {"$type": type_uri}
146
+ extra: Dict[str, Any] = {}
147
+ for key, value in json_obj.items():
148
+ if key == "$type":
149
+ continue
150
+ if key.startswith("$") or key in cls["props"]:
151
+ node[key] = value
152
+ else:
153
+ extra[key] = value
154
+ if extra:
155
+ node["$extra"] = extra
156
+ return node
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: kanonak-codec
3
+ Version: 0.1.0
4
+ Summary: Kanonak codec runtime (Python port): build the canonical input model from typed nodes + an embedded schema, content-address via kanonak-canonical, and (de)serialize the normalized-JSON wire form. Referenced by generated typed SDKs.
5
+ Author: Kanonak Maintainers
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://kanonak.org
8
+ Project-URL: Repository, https://github.com/kanonak-protocol/runtime
9
+ Project-URL: Issues, https://github.com/kanonak-protocol/runtime/issues
10
+ Requires-Python: >=3.8
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: kanonak-canonical>=0.1.0
13
+
14
+ # kanonak-codec
15
+
16
+ The generic Kanonak codec runtime (Python): builds the canonical input model from typed nodes +
17
+ an embedded schema, content-addresses it via `kanonak-canonical`, and (de)serializes the
18
+ normalized-JSON wire form. Referenced by generated typed SDKs.
19
+
20
+ Source & issues: https://github.com/kanonak-protocol/runtime
@@ -0,0 +1,8 @@
1
+ README.md
2
+ pyproject.toml
3
+ kanonak_codec/__init__.py
4
+ kanonak_codec.egg-info/PKG-INFO
5
+ kanonak_codec.egg-info/SOURCES.txt
6
+ kanonak_codec.egg-info/dependency_links.txt
7
+ kanonak_codec.egg-info/requires.txt
8
+ kanonak_codec.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ kanonak-canonical>=0.1.0
@@ -0,0 +1 @@
1
+ kanonak_codec
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kanonak-codec"
7
+ version = "0.1.0"
8
+ readme = "README.md"
9
+ description = "Kanonak codec runtime (Python port): build the canonical input model from typed nodes + an embedded schema, content-address via kanonak-canonical, and (de)serialize the normalized-JSON wire form. Referenced by generated typed SDKs."
10
+ requires-python = ">=3.8"
11
+ license = "Apache-2.0"
12
+ authors = [{ name = "Kanonak Maintainers" }]
13
+ dependencies = ["kanonak-canonical>=0.1.0"]
14
+
15
+ [project.urls]
16
+ Homepage = "https://kanonak.org"
17
+ Repository = "https://github.com/kanonak-protocol/runtime"
18
+ Issues = "https://github.com/kanonak-protocol/runtime/issues"
19
+
20
+ [tool.setuptools]
21
+ packages = ["kanonak_codec"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+