From a9dcba476ebf8bc18f6d28c395d3198a83c1e9ff Mon Sep 17 00:00:00 2001 From: Agustin Borgna Date: Wed, 10 Jan 2024 12:09:42 +0000 Subject: [PATCH] feat: Add hugr-to-json encoding, and validate it --- guppy/hugr/hugr.py | 4 ++++ guppy/hugr/raw.py | 17 +++++++++++++-- tests/integration/conftest.py | 6 ++++++ tests/integration/util.py | 6 +++++- validator/Cargo.lock | 39 ++++++++++++++++++----------------- validator/Cargo.toml | 1 + validator/src/lib.rs | 14 +++++++++++-- 7 files changed, 63 insertions(+), 24 deletions(-) diff --git a/guppy/hugr/hugr.py b/guppy/hugr/hugr.py index 980a1c63..33794a07 100644 --- a/guppy/hugr/hugr.py +++ b/guppy/hugr/hugr.py @@ -777,3 +777,7 @@ def to_raw(self) -> raw.RawHugr: def serialize(self) -> bytes: """Serialize this Hugr in binary format.""" return self.to_raw().packb() + + def serialize_json(self) -> str: + """Serialize this Hugr in JSON format.""" + return self.to_raw().to_json() diff --git a/guppy/hugr/raw.py b/guppy/hugr/raw.py index a3249a3c..fe1ca2a5 100644 --- a/guppy/hugr/raw.py +++ b/guppy/hugr/raw.py @@ -1,4 +1,6 @@ -from typing import Literal +from __future__ import annotations + +from typing import Any, Literal import ormsgpack from pydantic import BaseModel @@ -15,8 +17,19 @@ class RawHugr(BaseModel): edges: list[Edge] def packb(self) -> bytes: + """Encode the Hugr into msgpack.""" return ormsgpack.packb(self.dict(), option=ormsgpack.OPT_NON_STR_KEYS) + def to_json(self) -> str: + """Return a JSON representation of the Hugr.""" + return self.json() + @classmethod - def unpackb(cls, b: bytes) -> "RawHugr": + def unpackb(cls, b: bytes) -> RawHugr: + """Decode a msgpack-encoded Hugr.""" return cls(**ormsgpack.unpackb(b, option=ormsgpack.OPT_NON_STR_KEYS)) + + @classmethod + def load_json(cls, json: dict[Any]) -> RawHugr: + """Decode a JSON-encoded Hugr.""" + return cls(**json) diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 9841036a..9ed7e8e5 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -14,8 +14,14 @@ def export_test_cases_dir(request): @pytest.fixture() def validate(request, export_test_cases_dir): def validate_impl(hugr, name=None): + # Validate via the msgpack encoding bs = hugr.serialize() util.validate_bytes(bs) + + # Validate via the json encoding + js = hugr.serialize_json() + util.validate_json(js) + if export_test_cases_dir: file_name = f"{request.node.name}{f'_{name}' if name else ''}.msgpack" export_file = export_test_cases_dir / file_name diff --git a/tests/integration/util.py b/tests/integration/util.py index a99478e2..e9e6841d 100644 --- a/tests/integration/util.py +++ b/tests/integration/util.py @@ -2,7 +2,11 @@ def validate_bytes(hugr: bytes): - validator.validate(hugr) + validator.validate_bytes(hugr) + + +def validate_json(hugr: str): + validator.validate_json(hugr) class Decorator: diff --git a/validator/Cargo.lock b/validator/Cargo.lock index 726717e9..5ce8a47a 100644 --- a/validator/Cargo.lock +++ b/validator/Cargo.lock @@ -88,7 +88,7 @@ checksum = "b4801d755ab05b6e25cbbf1afc342993aafa00e572409f9ee21633a19e609d9f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.48", ] [[package]] @@ -99,7 +99,7 @@ checksum = "4e018fccbeeb50ff26562ece792ed06659b9c2dae79ece77c4456bb10d9bf79b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.48", ] [[package]] @@ -168,7 +168,7 @@ checksum = "e77ac7b51b8e6313251737fcef4b1c01a2ea102bde68415b62c0ee9268fec357" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.48", ] [[package]] @@ -373,9 +373,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -472,9 +472,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.28" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -580,29 +580,29 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.99" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -653,7 +653,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.18", + "syn 2.0.48", ] [[package]] @@ -669,9 +669,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.18" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -707,7 +707,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.48", ] [[package]] @@ -731,7 +731,7 @@ checksum = "2c3e1c30cedd24fc597f7d37a721efdbdc2b1acae012c1ef1218f4c7c2c0f3e7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn 2.0.48", ] [[package]] @@ -766,6 +766,7 @@ dependencies = [ "pyo3", "quantinuum-hugr", "rmp-serde", + "serde_json", ] [[package]] diff --git a/validator/Cargo.toml b/validator/Cargo.toml index 7a6ec6f0..d55cd28a 100644 --- a/validator/Cargo.toml +++ b/validator/Cargo.toml @@ -13,3 +13,4 @@ pyo3 = "0.19.0" quantinuum-hugr = { git = "https://github.com/CQCL/hugr.git", rev = "d07406c13b03a93014ae08a70bb789b25e0b31eb" } rmp-serde = "1.1.1" lazy_static = "1.4.0" +serde_json = "1.0.111" diff --git a/validator/src/lib.rs b/validator/src/lib.rs index da20fd76..a809cd44 100644 --- a/validator/src/lib.rs +++ b/validator/src/lib.rs @@ -16,15 +16,25 @@ lazy_static! { .unwrap(); } +/// Validate a msgpack-encoded Hugr #[pyfunction] -fn validate(hugr: Vec) -> PyResult<()> { +fn validate_bytes(hugr: Vec) -> PyResult<()> { let hg: hugr::Hugr = rmp_serde::from_slice(&hugr).unwrap(); hg.validate(®ISTRY).unwrap(); Ok(()) } +/// Validate a json-encoded Hugr +#[pyfunction] +fn validate_json(hugr: String) -> PyResult<()> { + let hg: hugr::Hugr = serde_json::from_str(&hugr).unwrap(); + hg.validate(®ISTRY).unwrap(); + Ok(()) +} + #[pymodule] fn validator(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(validate, m)?)?; + m.add_function(wrap_pyfunction!(validate_bytes, m)?)?; + m.add_function(wrap_pyfunction!(validate_json, m)?)?; Ok(()) }