Repository: kedro-org/kedro

Issue:
[DataCatalog]: Error message is confusing if the `catalog.yaml` is invalid
## Description
If one has an invalid entry in the `catalog.yml`, they get a confusing error message like `AttributeError: 'str' object has no attribute 'items'`

![image (13)](https://github.com/kedro-org/kedro/assets/157851531/9f6fea5d-1f48-48b8-8096-59633cc7616f)

Relates to https://github.com/kedro-org/kedro/issues/2401

## Context
<!-- Why is this change important to you? How would you use it? How can it benefit other users? -->

## Possible Implementation
<!-- (Optional) Suggest an idea for implementing the addition or change. -->

## Possible Alternatives
<!-- (Optional) Describe any alternative solutions or features you've considered. -->



Discussion:
Some extra context in #3555



Patch:
diff --git a/RELEASE.md b/RELEASE.md
index 4824c264ce..21925f4395 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -3,6 +3,7 @@
 ## Major features and improvements
 
 ## Bug fixes and other changes
+* Updated error message for invalid catalog entries.
 
 ## Breaking changes to the API
 
diff --git a/kedro/io/core.py b/kedro/io/core.py
index 0a3dbc93a2..ec1b25bfea 100644
--- a/kedro/io/core.py
+++ b/kedro/io/core.py
@@ -155,7 +155,7 @@ def from_config(
         except Exception as exc:
             raise DatasetError(
                 f"An exception occurred when parsing config "
-                f"for dataset '{name}':\n{str(exc)}"
+                f"for dataset '{name}':\n{str(exc)}."
             ) from exc
 
         try:
@@ -384,7 +384,11 @@ def parse_dataset_definition(
     config = copy.deepcopy(config)
 
     if "type" not in config:
-        raise DatasetError("'type' is missing from dataset catalog configuration")
+        raise DatasetError(
+            "'type' is missing from dataset catalog configuration."
+            "\nHint: If this catalog entry is intended for variable interpolation, "
+            "make sure that the top level key is preceded by an underscore."
+        )
 
     dataset_type = config.pop("type")
     class_obj = None
diff --git a/kedro/io/data_catalog.py b/kedro/io/data_catalog.py
index ea0fb12f75..7e2bdebdf1 100644
--- a/kedro/io/data_catalog.py
+++ b/kedro/io/data_catalog.py
@@ -288,6 +288,13 @@ class to be loaded is specified with the key ``type`` and their
         user_default = {}
 
         for ds_name, ds_config in catalog.items():
+            if not isinstance(ds_config, dict):
+                raise DatasetError(
+                    f"Catalog entry '{ds_name}' is not a valid dataset configuration. "
+                    "\nHint: If this catalog entry is intended for variable interpolation, "
+                    "make sure that the key is preceded by an underscore."
+                )
+
             ds_config = _resolve_credentials(  # noqa: PLW2901
                 ds_config, credentials
             )


Test patch:
diff --git a/tests/io/test_data_catalog.py b/tests/io/test_data_catalog.py
index d833aa7333..4a4caff7c6 100644
--- a/tests/io/test_data_catalog.py
+++ b/tests/io/test_data_catalog.py
@@ -519,6 +519,16 @@ def test_config_invalid_arguments(self, sane_config):
         with pytest.raises(DatasetError, match=pattern):
             DataCatalog.from_config(**sane_config)
 
+    def test_config_invalid_dataset_config(self, sane_config):
+        sane_config["catalog"]["invalid_entry"] = "some string"
+        pattern = (
+            "Catalog entry 'invalid_entry' is not a valid dataset configuration. "
+            "\nHint: If this catalog entry is intended for variable interpolation, "
+            "make sure that the key is preceded by an underscore."
+        )
+        with pytest.raises(DatasetError, match=pattern):
+            DataCatalog.from_config(**sane_config)
+
     def test_empty_config(self):
         """Test empty config"""
         assert DataCatalog.from_config(None)