From 133e6639b30a63131c346abae96c3994d7fb2d8d Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Thu, 18 Dec 2025 09:05:58 -0100 Subject: [PATCH] match_object() - Do not try to serialize identity functions --- localstack_snapshot/snapshots/prototype.py | 13 ++++++++++--- tests/test_snapshots.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/localstack_snapshot/snapshots/prototype.py b/localstack_snapshot/snapshots/prototype.py index 81d95a4..a32132d 100644 --- a/localstack_snapshot/snapshots/prototype.py +++ b/localstack_snapshot/snapshots/prototype.py @@ -183,14 +183,21 @@ def _convert_object_to_dict(obj_): return obj_.value elif hasattr(obj_, "__dict__"): # This is an object - let's try to convert it to a dictionary - # A naive approach would be to use the '__dict__' object directly, but that only lists the attributes + # A naive approach would be to use the '__dict__' object directly, but that only lists the attributes. # In order to also serialize the properties, we use the __dir__() method # Filtering by everything that is not a method gives us both attributes and properties - # We also (still) skip private attributes/properties, so everything that starts with an underscore + # We also (still) skip private attributes/properties, so everything that starts with an underscore. return { k: _convert_object_to_dict(getattr(obj_, k)) for k in obj_.__dir__() - if not k.startswith("_") and type(getattr(obj_, k, "")).__name__ != "method" + if ( + # Skip private attributes + not k.startswith("_") + # Skip everything that's not a method + and type(getattr(obj_, k, "")).__name__ != "method" + # Skip everything that refers to itself (identity functions), as that leads to recursion + and getattr(obj_, k) != obj_ + ) } return obj_ diff --git a/tests/test_snapshots.py b/tests/test_snapshots.py index 09d1268..2ef62f2 100644 --- a/tests/test_snapshots.py +++ b/tests/test_snapshots.py @@ -137,6 +137,21 @@ def __init__(self, name): sm.match_object("key_a", CustomObject(name="myname")) sm._assert_all() + def test_match_object_with_identity_function(self): + class CustomObject: + def __init__(self, name): + self.name = name + + @property + def me_myself_and_i(self): + # This would lead to a RecursionError, so we cannot snapshot this method + return self + + sm = SnapshotSession(scope_key="A", verify=True, base_file_path="", update=False) + sm.recorded_state = {"key_a": {"name": "myname"}} + sm.match_object("key_a", CustomObject(name="myname")) + sm._assert_all() + def test_match_object_change(self): class CustomObject: def __init__(self, name):