Repository: omry/omegaconf

Issue:
Do not sort keys by default in pretty(), and add sort_keys override.

Comments:

Patch:
diff --git a/docs/notebook/Tutorial.ipynb b/docs/notebook/Tutorial.ipynb
index b1d6a87..e038afe 100644
--- a/docs/notebook/Tutorial.ipynb
+++ b/docs/notebook/Tutorial.ipynb
@@ -131,11 +131,11 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
+      "server:\n",
+      "  port: 80\n",
       "log:\n",
       "  file: ???\n",
       "  rotation: 3600\n",
-      "server:\n",
-      "  port: 80\n",
       "users:\n",
       "- user1\n",
       "- user2\n",
@@ -248,10 +248,10 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "log:\n",
-      "  file: log2.txt\n",
       "server:\n",
       "  port: 82\n",
+      "log:\n",
+      "  file: log2.txt\n",
       "\n"
      ]
     }
@@ -285,11 +285,11 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
+      "server:\n",
+      "  port: 80\n",
       "log:\n",
       "  file: ???\n",
       "  rotation: 3600\n",
-      "server:\n",
-      "  port: 80\n",
       "users:\n",
       "- user1\n",
       "- user2\n",
@@ -546,12 +546,12 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "client:\n",
-      "  server_port: ${server.port}\n",
-      "  url: http://${server.host}:${server.port}/\n",
       "server:\n",
       "  host: localhost\n",
       "  port: 80\n",
+      "client:\n",
+      "  url: http://${server.host}:${server.port}/\n",
+      "  server_port: ${server.port}\n",
       "\n"
      ]
     }
@@ -602,12 +602,12 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "client:\n",
-      "  server_port: 80\n",
-      "  url: http://localhost:80/\n",
       "server:\n",
       "  host: localhost\n",
       "  port: 80\n",
+      "client:\n",
+      "  url: http://localhost:80/\n",
+      "  server_port: 80\n",
       "\n"
      ]
     }
@@ -659,8 +659,8 @@
      "output_type": "stream",
      "text": [
       "user:\n",
-      "  home: /home/${env:USER}\n",
       "  name: ${env:USER}\n",
+      "  home: /home/${env:USER}\n",
       "\n"
      ]
     }
@@ -684,8 +684,8 @@
      "output_type": "stream",
      "text": [
       "user:\n",
-      "  home: /home/omry\n",
       "  name: omry\n",
+      "  home: /home/omry\n",
       "\n"
      ]
     }
@@ -817,13 +817,13 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "log:\n",
-      "  file: log.txt\n",
       "server:\n",
       "  port: 82\n",
       "users:\n",
       "- user1\n",
       "- user2\n",
+      "log:\n",
+      "  file: log.txt\n",
       "\n"
      ]
     }
@@ -859,7 +859,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.5"
+   "version": "3.6.9"
   },
   "pycharm": {
    "stem_cell": {
diff --git a/docs/source/structured_config.rst b/docs/source/structured_config.rst
index f62d46a..6fb03b1 100644
--- a/docs/source/structured_config.rst
+++ b/docs/source/structured_config.rst
@@ -67,11 +67,11 @@ fields during construction.
     >>> conf3 = OmegaConf.create(SimpleTypes(num=20,
     ...                                      height=Height.TALL))
     >>> print(conf3.pretty())
-    description: text
-    height: Height.TALL
-    is_awesome: true
     num: 20
     pi: 3.1415
+    is_awesome: true
+    height: Height.TALL
+    description: text
     <BLANKLINE>
 
 The resulting object is a regular OmegaConf object, except it will utilize the type information in the input class/object
@@ -177,13 +177,13 @@ Structured configs can be nested.
 
     >>> conf : Group = OmegaConf.structured(Group)
     >>> print(conf.pretty())
+    name: ???
     admin:
-      height: ???
       name: ???
+      height: ???
     manager:
-      height: Height.TALL
       name: manager
-    name: ???
+      height: Height.TALL
     <BLANKLINE>
 
 OmegaConf will validate that assignment of nested objects is of the correct type:
diff --git a/docs/source/usage.rst b/docs/source/usage.rst
index 951c5e0..722f340 100644
--- a/docs/source/usage.rst
+++ b/docs/source/usage.rst
@@ -69,11 +69,11 @@ From a yaml file
     >>> conf = OmegaConf.load('source/example.yaml')
     >>> # Output is identical to the yaml file
     >>> print(conf.pretty())
+    server:
+      port: 80
     log:
       file: ???
       rotation: 3600
-    server:
-      port: 80
     users:
     - user1
     - user2
@@ -131,10 +131,10 @@ To parse the content of sys.arg:
     >>> sys.argv = ['your-program.py', 'server.port=82', 'log.file=log2.txt']
     >>> conf = OmegaConf.from_cli()
     >>> print(conf.pretty())
-    log:
-      file: log2.txt
     server:
       port: 82
+    log:
+      file: log2.txt
     <BLANKLINE>
 
 From structured config
@@ -154,8 +154,8 @@ See :doc:`structured_config` for more details, or keep reading for a minimal exa
     >>> # For strict typing purposes, prefer OmegaConf.structured() when creating structured configs
     >>> conf = OmegaConf.structured(MyConfig)
     >>> print(conf.pretty())
-    host: localhost
     port: 80
+    host: localhost
     <BLANKLINE>
 
 You can use an object to initialize the config as well:
@@ -164,8 +164,8 @@ You can use an object to initialize the config as well:
 
     >>> conf = OmegaConf.structured(MyConfig(port=443))
     >>> print(conf.pretty())
-    host: localhost
     port: 443
+    host: localhost
     <BLANKLINE>
 
 OmegaConf objects constructed from Structured classes offers runtime type safety:
@@ -385,13 +385,13 @@ Note how the port changes to 82, and how the users lists are combined.
     >>> # Merge with cli arguments
     >>> conf.merge_with_cli()
     >>> print(conf.pretty())
-    log:
-      file: log.txt
     server:
       port: 82
     users:
     - user1
     - user2
+    log:
+      file: log.txt
     <BLANKLINE>
 
 Configuration flags
diff --git a/news/161.feature b/news/161.feature
new file mode 100644
index 0000000..504ecd1
--- /dev/null
+++ b/news/161.feature
@@ -0,0 +1,1 @@
+Container.pretty() now preserves insertion order by default. override with sort_keys=True
diff --git a/news/161.removal b/news/161.removal
new file mode 100644
index 0000000..738624c
--- /dev/null
+++ b/news/161.removal
@@ -0,0 +1,1 @@
+Container.pretty() behavior change: sorted keys -> unsorted keys by default. override with sort_keys=True.
\ No newline at end of file
diff --git a/omegaconf/base.py b/omegaconf/base.py
index 507fc6b..d409579 100644
--- a/omegaconf/base.py
+++ b/omegaconf/base.py
@@ -61,7 +61,7 @@ class Container(Node):
     """
 
     @abstractmethod
-    def pretty(self, resolve: bool = False) -> str:
+    def pretty(self, resolve: bool = False, sort_keys: bool = False) -> str:
         ...  # pragma: no cover
 
     @abstractmethod
diff --git a/omegaconf/basecontainer.py b/omegaconf/basecontainer.py
index 76584dc..b86a20f 100644
--- a/omegaconf/basecontainer.py
+++ b/omegaconf/basecontainer.py
@@ -272,18 +272,19 @@ class BaseContainer(Container, ABC):
 
         return BaseContainer._to_content(self, resolve)
 
-    def pretty(self, resolve: bool = False) -> str:
+    def pretty(self, resolve: bool = False, sort_keys: bool = False) -> str:
         from omegaconf import OmegaConf
 
         """
         returns a yaml dump of this config object.
         :param resolve: if True, will return a string with the interpolations resolved, otherwise
         interpolations are preserved
+        :param sort_keys: If True, will print dict keys in sorted order. default False.
         :return: A string containing the yaml representation.
         """
         container = OmegaConf.to_container(self, resolve=resolve, enum_to_str=True)
         return yaml.dump(  # type: ignore
-            container, default_flow_style=False, allow_unicode=True
+            container, default_flow_style=False, allow_unicode=True, sort_keys=sort_keys
         )
 
     @staticmethod


Test patch:
diff --git a/tests/examples/test_dataclass_example.py b/tests/examples/test_dataclass_example.py
index 2adcdbb..945d629 100644
--- a/tests/examples/test_dataclass_example.py
+++ b/tests/examples/test_dataclass_example.py
@@ -146,17 +146,15 @@ def test_nesting() -> None:
         "manager": {"name": "manager", "height": Height.TALL},
     }
 
-    assert (
-        conf.pretty()
-        == """admin:
-  height: ???
+    expected = """name: ???
+admin:
   name: ???
+  height: ???
 manager:
-  height: Height.TALL
   name: manager
-name: ???
+  height: Height.TALL
 """
-    )
+    assert conf.pretty() == expected
 
     # you can assign a different object of the same type
     conf.admin = User(name="omry", height=Height.TALL)
diff --git a/tests/test_basic_ops_dict.py b/tests/test_basic_ops_dict.py
index 45cf3cb..24d1536 100644
--- a/tests/test_basic_ops_dict.py
+++ b/tests/test_basic_ops_dict.py
@@ -97,12 +97,20 @@ list:
     assert OmegaConf.create(c.pretty()) == c
 
 
+def test_pretty_sort_keys() -> None:
+    c = OmegaConf.create({"b": 2, "a": 1})
+    # keys are not sorted by default
+    assert c.pretty() == "b: 2\na: 1\n"
+    c = OmegaConf.create({"b": 2, "a": 1})
+    assert c.pretty(sort_keys=True) == "a: 1\nb: 2\n"
+
+
 def test_pretty_dict_unicode() -> None:
     c = OmegaConf.create(dict(你好="世界", list=[1, 2]))
-    expected = """list:
+    expected = """你好: 世界
+list:
 - 1
 - 2
-你好: 世界
 """
     assert expected == c.pretty()
     assert OmegaConf.create(c.pretty()) == c