Repository: kedro-org/kedro

Issue:
[KED-1798] AbstractDataSet.__str__ excludes zero-valued integers
_(Apologies for deviating from the template, but this is quite straightforward and probably doesn't need all of those sections)_

### Example

```pycon
>>> from kedro.io import AbstractDataSet
>>> class MyDataSet(AbstractDataSet):
...     def __init__(self, var):
...         self.var = var
...     def _describe(self):
...         return dict(var=self.var)
...     def _load(self):
...         pass
...     def _save(self, data):
...         pass
...
>>> str(MyDataSet(1))
'MyDataSet(var=1)'
>>> str(MyDataSet(0))
'MyDataSet()'
```

`var` isn't displayed when its value is zero, even though that could be a meaningful thing to display. This is due to the `if value` filter below:

https://github.com/quantumblacklabs/kedro/blob/c141467739d696c1f69dfd51be33b8d02d45b501/kedro/io/core.py#L266

Is falsiness an appropriate check, or should numeric values be included even when they're zero?


Discussion:

Resolving patch:
diff --git a/kedro/io/core.py b/kedro/io/core.py
--- a/kedro/io/core.py
+++ b/kedro/io/core.py
@@ -252,7 +252,7 @@ def _to_str(obj, is_root=False):
             1. The root level (i.e. the DataSet.__init__ arguments) are
             formatted like DataSet(key=value).
             2. Dictionaries have the keys alphabetically sorted recursively.
-            3. Empty dictionaries and None values are not shown.
+            3. None values are not shown.
             """
 
             fmt = "{}={}" if is_root else "'{}': {}"  # 1
@@ -263,8 +263,8 @@ def _to_str(obj, is_root=False):
                 text = ", ".join(
                     fmt.format(key, _to_str(value))  # 2
                     for key, value in sorted_dict
-                    if value or isinstance(value, bool)
-                )  # 3
+                    if value is not None  # 3
+                )
 
                 return text if is_root else "{" + text + "}"  # 1
 


Test patch:
diff --git a/tests/extras/datasets/pandas/test_sql_dataset.py b/tests/extras/datasets/pandas/test_sql_dataset.py
--- a/tests/extras/datasets/pandas/test_sql_dataset.py
+++ b/tests/extras/datasets/pandas/test_sql_dataset.py
@@ -210,8 +210,8 @@ def test_str_representation_table(self, table_data_set):
         """Test the data set instance string representation"""
         str_repr = str(table_data_set)
         assert (
-            "SQLTableDataSet(save_args={'index': False}, table_name=%s)" % TABLE_NAME
-            in str_repr
+            "SQLTableDataSet(load_args={}, save_args={'index': False}, "
+            f"table_name={TABLE_NAME})" in str_repr
         )
         assert CONNECTION not in str(str_repr)
 
@@ -309,5 +309,5 @@ def test_save_error(self, query_data_set, dummy_dataframe):
     def test_str_representation_sql(self, query_data_set):
         """Test the data set instance string representation"""
         str_repr = str(query_data_set)
-        assert "SQLQueryDataSet(sql={})".format(SQL_QUERY) in str_repr
+        assert f"SQLQueryDataSet(load_args={{}}, sql={SQL_QUERY})" in str_repr
         assert CONNECTION not in str_repr
diff --git a/tests/io/test_core.py b/tests/io/test_core.py
--- a/tests/io/test_core.py
+++ b/tests/io/test_core.py
@@ -26,14 +26,55 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+from decimal import Decimal
+from fractions import Fraction
 from pathlib import PurePosixPath
+from typing import Any, List
 
 import pytest
 
-from kedro.io.core import _parse_filepath, get_filepath_str
+from kedro.io.core import AbstractDataSet, _parse_filepath, get_filepath_str
+
+# List sourced from https://docs.python.org/3/library/stdtypes.html#truth-value-testing.
+# Excludes None, as None values are not shown in the str representation.
+FALSE_BUILTINS: List[Any] = [
+    False,
+    0,
+    0.0,
+    0j,
+    Decimal(0),
+    Fraction(0, 1),
+    "",
+    (),
+    [],
+    {},
+    set(),
+    range(0),
+]
+
+
+class MyDataSet(AbstractDataSet):
+    def __init__(self, var=None):
+        self.var = var
+
+    def _describe(self):
+        return dict(var=self.var)
+
+    def _load(self):
+        pass  # pragma: no cover
+
+    def _save(self, data):
+        pass  # pragma: no cover
 
 
 class TestCoreFunctions:
+    @pytest.mark.parametrize("var", [1, True] + FALSE_BUILTINS)
+    def test_str_representation(self, var):
+        assert str(MyDataSet(var)) == f"MyDataSet(var={var})"
+
+    def test_str_representation_none(self):
+        assert str(MyDataSet()) == "MyDataSet()"
+
     def test_get_filepath_str(self):
         path = get_filepath_str(PurePosixPath("example.com/test.csv"), "http")
         assert isinstance(path, str)
diff --git a/tests/io/test_partitioned_dataset.py b/tests/io/test_partitioned_dataset.py
--- a/tests/io/test_partitioned_dataset.py
+++ b/tests/io/test_partitioned_dataset.py
@@ -176,10 +176,7 @@ def test_describe(self, dataset):
 
         assert f"path={path}" in str(pds)
         assert "dataset_type=CSVDataSet" in str(pds)
-        if isinstance(dataset, dict) and dataset.keys() - {"type"}:
-            assert "dataset_config" in str(pds)
-        else:
-            assert "dataset_config" not in str(pds)
+        assert "dataset_config" in str(pds)
 
     def test_load_args(self, mocker):
         fake_partition_name = "fake_partition"
@@ -521,7 +518,4 @@ def test_describe(self, dataset):
 
         assert f"path={path}" in str(pds)
         assert "dataset_type=CSVDataSet" in str(pds)
-        if isinstance(dataset, dict) and dataset.keys() - {"type"}:
-            assert "dataset_config" in str(pds)
-        else:
-            assert "dataset_config" not in str(pds)
+        assert "dataset_config" in str(pds)