Repository: kedro-org/kedro

Issue:
Add official support for Python 3.12
## Description
<!-- Is your feature request related to a problem? A clear and concise description of what the problem is: "I'm always frustrated when ..." -->
Kedro itself probably works on Python 3.12 already, it would be nice to declare official support.

However, installing Kedro is one thing, but installing the typical dependencies might not be straightforward. For example, I just tested the spaceflights starter and most of the dependencies have already published precompiled wheels for Python 3.12 (at least for M1 Mac), but two of them are still problematic as of today:

- aiohttp https://github.com/aio-libs/aiohttp/issues/7739 worked by installing the beta version as advised there, so it will be solved soon (edit: fixed ✔️)
- pyzmq https://github.com/zeromq/pyzmq/issues/1907 (M1 specific), didn't work after installing the ZMQ header libraries with mamba (edit: fixed ✔️)

## Context
<!-- Why is this change important to you? How would you use it? How can it benefit other users? -->
#2815 was already completed, but officially Kedro does not support Python 3.12 yet.

You can use Kedro on Python 3.12 by manually disabling the warning.

## Possible Implementation
<!-- (Optional) Suggest an idea for implementing the addition or change. -->
Wait a bit until at least the spaceflights starter can be safely installed in most mainstream platforms.

## Possible Alternatives
<!-- (Optional) Describe any alternative solutions or features you've considered. -->
Declare Python 3.12 support already, at the cost of creating some grievance of users that then proceed to install some dependencies.



Discussion:
aiohttp 3.9.0 released
pyzmq has a fix incoming https://github.com/zeromq/pyzmq/pull/1913
One last transitive dependency of aiohttp, multidict, is missing 3.12 wheels https://github.com/aio-libs/multidict/issues/887
All transitive dependencies of Kedro have official support for 3.12. Let's go!

Resolving patch:
diff --git a/kedro/__init__.py b/kedro/__init__.py
--- a/kedro/__init__.py
+++ b/kedro/__init__.py
@@ -21,7 +21,7 @@ class KedroPythonVersionWarning(UserWarning):
     warnings.simplefilter("default", KedroDeprecationWarning)
     warnings.simplefilter("error", KedroPythonVersionWarning)
 
-if sys.version_info >= (3, 12):
+if sys.version_info >= (3, 13):
     warnings.warn(
         """Kedro is not yet fully compatible with this Python version.
 To proceed at your own risk and ignore this warning,
diff --git a/kedro/config/abstract_config.py b/kedro/config/abstract_config.py
--- a/kedro/config/abstract_config.py
+++ b/kedro/config/abstract_config.py
@@ -26,6 +26,17 @@ def __init__(
         self.env = env
         self.runtime_params = runtime_params or {}
 
+    # As of Python 3.12 __getitem__ is no longer called in the inherited UserDict.get()
+    # This causes AbstractConfigLoader.get() to break
+    # See: https://github.com/python/cpython/issues/105524
+    # Overwrite the inherited get function with the implementation from 3.11 and prior
+    def get(self, key: str, default: Any = None) -> Any:
+        "D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None."
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
 
 class BadConfigException(Exception):
     """Raised when a configuration file cannot be loaded, for instance


Test patch:
diff --git a/tests/config/test_omegaconf_config.py b/tests/config/test_omegaconf_config.py
--- a/tests/config/test_omegaconf_config.py
+++ b/tests/config/test_omegaconf_config.py
@@ -175,9 +175,11 @@ def test_load_core_config_get_syntax(self, tmp_path):
         )
         params = conf.get("parameters")
         catalog = conf.get("catalog")
+        missing_conf = conf.get("missing_conf")
 
         assert params["param1"] == 1
         assert catalog["trains"]["type"] == "MemoryDataset"
+        assert missing_conf is None
 
     @use_config_dir
     def test_load_local_config_overrides_base(self, tmp_path):
@@ -401,6 +403,7 @@ def test_empty_catalog_file(self, tmp_path):
         )["catalog"]
         assert catalog == {}
 
+    @pytest.mark.xfail(reason="Logic failing")
     def test_overlapping_patterns(self, tmp_path, mocker):
         """Check that same configuration file is not loaded more than once."""
         _write_yaml(
@@ -421,6 +424,7 @@ def test_overlapping_patterns(self, tmp_path, mocker):
             ]
         }
 
+        load_spy = mocker.spy(OmegaConf, "load")
         catalog = OmegaConfigLoader(
             conf_source=str(tmp_path),
             base_env=_BASE_ENV,
@@ -435,9 +439,9 @@ def test_overlapping_patterns(self, tmp_path, mocker):
         }
         assert catalog == expected_catalog
 
-        mocked_load = mocker.patch("omegaconf.OmegaConf.load")
+        # Assert any specific config file was only loaded once
         expected_path = (tmp_path / "dev" / "user1" / "catalog2.yml").resolve()
-        assert mocked_load.called_once_with(expected_path)
+        load_spy.assert_called_once_with(expected_path)
 
     def test_yaml_parser_error(self, tmp_path):
         conf_path = tmp_path / _BASE_ENV
diff --git a/tests/test_import.py b/tests/test_import.py
--- a/tests/test_import.py
+++ b/tests/test_import.py
@@ -4,8 +4,8 @@
 
 
 def test_import_kedro_with_no_official_support_raise_error(mocker):
-    """Test importing kedro with python>=3.12 should fail"""
-    mocker.patch("kedro.sys.version_info", (3, 12))
+    """Test importing kedro with python>=3.13 should fail"""
+    mocker.patch("kedro.sys.version_info", (3, 13))
 
     # We use the parent class to avoid issues with `exec_module`
     with pytest.raises(UserWarning) as excinfo:
@@ -15,8 +15,8 @@ def test_import_kedro_with_no_official_support_raise_error(mocker):
 
 
 def test_import_kedro_with_no_official_support_emits_warning(mocker):
-    """Test importing kedro python>=3.12 and controlled warnings should work"""
-    mocker.patch("kedro.sys.version_info", (3, 12))
+    """Test importing kedro python>=3.13 and controlled warnings should work"""
+    mocker.patch("kedro.sys.version_info", (3, 13))
     mocker.patch("kedro.sys.warnoptions", ["default:Kedro is not yet fully compatible"])
 
     # We use the parent class to avoid issues with `exec_module`