Repository: cdgriffith/Box

Issue:
box.box.Box.setdefault return wrong value if value is dictionary
in short, it should return self[item] instead of default

in details - if the value is dictionary, it is transformed to box internally in the "self[item] = default" call.
in this case, returning 'default' will return a detached empty dictionary that is usless

fixed code should look like:
    def setdefault(self, item, default=None):
        if item in self:
            return self[item]

        if isinstance(default, dict):
            default = self._box_config["box_class"](default, **self.__box_config())
        if isinstance(default, list):
            default = box.BoxList(default, **self.__box_config())
        self[item] = default
        return self[item]


Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/32280388/badge)](https://coveralls.io/builds/32280388)

Coverage decreased (-0.1%) to 99.882% when pulling **eef862701345895f935ad4006eb799ec0670fd61 on develop** into **3c0197bafc8e7946860a8b3cffeea188da944b78 on master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/32280389/badge)](https://coveralls.io/builds/32280389)

Coverage decreased (-0.1%) to 99.883% when pulling **eef862701345895f935ad4006eb799ec0670fd61 on develop** into **3c0197bafc8e7946860a8b3cffeea188da944b78 on master**.


Patch:
diff --git a/.pep8speaks.yml b/.pep8speaks.yml
deleted file mode 100644
index f92b889..0000000
--- a/.pep8speaks.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-scanner:
-    diff_only: True
-    linter: flake8
-
-flake8:
-    max-line-length: 120  # Default is 79 in PEP 8
-    ignore:  # Errors and warnings to ignore
-        - F401
-        - F403
-        - F405
-        - W503
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 400a34a..32bd885 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -21,6 +21,7 @@ Code contributions:
 - (jandelgado)
 - Jonas Irgens Kylling (jkylling)
 - Bruno Rocha (rochacbruno)
+- Noam Graetz (NoamGraetz2)
 
 Suggestions and bug reporting:
 
diff --git a/CHANGES.rst b/CHANGES.rst
index 46f106c..72fc58c 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,14 @@
 Changelog
 =========
 
+
+Version 5.1.0
+-------------
+
+* Adding `dotted` option for `items` function (thanks to ipcoder)
+* Fixing bug in box.set_default where value is dictionary, return the internal value and not detached temporary (thanks to Noam Graetz)
+* Removing warnings on import if optional libraries are missing
+
 Version 5.0.1
 -------------
 
diff --git a/box/__init__.py b/box/__init__.py
index 7e6a976..f128dfc 100644
--- a/box/__init__.py
+++ b/box/__init__.py
@@ -2,7 +2,7 @@
 # -*- coding: utf-8 -*-
 
 __author__ = "Chris Griffith"
-__version__ = "5.0.1"
+__version__ = "5.1.0"
 
 from box.box import Box
 from box.box_list import BoxList
diff --git a/box/box.py b/box/box.py
index 9eef453..17cc034 100644
--- a/box/box.py
+++ b/box/box.py
@@ -340,6 +340,15 @@ class Box(dict):
                     keys.add(key)
         return sorted(keys, key=lambda x: str(x))
 
+    def items(self, dotted: Union[bool] = False):
+        if not dotted:
+            return super().items()
+
+        if not self._box_config["box_dots"]:
+            raise BoxError("Cannot return dotted keys as this Box does not have `box_dots` enabled")
+
+        return [(k, self[k]) for k in self.keys(dotted=True)]
+
     def get(self, key, default=NO_DEFAULT):
         if key not in self:
             if default is NO_DEFAULT:
@@ -651,7 +660,7 @@ class Box(dict):
         if isinstance(default, list):
             default = box.BoxList(default, **self.__box_config())
         self[item] = default
-        return default
+        return self[item]
 
     def _safe_attr(self, attr):
         """Convert a key into something that is accessible as an attribute"""
@@ -809,9 +818,6 @@ class Box(dict):
             return cls(data, **box_args)
 
     else:
-        warnings.warn(
-            "yaml is not found in the environment. `to_yaml` and `from_yaml` transforms will not work", BoxWarning
-        )
 
         def to_yaml(
             self,
@@ -875,9 +881,6 @@ class Box(dict):
             return cls(data, **box_args)
 
     else:
-        warnings.warn(
-            "tom is not found in the environment. `to_toml` and `from_toml` transforms will not work", BoxWarning
-        )
 
         def to_toml(self, filename: Union[str, PathLike] = None, encoding: str = "utf-8", errors: str = "strict"):
             raise BoxError('toml is unavailable on this system, please install the "toml" package')
@@ -926,10 +929,6 @@ class Box(dict):
             return cls(data, **box_args)
 
     else:
-        warnings.warn(
-            "msgpack is not found in the environment. " "`to_msgpack` and `from_msgpack` transforms will not work",
-            BoxWarning,
-        )
 
         def to_msgpack(self, filename: Union[str, PathLike] = None, **kwargs):
             raise BoxError('msgpack is unavailable on this system, please install the "msgpack" package')


Test patch:
diff --git a/test/test_box.py b/test/test_box.py
index 2c2d805..07affeb 100644
--- a/test/test_box.py
+++ b/test/test_box.py
@@ -289,6 +289,12 @@ class TestBox:
         a.b.b = 4
         assert a.b.b == 4
 
+    def test_set_default_dict(self):
+        a = Box(test_dict)
+        new = a.setdefault("key3", {})
+        new.yy = 8
+        assert a.key3.yy == 8
+
     def test_set_default(self):
         a = Box(test_dict)
 
@@ -661,6 +667,43 @@ class TestBox:
         assert isinstance(list(c.items())[0][1], BoxList)
         d = Box(movie_data)
         assert len(movie_data["movies"].items()) == len(d.movies.items())
+        e = Box(movie_data, box_dots=True)
+        assert sorted(e.items(dotted=True), key=lambda x: x[0]) == sorted(
+            [
+                ("movies.Robin Hood: Men in Tights.Director", "Mel Brooks"),
+                ("movies.Robin Hood: Men in Tights.Stars[0].imdb", "nm0000144"),
+                ("movies.Robin Hood: Men in Tights.Stars[0].name", "Cary Elwes"),
+                ("movies.Robin Hood: Men in Tights.Stars[0].role", "Robin Hood"),
+                ("movies.Robin Hood: Men in Tights.Stars[1].imdb", "nm0507659"),
+                ("movies.Robin Hood: Men in Tights.Stars[1].name", "Richard Lewis"),
+                ("movies.Robin Hood: Men in Tights.Stars[1].role", "Prince John"),
+                ("movies.Robin Hood: Men in Tights.Stars[2].imdb", "nm0715953"),
+                ("movies.Robin Hood: Men in Tights.Stars[2].name", "Roger Rees"),
+                ("movies.Robin Hood: Men in Tights.Stars[2].role", "Sheriff of Rottingham"),
+                ("movies.Robin Hood: Men in Tights.Stars[3].imdb", "nm0001865"),
+                ("movies.Robin Hood: Men in Tights.Stars[3].name", "Amy Yasbeck"),
+                ("movies.Robin Hood: Men in Tights.Stars[3].role", "Marian"),
+                ("movies.Robin Hood: Men in Tights.imdb_stars", 6.7),
+                ("movies.Robin Hood: Men in Tights.length", 104),
+                ("movies.Robin Hood: Men in Tights.rating", "PG-13"),
+                ("movies.Spaceballs.Director", "Mel Brooks"),
+                ("movies.Spaceballs.Stars[0].imdb", "nm0000316"),
+                ("movies.Spaceballs.Stars[0].name", "Mel Brooks"),
+                ("movies.Spaceballs.Stars[0].role", "President Skroob"),
+                ("movies.Spaceballs.Stars[1].imdb", "nm0001006"),
+                ("movies.Spaceballs.Stars[1].name", "John Candy"),
+                ("movies.Spaceballs.Stars[1].role", "Barf"),
+                ("movies.Spaceballs.Stars[2].imdb", "nm0001548"),
+                ("movies.Spaceballs.Stars[2].name", "Rick Moranis"),
+                ("movies.Spaceballs.Stars[2].role", "Dark Helmet"),
+                ("movies.Spaceballs.imdb_stars", 7.1),
+                ("movies.Spaceballs.length", 96),
+                ("movies.Spaceballs.rating", "PG"),
+            ],
+            key=lambda x: x[0],
+        )
+        with pytest.raises(BoxError):
+            Box(box_dots=False).items(dotted=True)
 
     def test_get(self):
         bx = Box()