From 604f2f685b3249911f853216c17e279f7fcf4b12 Mon Sep 17 00:00:00 2001 From: acul71 Date: Fri, 2 Oct 2026 04:31:47 -0400 Subject: [PATCH] Add MultihashSet Visit and ForEach for go-multihash parity. Co-authored-by: Cursor --- multihash/multihash.py | 43 ++++++++++++++++++++++++++++++++++++ newsfragments/51.feature.rst | 1 + tests/test_new_features.py | 21 ++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 newsfragments/51.feature.rst diff --git a/multihash/multihash.py b/multihash/multihash.py index a6ec3b6..d07e39c 100644 --- a/multihash/multihash.py +++ b/multihash/multihash.py @@ -425,6 +425,49 @@ def All(self) -> list[Multihash]: """ return list(self._set) + def Visit(self, mh: Multihash) -> bool: + """Add mh if not present. Return True if it was added (Go-style API). + + Args: + mh: Multihash object to visit/add + + Returns: + True if mh was newly added, False if it was already present + + Raises: + TypeError: If mh is not a Multihash object + """ + if not isinstance(mh, Multihash): + raise TypeError(f"MultihashSet can only contain Multihash objects, got {type(mh)}") + if mh in self._set: + return False + self._set.add(mh) + return True + + def visit(self, mh: Multihash) -> bool: + """Add mh if not present. Return True if it was added (Python-style API).""" + return self.Visit(mh) + + def ForEach(self, func): + """Call ``func(mh)`` for each Multihash. Stop and return the error if raised. + + Args: + func: Callable invoked with each Multihash in the set + + Returns: + ``None`` on success, or the exception instance if ``func`` raises + """ + for mh in self._set: + try: + func(mh) + except Exception as e: + return e + return None + + def for_each(self, func): + """Call ``func(mh)`` for each Multihash (Python-style API).""" + return self.ForEach(func) + def __len__(self) -> int: """Return the number of Multihash objects in the set. diff --git a/newsfragments/51.feature.rst b/newsfragments/51.feature.rst new file mode 100644 index 0000000..7da88f4 --- /dev/null +++ b/newsfragments/51.feature.rst @@ -0,0 +1 @@ +Add ``Visit``/``visit`` and ``ForEach``/``for_each`` methods to :class:`~multihash.MultihashSet` for go-multihash parity. diff --git a/tests/test_new_features.py b/tests/test_new_features.py index 7ed8cfd..2b081d8 100644 --- a/tests/test_new_features.py +++ b/tests/test_new_features.py @@ -550,6 +550,27 @@ def test_multihash_set_go_api(self): mh_set.Remove(mh1) assert mh_set.Has(mh1) is False + def test_visit(self): + """Test Visit/visit add-if-absent semantics.""" + mh_set = MultihashSet() + mh1 = sum(b"file1", Func.sha2_256) + assert mh_set.visit(mh1) is True + assert mh_set.Visit(mh1) is False + assert len(mh_set) == 1 + + def test_for_each(self): + """Test ForEach/for_each iteration and error propagation.""" + mh1 = sum(b"a", Func.sha2_256) + mh2 = sum(b"b", Func.sha2_256) + mh_set = MultihashSet([mh1, mh2]) + collected = [] + assert mh_set.for_each(lambda mh: collected.append(mh)) is None + assert len(collected) == 2 + + err = mh_set.ForEach(lambda mh: (_ for _ in ()).throw(ValueError("stop"))) + assert isinstance(err, ValueError) + assert str(err) == "stop" + def test_multihash_set_clear(self): """Test clearing MultihashSet.""" mh1 = sum(b"file1", Func.sha2_256)