Repository: fabiocaccamo/python-benedict

Issue:
Allow keypaths to preserve order
Given a benedict dictionary d, invoking d.keypaths() will return all paths in a sorted order.

There are cases where this is an undesired behavior and is caused by `kps.sort()` in benedict.core.keypaths.py.

I would propose a new parameter "sorted" to keypaths that allows to disable this line of code.

Hints from issue comments:
@fabiocaccamo: @mucmch I agree with adding a "sorted" parameter to keypaths method (defaults to `True` for backward compatibility). 

Do you want to submit a PR with a couple tests?@mucmch: Did a simple implementation, but do not manage to commit to Github. I attached a patch file.

[keypaths_order.patch](https://github.com/fabiocaccamo/python-benedict/files/15162276/keypaths_order.patch)
@kotfu: New user of benedict here, and I could benefit from this "sorted" parameter. I'll submit a PR shortly.@fabiocaccamo: @kotfu thank you very much for the PR!
@mucmch parameter available in `0.34.1`.

Solution patch:
diff --git a/README.md b/README.md
index bc10ba6a..6ab15bc9 100644
--- a/README.md
+++ b/README.md
@@ -458,7 +458,8 @@ items = d.items_sorted_by_values(reverse=False)
 ```python
 # Return a list of all keypaths in the dict.
 # If indexes is True, the output will include list values indexes.
-k = d.keypaths(indexes=False)
+# If sort is True, the resulting list will be sorted
+k = d.keypaths(indexes=False, sort=True)
 ```
 
 #### `match`
diff --git a/benedict/core/keypaths.py b/benedict/core/keypaths.py
index 1785f926..696689f9 100644
--- a/benedict/core/keypaths.py
+++ b/benedict/core/keypaths.py
@@ -2,11 +2,12 @@
 from benedict.utils import type_util
 
 
-def keypaths(d, separator=".", indexes=False):
+def keypaths(d, separator=".", indexes=False, sort=True):
     separator = separator or "."
     if not type_util.is_string(separator):
         raise ValueError("separator argument must be a (non-empty) string.")
     kls = keylists(d, indexes=indexes)
     kps = [separator.join([f"{key}" for key in kl]) for kl in kls]
-    kps.sort()
+    if sort:
+        kps.sort()
     return kps
diff --git a/benedict/dicts/__init__.py b/benedict/dicts/__init__.py
index e3f79280..ecd57169 100644
--- a/benedict/dicts/__init__.py
+++ b/benedict/dicts/__init__.py
@@ -196,12 +196,14 @@ def items_sorted_by_values(self, reverse=False):
         """
         return _items_sorted_by_values(self, reverse=reverse)
 
-    def keypaths(self, indexes=False):
+    def keypaths(self, indexes=False, sort=True):
         """
         Return a list of all keypaths in the dict.
         If indexes is True, the output will include list values indexes.
         """
-        return _keypaths(self, separator=self._keypath_separator, indexes=indexes)
+        return _keypaths(
+            self, separator=self._keypath_separator, indexes=indexes, sort=sort
+        )
 
     def match(self, pattern, indexes=True):
         """


Test patch:
diff --git a/tests/core/test_keypaths.py b/tests/core/test_keypaths.py
index 3f72dd04..968d5063 100644
--- a/tests/core/test_keypaths.py
+++ b/tests/core/test_keypaths.py
@@ -35,6 +35,33 @@ def test_keypaths(self):
         ]
         self.assertEqual(o, r)
 
+    def test_keypaths_unsorted(self):
+        i = {
+            "b": {
+                "c": {
+                    "y": 3,
+                    "x": 2,
+                },
+                "d": {
+                    "x": 4,
+                    "y": 5,
+                },
+            },
+            "a": 1,
+        }
+        o = _keypaths(i, sort=False)
+        r = [
+            "b",
+            "b.c",
+            "b.c.y",
+            "b.c.x",
+            "b.d",
+            "b.d.x",
+            "b.d.y",
+            "a",
+        ]
+        self.assertEqual(o, r)
+
     def test_keypaths_with_custom_separator(self):
         i = {
             "a": 1,