Repository: mdolab/baseclasses (language: python)

Issue:
CaseInsensitive containers do not work with pprint
## Description
The `CaseInsensitive` containers are not properly formatted when printed with `pprint`. This bug was introduced with the ABC implementation in #51. I realized this because the ADflow options dictionary is now printed in one massive line instead of having line breaks. The same bug applies to both `CaseInsensitiveDict` and `CaseInsensitiveSet`.

### Steps to reproduce issue
Here is a MWE for `CaseInsensitiveDict`:

```
from baseclasses.utils import CaseInsensitiveDict
from pprint import pprint

sensitive = {"b-longstring": 2, "a-longstring": 1, "c-longstring": 3, "e-longstring": 5, "d-longstring": 4}
insensitive = CaseInsensitiveDict(sensitive)

pprint(sensitive)
pprint(insensitive)
```

### Current behavior
```
>>> pprint(sensitive)
{'a-longstring': 1,
 'b-longstring': 2,
 'c-longstring': 3,
 'd-longstring': 4,
 'e-longstring': 5}

>>> pprint(insensitive)
{'b-longstring': 2, 'a-longstring': 1, 'c-longstring': 3, 'e-longstring': 5, 'd-longstring': 4}
```

### Expected behavior
```
>>> pprint(sensitive)
{'a-longstring': 1,
 'b-longstring': 2,
 'c-longstring': 3,
 'd-longstring': 4,
 'e-longstring': 5}

>>> pprint(insensitive)
{'a-longstring': 1,
 'b-longstring': 2,
 'c-longstring': 3,
 'd-longstring': 4,
 'e-longstring': 5}
```





Discussion / hints:
So it seems that `pprint`'s implementation uses `isinstance(obj, dict)` to call class-specific formatters, see [here](https://github.com/python/cpython/blob/3.8/Lib/pprint.py#L179). Unfortunately because we do not subclass `dict` but its parent class, we don't get the benefit for `pprint`. That's a bummer.

At the same time, despite implementing the (correct) `__repr__` function, it does not seem straightforward to get `pprint` to just call that. It uses some dispatch thing which I don't fully understand. I guess we have a few options here:
1. subclass `pprint` as recommended [here](https://stackoverflow.com/questions/40828173/how-can-i-make-my-class-pretty-printable-in-python)
2. write a custom print function and use that when printing insensitive dicts
3. make `__repr__` return a nicely formatted string similar to `pprint`, and use `print` instead

I am leaning slightly towards 3, since `pprint` is really only meant to work for built in Python objects. If we can figure out when we are calling `pprint` on dicts vs. insensitive dicts (static type checking!) then we can just reliably call `print` instead.

Fix patch:
diff --git a/baseclasses/utils.py b/baseclasses/utils.py
index 61a8044..598b6fb 100644
--- a/baseclasses/utils.py
+++ b/baseclasses/utils.py
@@ -1,5 +1,6 @@
 from collections.abc import MutableMapping, MutableSet
 from typing import Any, Dict, Optional
+from pprint import pformat
 
 
 class CaseInsensitiveDict(MutableMapping):
@@ -93,7 +94,7 @@ def __eq__(self, other) -> bool:
         return selfLower.__eq__(otherLower)
 
     def __repr__(self):
-        return str(self.data)
+        return pformat(self.data)
 
 
 class CaseInsensitiveSet(MutableSet):
@@ -202,7 +203,7 @@ def issubset(self, other) -> bool:
         return lowerSelf.issubset(lowerOther)
 
     def __repr__(self):
-        return str(self.data)
+        return pformat(self.data)
 
 
 class Error(Exception):


Test patch:
diff --git a/tests/test_CaseInsensitve.py b/tests/test_CaseInsensitve.py
index 5b2b0d0..38931c0 100644
--- a/tests/test_CaseInsensitve.py
+++ b/tests/test_CaseInsensitve.py
@@ -1,5 +1,6 @@
 import unittest
 import pickle
+from pprint import pformat
 from baseclasses.utils import CaseInsensitiveDict, CaseInsensitiveSet
 from parameterized import parameterized
 from baseclasses.decorators import require_mpi
@@ -128,6 +129,14 @@ def test_values(self):
     def test_repr(self):
         self.assertEqual(self.d2.__str__(), self.d2.data.__str__())
 
+    def test_repr_pprint(self):
+        long_dict = {"b-longstring": 2, "a-longstring": 1, "c-longstring": 3, "e-longstring": 5, "d-longstring": 4}
+        string_format = pformat(CaseInsensitiveDict(long_dict))
+        string_expected = (
+            "{'a-longstring': 1,\n 'b-longstring': 2,\n 'c-longstring': 3,\n 'd-longstring': 4,\n 'e-longstring': 5}"
+        )
+        self.assertEqual(string_format, string_expected)
+
 
 class TestCaseInsensitiveSet(unittest.TestCase):
     def setUp(self):
@@ -225,6 +234,14 @@ def test_iter(self):
     def test_repr(self):
         self.assertEqual(self.s2.__str__(), self.s2.data.__str__())
 
+    def test_repr_pprint(self):
+        long_set = {"a-longstring", "b-longstring", "c-longstring", "d-longstring", "e-longstring", "f-longstring"}
+        string_format = pformat(CaseInsensitiveSet(long_set))
+        string_expected = (
+            "{'a-longstring',\n 'b-longstring',\n 'c-longstring',\n 'd-longstring',\n 'e-longstring',\n 'f-longstring'}"
+        )
+        self.assertEqual(string_format, string_expected)
+
 
 class TestParallel(unittest.TestCase):
     N_PROCS = 2