55from dataclasses import dataclass
66from typing import TYPE_CHECKING , Any , ClassVar , Protocol , TypeVar , overload
77
8+ import hugr ._hugr .metadata as rust_metadata
9+ from hugr .envelope import ExtensionDesc , GeneratorDesc
10+
811if TYPE_CHECKING :
912 from collections .abc import Iterable , Iterator
1013
11- MetaCovariant = TypeVar ("MetaCovariant" , covariant = True )
1214Meta = TypeVar ("Meta" )
1315
1416
15- class Metadata (Protocol [MetaCovariant ]):
16- """Metadata for a HUGR node."""
17+ class Metadata (Protocol [Meta ]):
18+ """Metadata for a HUGR node.
19+
20+ This is a protocol for metadata entries that defines a unique key to
21+ identify the entry, and the type of the value.
22+
23+ Values in a hugr are encoded using json. When the value type is not a
24+ primitive type, `to_json` and `from_json` must be implemented to serialize
25+ and deserialize the value.
26+
27+ Args:
28+ value: The value of the metadata.
29+ """
1730
1831 KEY : ClassVar [str ]
19- TYPE : ClassVar [type ]
32+
33+ @classmethod
34+ def to_json (cls , value : Meta ) -> Any :
35+ """Serialize the metadata value to a json value."""
36+ return value
37+
38+ @classmethod
39+ def from_json (cls , value : Any ) -> Meta :
40+ """Deserialize the metadata value from the stored json value."""
41+ return value
2042
2143
2244@dataclass
@@ -35,11 +57,19 @@ def __init__(self, metadata: dict[str, Any] | None = None) -> None:
3557 @overload
3658 def get (self , key : str , default : Any | None = None ) -> Any | None : ...
3759 @overload
38- def get (self , key : Metadata [Meta ], default : Meta | None = None ) -> Meta | None : ...
39- def get (self , key : str | Metadata [Meta ], default : Any | None = None ) -> Meta | None :
40- if not isinstance (key , str ):
41- key = key .KEY
42- return self ._dict .get (key , default )
60+ def get (
61+ self , key : type [Metadata [Meta ]], default : Meta | None = None
62+ ) -> Meta | None : ...
63+ def get (
64+ self , key : str | type [Metadata [Meta ]], default : Any | None = None
65+ ) -> Any | None :
66+ if isinstance (key , str ):
67+ return self ._dict .get (key , default )
68+ elif key .KEY in self ._dict :
69+ val = self ._dict [key .KEY ]
70+ return key .from_json (val )
71+ else :
72+ return None
4373
4474 def items (self ) -> Iterable [tuple [str , Any ]]:
4575 return self ._dict .items ()
@@ -50,34 +80,81 @@ def as_dict(self) -> dict[str, Any]:
5080 @overload
5181 def __getitem__ (self , key : str ) -> Any : ...
5282 @overload
53- def __getitem__ (self , key : Metadata [Meta ]) -> Meta : ...
54- def __getitem__ (self , key : str | Metadata [Meta ]) -> Any :
55- if not isinstance (key , str ):
56- key = key .KEY
57- return self ._dict [key ]
83+ def __getitem__ (self , key : type [Metadata [Meta ]]) -> Meta : ...
84+ def __getitem__ (self , key : str | type [Metadata [Meta ]]) -> Any :
85+ if isinstance (key , str ):
86+ return self ._dict [key ]
87+ else :
88+ val = self ._dict [key .KEY ]
89+ return key .from_json (val )
5890
5991 @overload
6092 def __setitem__ (self , key : str , value : Any ) -> None : ...
6193 @overload
62- def __setitem__ (self , key : Metadata [Meta ], value : Meta ) -> None : ...
63- def __setitem__ (self , key : str | Metadata [Meta ], value : Any ) -> None :
64- if not isinstance (key , str ):
65- if not isinstance (value , key .TYPE ):
66- error = f"Value for metadata key { key .KEY } must be of type { key .TYPE } "
67- raise TypeError (error )
68- key = key .KEY
69- self ._dict [key ] = value
94+ def __setitem__ (self , key : type [Metadata [Meta ]], value : Meta ) -> None : ...
95+ def __setitem__ (self , key : str | type [Metadata [Meta ]], value : Any ) -> None :
96+ if isinstance (key , str ):
97+ self ._dict [key ] = value
98+ else :
99+ json_value = key .to_json (value )
100+ self ._dict [key .KEY ] = json_value
70101
71102 def __iter__ (self ) -> Iterator [str ]:
72103 return iter (self ._dict )
73104
74105 def __len__ (self ) -> int :
75106 return len (self ._dict )
76107
77- def __contains__ (self , key : str | Metadata [Meta ]) -> bool :
108+ def __contains__ (self , key : str | type [ Metadata [Meta ] ]) -> bool :
78109 if not isinstance (key , str ):
79110 key = key .KEY
80111 return key in self ._dict
81112
82113 def __repr__ (self ) -> str :
83114 return f"NodeMetadata({ self ._dict } )"
115+
116+
117+ # --- Core metadata keys ---
118+
119+
120+ class HugrGenerator (Metadata [GeneratorDesc ]):
121+ """Metadata describing the generator that defined the HUGR module.
122+
123+ This value is only valid when set at the module root node.
124+ """
125+
126+ KEY = rust_metadata .HUGR_GENERATOR
127+
128+ @classmethod
129+ def to_json (cls , value : GeneratorDesc ) -> dict [str , str ]:
130+ return value ._to_json ()
131+
132+ @classmethod
133+ def from_json (cls , value : Any ) -> GeneratorDesc :
134+ return GeneratorDesc ._from_json (value )
135+
136+
137+ class HugrUsedExtensions (Metadata [list [ExtensionDesc ]]):
138+ """Metadata storing the list of extensions required to define the HUGR.
139+
140+ This list may contain additional extensions that are no longer present in
141+ the Hugr.
142+
143+ This value is only valid when set at the module root node.
144+ """
145+
146+ KEY = rust_metadata .HUGR_USED_EXTENSIONS
147+
148+ @classmethod
149+ def to_json (cls , value : list [ExtensionDesc ]) -> list [dict [str , str ]]:
150+ return [e ._to_json () for e in value ]
151+
152+ @classmethod
153+ def from_json (cls , value : Any ) -> list [ExtensionDesc ]:
154+ if not isinstance (value , list ):
155+ msg = (
156+ "Expected UsedExtensions metadata to be a list,"
157+ + f" but got { type (value )} "
158+ )
159+ raise TypeError (msg )
160+ return [ExtensionDesc ._from_json (e ) for e in value ]
0 commit comments