@@ -136,6 +136,15 @@ def canonical_json(value: Any) -> bytes:
136136 are emitted verbatim. This is the byte string that the hook discriminator
137137 hashes and that the manifest loader uses to detect byte-identical duplicate
138138 hook entries.
139+
140+ Only JSON-compatible scalars (``None``, ``str``, ``bool``, ``int``,
141+ ``float``), mappings, and lists/tuples are accepted. ``yaml.safe_load()``
142+ can produce values outside that set — for example an unquoted date is
143+ parsed as ``datetime.date`` — and passing one through here would otherwise
144+ surface as a raw, uncaught ``TypeError`` from ``json.dumps()`` deep inside
145+ this function. Callers get a clean ``IdentifierComponentError`` instead, so
146+ a manifest with such a value can be rejected the same way as any other
147+ invalid declared field.
139148 """
140149 normalized = _normalize_for_canonical_json (value )
141150 return json .dumps (
@@ -147,11 +156,16 @@ def canonical_json(value: Any) -> bytes:
147156
148157
149158def _normalize_for_canonical_json (value : Any ) -> Any :
159+ if value is None or isinstance (value , (str , bool , int , float )):
160+ return value
150161 if isinstance (value , Mapping ):
151162 return {str (k ): _normalize_for_canonical_json (v ) for k , v in value .items ()}
152163 if isinstance (value , (list , tuple )):
153164 return [_normalize_for_canonical_json (v ) for v in value ]
154- return value
165+ raise IdentifierComponentError (
166+ f"Value of type { type (value ).__name__ !r} is not JSON-compatible and "
167+ "cannot be canonicalized"
168+ )
155169
156170
157171def _has_hook_sibling_collision (
0 commit comments