Repository: kedro-org/kedro

Issue:
Add parameters to `%reload_kedro` line magic 
## Description

Currently you cannot pass things like `env` or `extra_params` via the line magic, but you can by importing the function.

https://github.com/kedro-org/kedro/blob/5ae97cfb70e5b0d4490132847977d482f13c840f/kedro/extras/extensions/ipython.py#L38

Why don't we introduce feature parity here? 


Discussion:

Resolving patch:
diff --git a/kedro/extras/extensions/ipython.py b/kedro/extras/extensions/ipython.py
--- a/kedro/extras/extensions/ipython.py
+++ b/kedro/extras/extensions/ipython.py
@@ -8,6 +8,9 @@
 from pathlib import Path
 from typing import Any, Dict
 
+from kedro.framework.cli.project import PARAMS_ARG_HELP
+from kedro.framework.cli.utils import ENV_HELP, _split_params
+
 logger = logging.getLogger(__name__)
 default_project_path = Path.cwd()
 
@@ -84,12 +87,46 @@ def reload_kedro(
 
 
 def load_ipython_extension(ipython):
-    """Main entry point when %load_ext is executed"""
+    """
+    Main entry point when %load_ext is executed.
+    IPython will look for this function specifically.
+    See https://ipython.readthedocs.io/en/stable/config/extensions/index.html
 
-    global default_project_path
+    This function is called when users do `%load_ext kedro.extras.extensions.ipython`.
+    When user use `kedro jupyter notebook` or `jupyter ipython`, this extension is
+    loaded automatically.
+    """
+    from IPython.core.magic_arguments import argument, magic_arguments, parse_argstring
+
+    @magic_arguments()
+    @argument(
+        "path",
+        type=str,
+        help=(
+            "Path to the project root directory. If not given, use the previously set"
+            "project root."
+        ),
+        nargs="?",
+        default=None,
+    )
+    @argument("-e", "--env", type=str, default=None, help=ENV_HELP)
+    @argument(
+        "--params",
+        type=lambda value: _split_params(None, None, value),
+        default=None,
+        help=PARAMS_ARG_HELP,
+    )
+    def magic_reload_kedro(line: str):
+        """
+        The `%reload_kedro` IPython line magic. See
+         https://kedro.readthedocs.io/en/stable/tools_integration/ipython.html for more.
+        """
+        args = parse_argstring(magic_reload_kedro, line)
+        reload_kedro(args.path, args.env, args.params)
 
-    ipython.register_magic_function(reload_kedro, "line", "reload_kedro")
+    global default_project_path
 
+    ipython.register_magic_function(magic_reload_kedro, magic_name="reload_kedro")
     default_project_path = _find_kedro_project(Path.cwd())
 
     if default_project_path is None:


Test patch:
diff --git a/tests/extras/extensions/test_ipython.py b/tests/extras/extensions/test_ipython.py
--- a/tests/extras/extensions/test_ipython.py
+++ b/tests/extras/extensions/test_ipython.py
@@ -1,5 +1,7 @@
 # pylint: disable=import-outside-toplevel,reimported
 import pytest
+from IPython.core.error import UsageError
+from IPython.testing.globalipapp import get_ipython
 
 from kedro.extras.extensions.ipython import load_ipython_extension, reload_kedro
 from kedro.framework.startup import ProjectMetadata
@@ -20,6 +22,13 @@ def cleanup_pipeline():
     pipelines.configure()
 
 
+@pytest.fixture(scope="module")  # get_ipython() twice will result in None
+def ipython():
+    ipython = get_ipython()
+    load_ipython_extension(ipython)
+    return ipython
+
+
 PACKAGE_NAME = "fake_page_name"
 PROJECT_NAME = "fake_project_name"
 PROJECT_VERSION = "0.1"
@@ -135,7 +144,9 @@ def test_load_kedro_objects_extra_args(self, tmp_path, mocker):
         )
         assert mock_register_line_magic.call_count == 1
 
-    def test_load_kedro_objects_no_path(self, tmp_path, caplog, mocker):
+    def test_load_kedro_objects_no_path(
+        self, tmp_path, caplog, mocker, ipython
+    ):  # pylint: disable=unused-argument
         from kedro.extras.extensions.ipython import default_project_path
 
         assert default_project_path == tmp_path
@@ -170,7 +181,6 @@ def my_register_pipeline():
         )
         mocker.patch("IPython.core.magic.register_line_magic")
         mocker.patch("kedro.framework.session.KedroSession.load_context")
-        mocker.patch("IPython.get_ipython")
 
         reload_kedro()
 
@@ -193,6 +203,9 @@ def test_load_extension_missing_dependency(self, mocker):
             "kedro.extras.extensions.ipython._find_kedro_project",
             return_value=mocker.Mock(),
         )
+        mocker.patch("IPython.core.magic.register_line_magic")
+        mocker.patch("IPython.core.magic_arguments.magic_arguments")
+        mocker.patch("IPython.core.magic_arguments.argument")
         mock_ipython = mocker.patch("IPython.get_ipython")
 
         with pytest.raises(ImportError):
@@ -205,6 +218,9 @@ def test_load_extension_not_in_kedro_project(self, mocker, caplog):
         mocker.patch(
             "kedro.extras.extensions.ipython._find_kedro_project", return_value=None
         )
+        mocker.patch("IPython.core.magic.register_line_magic")
+        mocker.patch("IPython.core.magic_arguments.magic_arguments")
+        mocker.patch("IPython.core.magic_arguments.argument")
         mock_ipython = mocker.patch("IPython.get_ipython")
 
         load_ipython_extension(mocker.Mock())
@@ -218,3 +234,41 @@ def test_load_extension_not_in_kedro_project(self, mocker, caplog):
             "Make sure you run '%reload_kedro <project_root>'."
         )
         assert expected_message in log_messages
+
+    def test_load_extension_register_line_magic(self, mocker, ipython):
+
+        mocker.patch("kedro.extras.extensions.ipython._find_kedro_project")
+        mock_reload_kedro = mocker.patch("kedro.extras.extensions.ipython.reload_kedro")
+        load_ipython_extension(ipython)
+        mock_reload_kedro.assert_called_once()
+
+        # Calling the line magic to check if the line magic is available
+        ipython.magic("reload_kedro")
+        assert mock_reload_kedro.call_count == 2
+
+    @pytest.mark.parametrize(
+        "args",
+        [
+            "",
+            ".",
+            ". --env=base",
+            "--env=base",
+            "-e base",
+            ". --env=base --params=key:val",
+        ],
+    )
+    def test_line_magic_with_valid_arguments(self, mocker, args, ipython):
+        mocker.patch("kedro.extras.extensions.ipython._find_kedro_project")
+        mocker.patch("kedro.extras.extensions.ipython.reload_kedro")
+
+        ipython.magic(f"reload_kedro {args}")
+
+    def test_line_magic_with_invalid_arguments(self, mocker, ipython):
+        mocker.patch("kedro.extras.extensions.ipython._find_kedro_project")
+        mocker.patch("kedro.extras.extensions.ipython.reload_kedro")
+        load_ipython_extension(ipython)
+
+        with pytest.raises(
+            UsageError, match=r"unrecognized arguments: --invalid_arg=dummy"
+        ):
+            ipython.magic("reload_kedro --invalid_arg=dummy")