From 19bac6210aa8c495679bd7c783751e9cde744c61 Mon Sep 17 00:00:00 2001 From: doug-q <141026920+doug-q@users.noreply.github.com> Date: Tue, 14 May 2024 16:28:34 +0100 Subject: [PATCH] feat: Add serialization schema for metadata (#1038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #954. We support deserializing HUGRs that do not have a `metadata` field. We could optimise a little by not serializing a `metadata` field when all node metadata fields are null, but for now we don't bother. --------- Co-authored-by: Agustín Borgna <121866228+aborgna-q@users.noreply.github.com> --- hugr-py/src/hugr/serialization/serial_hugr.py | 7 ++--- hugr/src/hugr/serialize.rs | 27 +++++++++---------- .../schema/hugr_schema_strict_v1.json | 23 ++++++++++++++++ specification/schema/hugr_schema_v1.json | 23 ++++++++++++++++ 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/hugr-py/src/hugr/serialization/serial_hugr.py b/hugr-py/src/hugr/serialization/serial_hugr.py index a8c104937..2928f4c93 100644 --- a/hugr-py/src/hugr/serialization/serial_hugr.py +++ b/hugr-py/src/hugr/serialization/serial_hugr.py @@ -1,21 +1,22 @@ from typing import Any, Literal -from pydantic import BaseModel, Field, ConfigDict +from pydantic import Field, ConfigDict from .ops import NodeID, OpType, classes as ops_classes -from .tys import model_rebuild +from .tys import model_rebuild, ConfiguredBaseModel import hugr Port = tuple[NodeID, int | None] # (node, offset) Edge = tuple[Port, Port] -class SerialHugr(BaseModel): +class SerialHugr(ConfiguredBaseModel): """A serializable representation of a Hugr.""" version: Literal["v1"] = "v1" nodes: list[OpType] edges: list[Edge] + metadata: list[dict[str, Any] | None] | None = None encoder: str | None = Field( default=None, description="The name of the encoder used to generate the Hugr." ) diff --git a/hugr/src/hugr/serialize.rs b/hugr/src/hugr/serialize.rs index 89f76b043..ab979ec68 100644 --- a/hugr/src/hugr/serialize.rs +++ b/hugr/src/hugr/serialize.rs @@ -14,7 +14,7 @@ use portgraph::{Direction, LinkError, PortView}; use serde::{Deserialize, Deserializer, Serialize}; -use super::{HugrMut, HugrView}; +use super::{HugrMut, HugrView, NodeMetadataMap}; /// A wrapper over the available HUGR serialization formats. /// @@ -61,11 +61,8 @@ struct SerHugrV1 { /// for each edge: (src, src_offset, tgt, tgt_offset) edges: Vec<[(Node, Option); 2]>, /// for each node: (metadata) - // - // TODO: Update to Vec>> to more closely - // match the internal representation. #[serde(default)] - metadata: Vec, + metadata: Option>>, /// A metadata field with the package identifier that encoded the HUGR. #[serde(default)] encoder: Option, @@ -173,7 +170,7 @@ impl TryFrom<&Hugr> for SerHugrV1 { } let mut nodes = vec![None; hugr.node_count()]; - let mut metadata = vec![serde_json::Value::Null; hugr.node_count()]; + let mut metadata = vec![None; hugr.node_count()]; for n in hugr.nodes() { let parent = node_rekey[&hugr.get_parent(n).unwrap_or(n)]; let opt = hugr.get_nodetype(n); @@ -183,11 +180,7 @@ impl TryFrom<&Hugr> for SerHugrV1 { input_extensions: opt.input_extensions.clone(), op: opt.op.clone(), }); - let node_metadata = hugr.metadata.get(n.pg_index()).clone(); - metadata[new_node] = match node_metadata { - Some(m) => serde_json::Value::Object(m.clone()), - None => serde_json::Value::Null, - }; + metadata[new_node].clone_from(hugr.metadata.get(n.pg_index())); } let nodes = nodes .into_iter() @@ -222,7 +215,7 @@ impl TryFrom<&Hugr> for SerHugrV1 { Ok(Self { nodes, edges, - metadata, + metadata: Some(metadata), encoder, }) } @@ -263,9 +256,13 @@ impl TryFrom for Hugr { ); } - for (node, metadata) in metadata.into_iter().enumerate() { - let node = portgraph::NodeIndex::new(node); - hugr.metadata[node] = metadata.as_object().cloned(); + if let Some(metadata) = metadata { + for (node, metadata) in metadata.into_iter().enumerate() { + if let Some(metadata) = metadata { + let node = portgraph::NodeIndex::new(node); + hugr.metadata[node] = Some(metadata); + } + } } let unwrap_offset = |node: Node, offset, dir, hugr: &Hugr| -> Result { diff --git a/specification/schema/hugr_schema_strict_v1.json b/specification/schema/hugr_schema_strict_v1.json index 056ceeaf2..df387a43f 100644 --- a/specification/schema/hugr_schema_strict_v1.json +++ b/specification/schema/hugr_schema_strict_v1.json @@ -2259,6 +2259,7 @@ "type": "object" } }, + "additionalProperties": false, "description": "A serializable representation of a Hugr.", "properties": { "version": { @@ -2328,6 +2329,28 @@ "title": "Edges", "type": "array" }, + "metadata": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Metadata" + }, "encoder": { "anyOf": [ { diff --git a/specification/schema/hugr_schema_v1.json b/specification/schema/hugr_schema_v1.json index 27d58db73..6e8c177eb 100644 --- a/specification/schema/hugr_schema_v1.json +++ b/specification/schema/hugr_schema_v1.json @@ -2259,6 +2259,7 @@ "type": "object" } }, + "additionalProperties": true, "description": "A serializable representation of a Hugr.", "properties": { "version": { @@ -2328,6 +2329,28 @@ "title": "Edges", "type": "array" }, + "metadata": { + "anyOf": [ + { + "items": { + "anyOf": [ + { + "type": "object" + }, + { + "type": "null" + } + ] + }, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Metadata" + }, "encoder": { "anyOf": [ {