Repository: kedro-org/kedro

Issue:
Provide OmegaConf syntax as option for `--params`
## Description
Provide OmegaConf syntax as second option for CLI `--params`. Still have Kedro syntax as default + deprecation warning and then replace in 0.19.0. 

## Context
Currently, Kedro has its own custom syntax and logic for passing parameters in the CLI. We can simplify this by leveraging `OmegaConf`, which we're also going to use in the configloaders instead of `anyconf`: https://github.com/kedro-org/kedro/issues/1868

## Possible Implementation
Option 1:
![image (17)](https://user-images.githubusercontent.com/49397448/194351161-3c096f54-0b18-4ead-85e1-ec77df29bb0a.png)

Option 2:
Add a new boolean flag to support OmegaConf syntax: `--new-style`
`kedro run --new-style --params ...`



Discussion:
That's a fantastic idea! In `kedro-kubeflow` and `kedro-vertexai` we [struggled to pass inline parameters of complex types (list and dicts)](https://github.com/getindata/kedro-kubeflow/issues/60). It looks like `OmegaConf.from_dotlist/from_cli` can handle both nicely, no need to pre-create `config.yaml` before the run anymore.

Resolving patch:
diff --git a/kedro/framework/cli/project.py b/kedro/framework/cli/project.py
--- a/kedro/framework/cli/project.py
+++ b/kedro/framework/cli/project.py
@@ -53,8 +53,8 @@
 PIPELINE_ARG_HELP = """Name of the registered pipeline to run.
 If not set, the '__default__' pipeline is run."""
 PARAMS_ARG_HELP = """Specify extra parameters that you want to pass
-to the context initializer. Items must be separated by comma, keys - by colon,
-example: param1:value1,param2:value2. Each parameter is split by the first comma,
+to the context initialiser. Items must be separated by comma, keys - by colon or equals sign,
+example: param1=value1,param2=value2. Each parameter is split by the first comma,
 so parameter values are allowed to contain colons, parameter keys are not.
 To pass a nested dictionary as parameter, separate keys by '.', example:
 param_group.param1:value1."""
diff --git a/kedro/framework/cli/utils.py b/kedro/framework/cli/utils.py
--- a/kedro/framework/cli/utils.py
+++ b/kedro/framework/cli/utils.py
@@ -14,10 +14,11 @@
 from importlib import import_module
 from itertools import chain
 from pathlib import Path
-from typing import Any, Dict, Iterable, List, Mapping, Sequence, Set, Tuple, Union
+from typing import Dict, Iterable, List, Mapping, Sequence, Set, Tuple, Union
 
 import click
 import importlib_metadata
+from omegaconf import OmegaConf
 
 CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
 MAX_SUGGESTIONS = 3
@@ -401,68 +402,35 @@ def _reformat_load_versions(  # pylint: disable=unused-argument
     return load_versions_dict
 
 
-def _try_convert_to_numeric(value):
-    try:
-        value = float(value)
-    except ValueError:
-        return value
-    return int(value) if value.is_integer() else value
-
-
 def _split_params(ctx, param, value):
     if isinstance(value, dict):
         return value
-    result = {}
+    dot_list = []
     for item in split_string(ctx, param, value):
-        item = item.split(":", 1)
-        if len(item) != 2:
+        equals_idx = item.find("=")
+        colon_idx = item.find(":")
+        if equals_idx != -1 and colon_idx != -1 and equals_idx < colon_idx:
+            # For cases where key-value pair is separated by = and the value contains a colon
+            # which should not be replaced by =
+            pass
+        else:
+            item = item.replace(":", "=", 1)
+        items = item.split("=", 1)
+        if len(items) != 2:
             ctx.fail(
                 f"Invalid format of `{param.name}` option: "
-                f"Item `{item[0]}` must contain "
-                f"a key and a value separated by `:`."
+                f"Item `{items[0]}` must contain "
+                f"a key and a value separated by `:` or `=`."
             )
-        key = item[0].strip()
+        key = items[0].strip()
         if not key:
             ctx.fail(
                 f"Invalid format of `{param.name}` option: Parameter key "
                 f"cannot be an empty string."
             )
-        value = item[1].strip()
-        result = _update_value_nested_dict(
-            result, _try_convert_to_numeric(value), key.split(".")
-        )
-    return result
-
-
-def _update_value_nested_dict(
-    nested_dict: Dict[str, Any], value: Any, walking_path: List[str]
-) -> Dict:
-    """Update nested dict with value using walking_path as a parse tree to walk
-    down the nested dict.
-
-    Example:
-    ::
-        >>> nested_dict = {"foo": {"hello": "world", "bar": 1}}
-        >>> _update_value_nested_dict(nested_dict, value=2, walking_path=["foo", "bar"])
-        >>> print(nested_dict)
-        >>> {'foo': {'hello': 'world', 'bar': 2}}
-
-    Args:
-        nested_dict: dict to be updated
-        value: value to update the nested_dict with
-        walking_path: list of nested keys to use to walk down the nested_dict
-
-    Returns:
-        nested_dict updated with value at path `walking_path`
-    """
-    key = walking_path.pop(0)
-    if not walking_path:
-        nested_dict[key] = value
-        return nested_dict
-    nested_dict[key] = _update_value_nested_dict(
-        nested_dict.get(key, {}), value, walking_path
-    )
-    return nested_dict
+        dot_list.append(item)
+    conf = OmegaConf.from_dotlist(dot_list)
+    return conf
 
 
 def _get_values_as_tuple(values: Iterable[str]) -> Tuple[str, ...]:


Test patch:
diff --git a/tests/framework/cli/test_cli.py b/tests/framework/cli/test_cli.py
--- a/tests/framework/cli/test_cli.py
+++ b/tests/framework/cli/test_cli.py
@@ -23,7 +23,6 @@
     CommandCollection,
     KedroCliError,
     _clean_pycache,
-    _update_value_nested_dict,
     forward_command,
     get_pkg_version,
 )
@@ -279,19 +278,6 @@ def test_clean_pycache(self, tmp_path, mocker):
         ]
         assert mocked_rmtree.mock_calls == expected_calls
 
-    def test_update_value_nested_dict(self):
-        """Test `_update_value_nested_dict` utility function."""
-
-        nested_dict = {"foo": {"hello": "world", "bar": 1}}
-        value_for_nested_dict = 2
-        walking_path_for_nested_dict = ["foo", "bar"]
-
-        expected = {"foo": {"hello": "world", "bar": 2}}
-        actual = _update_value_nested_dict(
-            nested_dict, value_for_nested_dict, walking_path_for_nested_dict
-        )
-        assert actual == expected
-
 
 class TestEntryPoints:
     def test_project_groups(self, entry_points, entry_point):
@@ -627,26 +613,29 @@ def test_run_with_params_in_config(
         "cli_arg,expected_extra_params",
         [
             ("foo:bar", {"foo": "bar"}),
+            ("foo=bar", {"foo": "bar"}),
             (
                 "foo:123.45, bar:1a,baz:678. ,qux:1e-2,quux:0,quuz:",
                 {
                     "foo": 123.45,
                     "bar": "1a",
-                    "baz": 678,
+                    "baz": 678.0,
                     "qux": 0.01,
                     "quux": 0,
-                    "quuz": "",
+                    "quuz": None,
                 },
             ),
             ("foo:bar,baz:fizz:buzz", {"foo": "bar", "baz": "fizz:buzz"}),
+            ("foo=fizz:buzz", {"foo": "fizz:buzz"}),
+            ("foo:fizz=buzz", {"foo": "fizz=buzz"}),
             (
                 "foo:bar, baz: https://example.com",
                 {"foo": "bar", "baz": "https://example.com"},
             ),
+            ("foo:bar, foo:fizz buzz", {"foo": "fizz buzz"}),
             ("foo:bar,baz:fizz buzz", {"foo": "bar", "baz": "fizz buzz"}),
-            ("foo:bar, foo : fizz buzz  ", {"foo": "fizz buzz"}),
             ("foo.nested:bar", {"foo": {"nested": "bar"}}),
-            ("foo.nested:123.45", {"foo": {"nested": 123.45}}),
+            ("foo.nested=123.45", {"foo": {"nested": 123.45}}),
             (
                 "foo.nested_1.double_nest:123.45,foo.nested_2:1a",
                 {"foo": {"nested_1": {"double_nest": 123.45}, "nested_2": "1a"}},
@@ -679,7 +668,7 @@ def test_bad_extra_params(self, fake_project_cli, fake_metadata, bad_arg):
         )
         assert result.exit_code
         assert (
-            "Item `bad` must contain a key and a value separated by `:`"
+            "Item `bad` must contain a key and a value separated by `:` or `=`."
             in result.stdout
         )