From fb78e96994eec2383738e8666cb0cb094bc53095 Mon Sep 17 00:00:00 2001
From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
Date: Wed, 11 Dec 2024 16:57:47 +0000
Subject: [PATCH 1/2] First Pass

---
 .../graph_traversal/default_traversal.py      | 28 +++++++++++++++++++
 .../objects/graph_traversal/traversal.py      | 15 +++++++++-
 2 files changed, 42 insertions(+), 1 deletion(-)
 create mode 100644 src/specklepy/objects/graph_traversal/default_traversal.py

diff --git a/src/specklepy/objects/graph_traversal/default_traversal.py b/src/specklepy/objects/graph_traversal/default_traversal.py
new file mode 100644
index 00000000..8c02dfb0
--- /dev/null
+++ b/src/specklepy/objects/graph_traversal/default_traversal.py
@@ -0,0 +1,28 @@
+from specklepy.objects.base import Base
+from specklepy.objects.graph_traversal.traversal import GraphTraversal, TraversalRule
+
+DISPLAY_VALUE_PROPERTY_ALIASES = {"displayValue", "@displayValue"}
+ELEMENTS_PROPERTY_ALIASES = {"elements", "@elements"}
+
+
+def has_display_value(x: Base):
+    return any(hasattr(x, alias) for alias in DISPLAY_VALUE_PROPERTY_ALIASES)
+
+
+def create_default_traversal_function() -> GraphTraversal:
+    """
+    Traversal func for traversing the root object of a Speckle Model
+    """
+
+    convertible_rule = TraversalRule(
+        [lambda b: b.speckle_type != "Base", has_display_value],
+        lambda _: ELEMENTS_PROPERTY_ALIASES,
+    )
+
+    default_rule = TraversalRule(
+        [lambda _: True],
+        lambda o: o.get_member_names(),  # NOTE: Unlike the C# implementation, this does not ignore Obsolete members
+        False,
+    )
+
+    return GraphTraversal([convertible_rule, default_rule])
diff --git a/src/specklepy/objects/graph_traversal/traversal.py b/src/specklepy/objects/graph_traversal/traversal.py
index b32d0536..6ab16a7a 100644
--- a/src/specklepy/objects/graph_traversal/traversal.py
+++ b/src/specklepy/objects/graph_traversal/traversal.py
@@ -7,6 +7,11 @@
 
 
 class ITraversalRule(Protocol):
+
+    @property
+    def should_return(self) -> bool:
+        pass
+
     def get_members_to_traverse(self, o: Base) -> Set[str]:
         """Get the members to traverse."""
         pass
@@ -50,10 +55,13 @@ def traverse(self, root: Base) -> Iterator[TraversalContext]:
 
         while len(stack) > 0:
             head = stack.pop()
-            yield head
 
             current = head.current
             active_rule = self._get_active_rule_or_default_rule(current)
+
+            if active_rule.should_return:
+                yield head
+
             members_to_traverse = active_rule.get_members_to_traverse(current)
             for child_prop in members_to_traverse:
                 try:
@@ -114,6 +122,11 @@ def _get_active_rule(self, o: Base) -> Optional[ITraversalRule]:
 class TraversalRule:
     _conditions: Collection[Callable[[Base], bool]]
     _members_to_traverse: Callable[[Base], Iterable[str]]
+    _should_return_to_output: bool = True
+
+    @property
+    def should_return(self) -> bool:
+        return self._should_return_to_output
 
     def get_members_to_traverse(self, o: Base) -> Set[str]:
         return set(self._members_to_traverse(o))

From 17fb320f20da6ccb329c1f3f76144764d6cea680 Mon Sep 17 00:00:00 2001
From: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
Date: Fri, 24 Jan 2025 15:05:47 +0000
Subject: [PATCH 2/2] Updated traversal

---
 .../objects/graph_traversal/default_traversal.py          | 3 ++-
 src/specklepy/objects/graph_traversal/traversal.py        | 1 -
 tests/unit/test_traverse_value.py                         | 8 +++-----
 3 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/src/specklepy/objects/graph_traversal/default_traversal.py b/src/specklepy/objects/graph_traversal/default_traversal.py
index 8c02dfb0..7729b0ed 100644
--- a/src/specklepy/objects/graph_traversal/default_traversal.py
+++ b/src/specklepy/objects/graph_traversal/default_traversal.py
@@ -21,7 +21,8 @@ def create_default_traversal_function() -> GraphTraversal:
 
     default_rule = TraversalRule(
         [lambda _: True],
-        lambda o: o.get_member_names(),  # NOTE: Unlike the C# implementation, this does not ignore Obsolete members
+        # NOTE: Unlike the C# implementation, this does not ignore Obsolete members
+        lambda o: o.get_member_names(),
         False,
     )
 
diff --git a/src/specklepy/objects/graph_traversal/traversal.py b/src/specklepy/objects/graph_traversal/traversal.py
index 6cee4159..1c980f03 100644
--- a/src/specklepy/objects/graph_traversal/traversal.py
+++ b/src/specklepy/objects/graph_traversal/traversal.py
@@ -7,7 +7,6 @@
 
 
 class ITraversalRule(Protocol):
-
     @property
     def should_return(self) -> bool:
         pass
diff --git a/tests/unit/test_traverse_value.py b/tests/unit/test_traverse_value.py
index 47e8c75b..e6932c11 100644
--- a/tests/unit/test_traverse_value.py
+++ b/tests/unit/test_traverse_value.py
@@ -1,5 +1,5 @@
-from typing import List
 from dataclasses import dataclass
+from typing import List
 
 from specklepy.objects.base import Base
 from specklepy.serialization.base_object_serializer import BaseObjectSerializer
@@ -12,16 +12,14 @@ class FakeBase(Base):
 
 
 def test_traverse_value():
-    base = FakeBase(bar=1)
-    base.foo = [None]
+    base = FakeBase(bar=1, foo=["abcd"])
     serializer = BaseObjectSerializer()
     object_id, object_dict = serializer.traverse_base(base)
     assert object_dict == {
         "id": object_id,
         "speckle_type": "Tests.Unit.TestTraverseValue.FakeBase",
         "applicationId": None,
-        "foo": [None],
-        "units": None,
+        "foo": ["abcd"],
         "bar": 1,
         "totalChildrenCount": 0,
     }