Skip to content

feat(hugr-py): Define typed Metadata protocol#2765

Merged
aborgna-q merged 12 commits intomainfrom
ab/py-metadata
Feb 12, 2026
Merged

feat(hugr-py): Define typed Metadata protocol#2765
aborgna-q merged 12 commits intomainfrom
ab/py-metadata

Conversation

@aborgna-q
Copy link
Copy Markdown
Collaborator

@aborgna-q aborgna-q commented Dec 17, 2025

Adds a Metadata protocol similar to the one in the rust side, and exports the rust key definitions.

Depends on #2764

Closes #2757

@aborgna-q aborgna-q requested a review from a team as a code owner December 17, 2025 14:46
@aborgna-q aborgna-q requested review from acl-cqc and removed request for a team December 17, 2025 14:46
@codecov
Copy link
Copy Markdown

codecov bot commented Dec 17, 2025

Codecov Report

❌ Patch coverage is 89.11565% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.70%. Comparing base (48d76d4) to head (0fa117f).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
hugr-py/src/hugr/envelope.py 68.88% 14 Missing ⚠️
hugr-py/src/hugr/metadata.py 97.50% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2765      +/-   ##
==========================================
+ Coverage   83.67%   83.70%   +0.02%     
==========================================
  Files         266      267       +1     
  Lines       53333    53461     +128     
  Branches    47587    47587              
==========================================
+ Hits        44629    44748     +119     
- Misses       6297     6306       +9     
  Partials     2407     2407              
Flag Coverage Δ
python 88.47% <89.11%> (+0.10%) ⬆️
rust 83.11% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from ab/pyo3-modules to main December 17, 2025 17:04
Copy link
Copy Markdown
Contributor

@maximilianruesch maximilianruesch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this will be a very useful addition! I have a bunch of suggested changes, some more nitty, some questions, some remarks for later.

Comment on lines +128 to +134
@classmethod
def to_json(cls, value: GeneratorDesc) -> dict[str, str]:
return value._to_json()

@classmethod
def from_json(cls, value: Any) -> GeneratorDesc:
return GeneratorDesc._from_json(value)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have a lot of "simple" metadata keys that do not require special validation (only calling the JSON serialize / deserialize of their wrapped type), we might want to think about creating something like a mixin for such cases to reduce boilerplate / verbosity. I do think we need a bit more data / keys to assess what the best solution for this is, so there is no need to refactor this right now.

Copy link
Copy Markdown
Collaborator Author

@aborgna-q aborgna-q Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

For native-type metadata (ints, strings, lists, and dicts) there's no need to override these methods. But yeah, it'd be nice to have a deserialize/_serialize`
I guess we could use pydantic for that? It's a bit annoying that we're starting from the already-loaded object rather than a json string.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not yet used pydantic, but it strikes me as very powerful. If we adopt it, we should assess whether there is considerable overhead to serialization / deserialization that is not ignorable in the context of hugr-py.

Comment on lines +148 to +160
@classmethod
def to_json(cls, value: list[ExtensionDesc]) -> list[dict[str, str]]:
return [e._to_json() for e in value]

@classmethod
def from_json(cls, value: Any) -> list[ExtensionDesc]:
if not isinstance(value, list):
msg = (
"Expected UsedExtensions metadata to be a list,"
+ f" but got {type(value)}"
)
raise TypeError(msg)
return [ExtensionDesc._from_json(e) for e in value]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the mixin suggestion, lists of wrapped types may be another example of a common pattern that could occur multiple times.

name: str
version: Version | None

def _to_json(self) -> dict[str, str]:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it useful to have a protocol for _to_json and _from_json? Also, should these be non-private since they are called from outside the class?

Copy link
Copy Markdown
Collaborator Author

@aborgna-q aborgna-q Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep the PR simple and focused on the metadata protocol for now.

We should probably look into serializable protocols for the value types. (does pydantic help here?), but I'll leave it for later.

The _to/_from_json shouldn't normally be called by users. I'd love to have a pub(crate) attribute here...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While its true that a user has no benefit to calling these methods, there is no harm either, since its either read-only or a constructing method, i.e. they cannot violate any contracts with using these methods. I am unsure as to what the policy of this repo is, i.e. whether a cleaner user interface or adhering to general Python guidelines has a higher priority.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the _ from these

Copy link
Copy Markdown
Contributor

@maximilianruesch maximilianruesch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A preliminary LGTM on the code, save for the last comment on _to_json visibility, and some failing coverage checks (which imo one should address in some way, since the code contains many paths).

@maximilianruesch
Copy link
Copy Markdown
Contributor

maximilianruesch commented Feb 11, 2026

Lettuce wait for CI 🥬 (it is done)

@aborgna-q aborgna-q removed the request for review from acl-cqc February 12, 2026 12:24
@aborgna-q aborgna-q added this pull request to the merge queue Feb 12, 2026
Merged via the queue into main with commit 4390230 Feb 12, 2026
30 checks passed
@aborgna-q aborgna-q deleted the ab/py-metadata branch February 12, 2026 12:47
This was referenced Feb 12, 2026
github-merge-queue bot pushed a commit that referenced this pull request Feb 20, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.15.4](hugr-py-v0.15.3...hugr-py-v0.15.4)
(2026-02-20)


### Features

* **hugr-py:** Define typed Metadata protocol
([#2765](#2765))
([4390230](4390230))


### Bug Fixes

* Add truncation options for node and edge labels in rendering
([#2885](#2885))
([25c625d](25c625d))
* used_extensions should include transitive requirements
([#2891](#2891))
([18e78e4](18e78e4))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: Agustín Borgna <[email protected]>
github-merge-queue bot pushed a commit that referenced this pull request Feb 20, 2026
## 🤖 New release

* `hugr-model`: 0.25.5 -> 0.25.6 (✓ API compatible changes)
* `hugr-core`: 0.25.5 -> 0.25.6 (✓ API compatible changes)
* `hugr-llvm`: 0.25.5 -> 0.25.6 (✓ API compatible changes)
* `hugr-passes`: 0.25.5 -> 0.25.6 (✓ API compatible changes)
* `hugr`: 0.25.5 -> 0.25.6 (✓ API compatible changes)
* `hugr-cli`: 0.25.5 -> 0.25.6 (✓ API compatible changes)
* `hugr-persistent`: 0.4.5 -> 0.4.6

<details><summary><i><b>Changelog</b></i></summary><p>

## `hugr-model`

<blockquote>

##
[0.25.6](hugr-model-v0.25.5...hugr-model-v0.25.6)
- 2026-02-20

### New Features

- Remove size limitation for binary envelopes
([#2880](#2880))
</blockquote>

## `hugr-core`

<blockquote>

##
[0.25.6](hugr-core-v0.25.5...hugr-core-v0.25.6)
- 2026-02-20

### Bug Fixes

- Canonicalize more
([#2839](#2839))
- used_extensions should include transitive requirements
([#2891](#2891))

### New Features

- Add s expression format to envelope formats
([#2864](#2864))
- added hash.rs, updated imports
([#2840](#2840))
- *(hugr-py)* Define typed Metadata protocol
([#2765](#2765))
- Add a `NodeTemplate::call_to_function` helper
([#2878](#2878))
- Remove size limitation for binary envelopes
([#2880](#2880))
</blockquote>

## `hugr-llvm`

<blockquote>

##
[0.25.6](hugr-llvm-v0.25.5...hugr-llvm-v0.25.6)
- 2026-02-20

### New Features

- Add error context when lowering hugrs to LLVM
([#2869](#2869))
</blockquote>

## `hugr-passes`

<blockquote>

##
[0.25.6](hugr-passes-v0.25.5...hugr-passes-v0.25.6)
- 2026-02-20

### Bug Fixes

- Panic on UntuplePass when nodes had order edges
([#2883](#2883))

### New Features

- added hash.rs, updated imports
([#2840](#2840))
- Add a `NodeTemplate::call_to_function` helper
([#2878](#2878))
</blockquote>

## `hugr`

<blockquote>

##
[0.25.6](hugr-v0.25.5...hugr-v0.25.6)
- 2026-02-20

### Bug Fixes

- Panic on UntuplePass when nodes had order edges
([#2883](#2883))
- Canonicalize more
([#2839](#2839))
- used_extensions should include transitive requirements
([#2891](#2891))

### New Features

- Add s expression format to envelope formats
([#2864](#2864))
- *(hugr-py)* Define typed Metadata protocol
([#2765](#2765))
- Add a `NodeTemplate::call_to_function` helper
([#2878](#2878))
- added hash.rs, updated imports
([#2840](#2840))
- Remove size limitation for binary envelopes
([#2880](#2880))
</blockquote>

## `hugr-cli`

<blockquote>

##
[0.25.6](hugr-cli-v0.25.5...hugr-cli-v0.25.6)
- 2026-02-20

### New Features

- Add s expression format to envelope formats
([#2864](#2864))
</blockquote>

## `hugr-persistent`

<blockquote>

##
[0.4.0](hugr-persistent-v0.3.4...hugr-persistent-v0.4.0)
- 2025-12-22

### New Features

- [**breaking**] Remove `RootCheckable`
([#2704](#2704))
- [**breaking**] Bump MSRV to Rust 1.89
([#2747](#2747))
- [**breaking**] Type-safe access for node metadata
([#2755](#2755))

### Refactor

- [**breaking**] Remove multiple deprecated definitions
([#2751](#2751))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).
@hugrbot hugrbot mentioned this pull request Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export core metadata keys in hugr-py

3 participants