We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Provide better error message when you try to use a project command from outside a project
> **Note**. This ticket does not handle the question of what happens if you're in a subdirectory of the project. That is done in #1831 . This ticket is just about what happens if you're outside the project altogether (e.g. the level above the project).

When a kedro project command (e.g. `kedro run`) is run from outside a project then currently you get the very uninformative message `run command is not found`. We should change this so that it gives a much clearer message that you are outside a kedro project and should `cd` into the project before executing the command.

This has been started in #1720 @noklam . You could either continue working on that PR or start a new one. Relevant modifications to make:
* since we're not interested in iterating upwards through parent directories here, the existing upwards iteration using `find_project_root` is no longer relevant. That leaves open the question of whether and how we might suggest what the correct kedro project directory is. It's definitely a bad idea to iterate _downwards_ through directory structure arbitrarily deep (could take a long time). We could go one directory deep so long as it's not too expensive an operation (maybe need to check there aren't too many folders in the current directory first? Needs some quick time profiling to see). Or just not make any suggestion as to the right `cd` command. 
* if we're totally outside a kedro project, a helpful error message is great but:
  * is there some better way to check if the command is a project one without overriding `get_command`, e.g. check if it's in `self.project_groups`?
  * I don't think this formatting is "on brand" yet since we haven't moved the rest of the CLI to rich yet. Let's just do it as `click.secho` for now. Doing rich styling for CLI messages is a separate issue for another time (e.g. we might move to https://github.com/ewels/rich-click)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `kedro/framework/cli/__init__.py`
Content:
```
1 """``kedro.framework.cli`` implements commands available from Kedro's CLI.
2 """
3 
4 from .cli import main
5 from .utils import command_with_verbosity, load_entry_points
6 
7 __all__ = ["main", "command_with_verbosity", "load_entry_points"]
8
```

Path: `kedro/framework/cli/cli.py`
Content:
```
1 """kedro is a CLI for managing Kedro projects.
2 
3 This module implements commands available from the kedro CLI.
4 """
5 from __future__ import annotations
6 
7 import importlib
8 import sys
9 from collections import defaultdict
10 from pathlib import Path
11 from typing import Any, Sequence
12 
13 import click
14 
15 from kedro import __version__ as version
16 from kedro.framework.cli.catalog import catalog_cli
17 from kedro.framework.cli.hooks import get_cli_hook_manager
18 from kedro.framework.cli.jupyter import jupyter_cli
19 from kedro.framework.cli.micropkg import micropkg_cli
20 from kedro.framework.cli.pipeline import pipeline_cli
21 from kedro.framework.cli.project import project_group
22 from kedro.framework.cli.registry import registry_cli
23 from kedro.framework.cli.starters import create_cli
24 from kedro.framework.cli.utils import (
25     CONTEXT_SETTINGS,
26     ENTRY_POINT_GROUPS,
27     CommandCollection,
28     KedroCliError,
29     _get_entry_points,
30     load_entry_points,
31 )
32 from kedro.framework.project import LOGGING  # noqa: F401
33 from kedro.framework.startup import _is_project, bootstrap_project
34 
35 LOGO = rf"""
36  _            _
37 | | _____  __| |_ __ ___
38 | |/ / _ \/ _` | '__/ _ \
39 |   <  __/ (_| | | | (_) |
40 |_|\_\___|\__,_|_|  \___/
41 v{version}
42 """
43 
44 
45 @click.group(context_settings=CONTEXT_SETTINGS, name="Kedro")
46 @click.version_option(version, "--version", "-V", help="Show version and exit")
47 def cli() -> None:  # pragma: no cover
48     """Kedro is a CLI for creating and using Kedro projects. For more
49     information, type ``kedro info``.
50 
51     """
52     pass
53 
54 
55 @cli.command()
56 def info() -> None:
57     """Get more information about kedro."""
58     click.secho(LOGO, fg="green")
59     click.echo(
60         "Kedro is a Python framework for\n"
61         "creating reproducible, maintainable\n"
62         "and modular data science code."
63     )
64 
65     plugin_versions = {}
66     plugin_entry_points = defaultdict(set)
67     for plugin_entry_point in ENTRY_POINT_GROUPS:
68         for entry_point in _get_entry_points(plugin_entry_point):
69             module_name = entry_point.module.split(".")[0]
70             plugin_versions[module_name] = entry_point.dist.version
71             plugin_entry_points[module_name].add(plugin_entry_point)
72 
73     click.echo()
74     if plugin_versions:
75         click.echo("Installed plugins:")
76         for plugin_name, plugin_version in sorted(plugin_versions.items()):
77             entrypoints_str = ",".join(sorted(plugin_entry_points[plugin_name]))
78             click.echo(
79                 f"{plugin_name}: {plugin_version} (entry points:{entrypoints_str})"
80             )
81     else:
82         click.echo("No plugins installed")
83 
84 
85 def _init_plugins() -> None:
86     init_hooks = load_entry_points("init")
87     for init_hook in init_hooks:
88         init_hook()
89 
90 
91 class KedroCLI(CommandCollection):
92     """A CommandCollection class to encapsulate the KedroCLI command
93     loading.
94     """
95 
96     def __init__(self, project_path: Path):
97         self._metadata = None  # running in package mode
98         if _is_project(project_path):
99             self._metadata = bootstrap_project(project_path)
100         self._cli_hook_manager = get_cli_hook_manager()
101 
102         super().__init__(
103             ("Global commands", self.global_groups),
104             ("Project specific commands", self.project_groups),
105         )
106 
107     def main(
108         self,
109         args: Any | None = None,
110         prog_name: Any | None = None,
111         complete_var: Any | None = None,
112         standalone_mode: bool = True,
113         **extra: Any,
114     ) -> Any:
115         if self._metadata:
116             extra.update(obj=self._metadata)
117 
118         # This is how click's internals parse sys.argv, which include the command,
119         # subcommand, arguments and options. click doesn't store this information anywhere
120         # so we have to re-do it.
121         args = sys.argv[1:] if args is None else list(args)
122         self._cli_hook_manager.hook.before_command_run(
123             project_metadata=self._metadata, command_args=args
124         )
125 
126         try:
127             super().main(
128                 args=args,
129                 prog_name=prog_name,
130                 complete_var=complete_var,
131                 standalone_mode=standalone_mode,
132                 **extra,
133             )
134         # click.core.main() method exits by default, we capture this and then
135         # exit as originally intended
136         except SystemExit as exc:
137             self._cli_hook_manager.hook.after_command_run(
138                 project_metadata=self._metadata, command_args=args, exit_code=exc.code
139             )
140             sys.exit(exc.code)
141 
142     @property
143     def global_groups(self) -> Sequence[click.MultiCommand]:
144         """Property which loads all global command groups from plugins and
145         combines them with the built-in ones (eventually overriding the
146         built-in ones if they are redefined by plugins).
147         """
148         return [cli, create_cli, *load_entry_points("global")]
149 
150     @property
151     def project_groups(self) -> Sequence[click.MultiCommand]:
152         """Property which loads all project command groups from the
153         project and the plugins, then combines them with the built-in ones.
154         Built-in commands can be overridden by plugins, which can be
155         overridden by a custom project cli.py.
156         See https://kedro.readthedocs.io/en/stable/extend_kedro/common_use_cases.html#use-case-3-how-to-add-or-modify-cli-commands
157         on how to add this.
158         """
159         if not self._metadata:
160             return []
161 
162         built_in = [
163             catalog_cli,
164             jupyter_cli,
165             pipeline_cli,
166             micropkg_cli,
167             project_group,
168             registry_cli,
169         ]
170 
171         plugins = load_entry_points("project")
172 
173         try:
174             project_cli = importlib.import_module(f"{self._metadata.package_name}.cli")
175             # fail gracefully if cli.py does not exist
176         except ModuleNotFoundError:
177             # return only built-in commands and commands from plugins
178             # (plugins can override built-in commands)
179             return [*built_in, *plugins]
180 
181         # fail badly if cli.py exists, but has no `cli` in it
182         if not hasattr(project_cli, "cli"):
183             raise KedroCliError(
184                 f"Cannot load commands from {self._metadata.package_name}.cli"
185             )
186         user_defined = project_cli.cli
187         # return built-in commands, plugin commands and user defined commands
188         # (overriding happens as follows built-in < plugins < cli.py)
189         return [*built_in, *plugins, user_defined]
190 
191 
192 def main() -> None:  # pragma: no cover
193     """Main entry point. Look for a ``cli.py``, and, if found, add its
194     commands to `kedro`'s before invoking the CLI.
195     """
196     _init_plugins()
197     cli_collection = KedroCLI(project_path=Path.cwd())
198     cli_collection()
199
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/kedro/framework/cli/__init__.py b/kedro/framework/cli/__init__.py
--- a/kedro/framework/cli/__init__.py
+++ b/kedro/framework/cli/__init__.py
@@ -1,7 +1,11 @@
 """``kedro.framework.cli`` implements commands available from Kedro's CLI.
 """
 
-from .cli import main
-from .utils import command_with_verbosity, load_entry_points
+# The constant need to be defined first otherwise it causes circular depdencies
+ORANGE = (255, 175, 0)
+BRIGHT_BLACK = (128, 128, 128)
+
+from .cli import main  # noqa: E402
+from .utils import command_with_verbosity, load_entry_points  # noqa: E402
 
 __all__ = ["main", "command_with_verbosity", "load_entry_points"]
diff --git a/kedro/framework/cli/cli.py b/kedro/framework/cli/cli.py
--- a/kedro/framework/cli/cli.py
+++ b/kedro/framework/cli/cli.py
@@ -6,6 +6,7 @@
 
 import importlib
 import sys
+import traceback
 from collections import defaultdict
 from pathlib import Path
 from typing import Any, Sequence
@@ -13,6 +14,7 @@
 import click
 
 from kedro import __version__ as version
+from kedro.framework.cli import BRIGHT_BLACK, ORANGE
 from kedro.framework.cli.catalog import catalog_cli
 from kedro.framework.cli.hooks import get_cli_hook_manager
 from kedro.framework.cli.jupyter import jupyter_cli
@@ -133,10 +135,40 @@
             )
         # click.core.main() method exits by default, we capture this and then
         # exit as originally intended
+
         except SystemExit as exc:
             self._cli_hook_manager.hook.after_command_run(
                 project_metadata=self._metadata, command_args=args, exit_code=exc.code
             )
+            # When CLI is run outside of a project, project_groups are not registered
+            catch_exception = "click.exceptions.UsageError: No such command"
+            # click convert exception handles to error message
+            if catch_exception in traceback.format_exc() and not self.project_groups:
+                warn = click.style(
+                    "\nKedro project not found in this directory. ",
+                    fg=ORANGE,
+                    bold=True,
+                )
+                result = (
+                    click.style("Project specific commands such as ")
+                    + click.style("'run' ", fg="cyan")
+                    + "or "
+                    + click.style("'jupyter' ", fg="cyan")
+                    + "are only available within a project directory."
+                )
+                message = warn + result
+                hint = (
+                    click.style(
+                        "\nHint: Kedro is looking for a file called ", fg=BRIGHT_BLACK
+                    )
+                    + click.style("'pyproject.toml", fg="magenta")
+                    + click.style(
+                        ", is one present in your current working directory?",
+                        fg=BRIGHT_BLACK,
+                    )
+                )
+                click.echo(message)
+                click.echo(hint)
             sys.exit(exc.code)
 
     @property

{"golden_diff": "diff --git a/kedro/framework/cli/__init__.py b/kedro/framework/cli/__init__.py\n--- a/kedro/framework/cli/__init__.py\n+++ b/kedro/framework/cli/__init__.py\n@@ -1,7 +1,11 @@\n \"\"\"``kedro.framework.cli`` implements commands available from Kedro's CLI.\n \"\"\"\n \n-from .cli import main\n-from .utils import command_with_verbosity, load_entry_points\n+# The constant need to be defined first otherwise it causes circular depdencies\n+ORANGE = (255, 175, 0)\n+BRIGHT_BLACK = (128, 128, 128)\n+\n+from .cli import main  # noqa: E402\n+from .utils import command_with_verbosity, load_entry_points  # noqa: E402\n \n __all__ = [\"main\", \"command_with_verbosity\", \"load_entry_points\"]\ndiff --git a/kedro/framework/cli/cli.py b/kedro/framework/cli/cli.py\n--- a/kedro/framework/cli/cli.py\n+++ b/kedro/framework/cli/cli.py\n@@ -6,6 +6,7 @@\n \n import importlib\n import sys\n+import traceback\n from collections import defaultdict\n from pathlib import Path\n from typing import Any, Sequence\n@@ -13,6 +14,7 @@\n import click\n \n from kedro import __version__ as version\n+from kedro.framework.cli import BRIGHT_BLACK, ORANGE\n from kedro.framework.cli.catalog import catalog_cli\n from kedro.framework.cli.hooks import get_cli_hook_manager\n from kedro.framework.cli.jupyter import jupyter_cli\n@@ -133,10 +135,40 @@\n             )\n         # click.core.main() method exits by default, we capture this and then\n         # exit as originally intended\n+\n         except SystemExit as exc:\n             self._cli_hook_manager.hook.after_command_run(\n                 project_metadata=self._metadata, command_args=args, exit_code=exc.code\n             )\n+            # When CLI is run outside of a project, project_groups are not registered\n+            catch_exception = \"click.exceptions.UsageError: No such command\"\n+            # click convert exception handles to error message\n+            if catch_exception in traceback.format_exc() and not self.project_groups:\n+                warn = click.style(\n+                    \"\\nKedro project not found in this directory. \",\n+                    fg=ORANGE,\n+                    bold=True,\n+                )\n+                result = (\n+                    click.style(\"Project specific commands such as \")\n+                    + click.style(\"'run' \", fg=\"cyan\")\n+                    + \"or \"\n+                    + click.style(\"'jupyter' \", fg=\"cyan\")\n+                    + \"are only available within a project directory.\"\n+                )\n+                message = warn + result\n+                hint = (\n+                    click.style(\n+                        \"\\nHint: Kedro is looking for a file called \", fg=BRIGHT_BLACK\n+                    )\n+                    + click.style(\"'pyproject.toml\", fg=\"magenta\")\n+                    + click.style(\n+                        \", is one present in your current working directory?\",\n+                        fg=BRIGHT_BLACK,\n+                    )\n+                )\n+                click.echo(message)\n+                click.echo(hint)\n             sys.exit(exc.code)\n \n     @property\n", "issue": "Provide better error message when you try to use a project command from outside a project\n> **Note**. This ticket does not handle the question of what happens if you're in a subdirectory of the project. That is done in #1831 . This ticket is just about what happens if you're outside the project altogether (e.g. the level above the project).\r\n\r\nWhen a kedro project command (e.g. `kedro run`) is run from outside a project then currently you get the very uninformative message `run command is not found`. We should change this so that it gives a much clearer message that you are outside a kedro project and should `cd` into the project before executing the command.\r\n\r\nThis has been started in #1720 @noklam . You could either continue working on that PR or start a new one. Relevant modifications to make:\r\n* since we're not interested in iterating upwards through parent directories here, the existing upwards iteration using `find_project_root` is no longer relevant. That leaves open the question of whether and how we might suggest what the correct kedro project directory is. It's definitely a bad idea to iterate _downwards_ through directory structure arbitrarily deep (could take a long time). We could go one directory deep so long as it's not too expensive an operation (maybe need to check there aren't too many folders in the current directory first? Needs some quick time profiling to see). Or just not make any suggestion as to the right `cd` command. \r\n* if we're totally outside a kedro project, a helpful error message is great but:\r\n  * is there some better way to check if the command is a project one without overriding `get_command`, e.g. check if it's in `self.project_groups`?\r\n  * I don't think this formatting is \"on brand\" yet since we haven't moved the rest of the CLI to rich yet. Let's just do it as `click.secho` for now. Doing rich styling for CLI messages is a separate issue for another time (e.g. we might move to https://github.com/ewels/rich-click)\n", "before_files": [{"content": "\"\"\"``kedro.framework.cli`` implements commands available from Kedro's CLI.\n\"\"\"\n\nfrom .cli import main\nfrom .utils import command_with_verbosity, load_entry_points\n\n__all__ = [\"main\", \"command_with_verbosity\", \"load_entry_points\"]\n", "path": "kedro/framework/cli/__init__.py"}, {"content": "\"\"\"kedro is a CLI for managing Kedro projects.\n\nThis module implements commands available from the kedro CLI.\n\"\"\"\nfrom __future__ import annotations\n\nimport importlib\nimport sys\nfrom collections import defaultdict\nfrom pathlib import Path\nfrom typing import Any, Sequence\n\nimport click\n\nfrom kedro import __version__ as version\nfrom kedro.framework.cli.catalog import catalog_cli\nfrom kedro.framework.cli.hooks import get_cli_hook_manager\nfrom kedro.framework.cli.jupyter import jupyter_cli\nfrom kedro.framework.cli.micropkg import micropkg_cli\nfrom kedro.framework.cli.pipeline import pipeline_cli\nfrom kedro.framework.cli.project import project_group\nfrom kedro.framework.cli.registry import registry_cli\nfrom kedro.framework.cli.starters import create_cli\nfrom kedro.framework.cli.utils import (\n    CONTEXT_SETTINGS,\n    ENTRY_POINT_GROUPS,\n    CommandCollection,\n    KedroCliError,\n    _get_entry_points,\n    load_entry_points,\n)\nfrom kedro.framework.project import LOGGING  # noqa: F401\nfrom kedro.framework.startup import _is_project, bootstrap_project\n\nLOGO = rf\"\"\"\n _            _\n| | _____  __| |_ __ ___\n| |/ / _ \\/ _` | '__/ _ \\\n|   <  __/ (_| | | | (_) |\n|_|\\_\\___|\\__,_|_|  \\___/\nv{version}\n\"\"\"\n\n\n@click.group(context_settings=CONTEXT_SETTINGS, name=\"Kedro\")\n@click.version_option(version, \"--version\", \"-V\", help=\"Show version and exit\")\ndef cli() -> None:  # pragma: no cover\n    \"\"\"Kedro is a CLI for creating and using Kedro projects. For more\n    information, type ``kedro info``.\n\n    \"\"\"\n    pass\n\n\n@cli.command()\ndef info() -> None:\n    \"\"\"Get more information about kedro.\"\"\"\n    click.secho(LOGO, fg=\"green\")\n    click.echo(\n        \"Kedro is a Python framework for\\n\"\n        \"creating reproducible, maintainable\\n\"\n        \"and modular data science code.\"\n    )\n\n    plugin_versions = {}\n    plugin_entry_points = defaultdict(set)\n    for plugin_entry_point in ENTRY_POINT_GROUPS:\n        for entry_point in _get_entry_points(plugin_entry_point):\n            module_name = entry_point.module.split(\".\")[0]\n            plugin_versions[module_name] = entry_point.dist.version\n            plugin_entry_points[module_name].add(plugin_entry_point)\n\n    click.echo()\n    if plugin_versions:\n        click.echo(\"Installed plugins:\")\n        for plugin_name, plugin_version in sorted(plugin_versions.items()):\n            entrypoints_str = \",\".join(sorted(plugin_entry_points[plugin_name]))\n            click.echo(\n                f\"{plugin_name}: {plugin_version} (entry points:{entrypoints_str})\"\n            )\n    else:\n        click.echo(\"No plugins installed\")\n\n\ndef _init_plugins() -> None:\n    init_hooks = load_entry_points(\"init\")\n    for init_hook in init_hooks:\n        init_hook()\n\n\nclass KedroCLI(CommandCollection):\n    \"\"\"A CommandCollection class to encapsulate the KedroCLI command\n    loading.\n    \"\"\"\n\n    def __init__(self, project_path: Path):\n        self._metadata = None  # running in package mode\n        if _is_project(project_path):\n            self._metadata = bootstrap_project(project_path)\n        self._cli_hook_manager = get_cli_hook_manager()\n\n        super().__init__(\n            (\"Global commands\", self.global_groups),\n            (\"Project specific commands\", self.project_groups),\n        )\n\n    def main(\n        self,\n        args: Any | None = None,\n        prog_name: Any | None = None,\n        complete_var: Any | None = None,\n        standalone_mode: bool = True,\n        **extra: Any,\n    ) -> Any:\n        if self._metadata:\n            extra.update(obj=self._metadata)\n\n        # This is how click's internals parse sys.argv, which include the command,\n        # subcommand, arguments and options. click doesn't store this information anywhere\n        # so we have to re-do it.\n        args = sys.argv[1:] if args is None else list(args)\n        self._cli_hook_manager.hook.before_command_run(\n            project_metadata=self._metadata, command_args=args\n        )\n\n        try:\n            super().main(\n                args=args,\n                prog_name=prog_name,\n                complete_var=complete_var,\n                standalone_mode=standalone_mode,\n                **extra,\n            )\n        # click.core.main() method exits by default, we capture this and then\n        # exit as originally intended\n        except SystemExit as exc:\n            self._cli_hook_manager.hook.after_command_run(\n                project_metadata=self._metadata, command_args=args, exit_code=exc.code\n            )\n            sys.exit(exc.code)\n\n    @property\n    def global_groups(self) -> Sequence[click.MultiCommand]:\n        \"\"\"Property which loads all global command groups from plugins and\n        combines them with the built-in ones (eventually overriding the\n        built-in ones if they are redefined by plugins).\n        \"\"\"\n        return [cli, create_cli, *load_entry_points(\"global\")]\n\n    @property\n    def project_groups(self) -> Sequence[click.MultiCommand]:\n        \"\"\"Property which loads all project command groups from the\n        project and the plugins, then combines them with the built-in ones.\n        Built-in commands can be overridden by plugins, which can be\n        overridden by a custom project cli.py.\n        See https://kedro.readthedocs.io/en/stable/extend_kedro/common_use_cases.html#use-case-3-how-to-add-or-modify-cli-commands\n        on how to add this.\n        \"\"\"\n        if not self._metadata:\n            return []\n\n        built_in = [\n            catalog_cli,\n            jupyter_cli,\n            pipeline_cli,\n            micropkg_cli,\n            project_group,\n            registry_cli,\n        ]\n\n        plugins = load_entry_points(\"project\")\n\n        try:\n            project_cli = importlib.import_module(f\"{self._metadata.package_name}.cli\")\n            # fail gracefully if cli.py does not exist\n        except ModuleNotFoundError:\n            # return only built-in commands and commands from plugins\n            # (plugins can override built-in commands)\n            return [*built_in, *plugins]\n\n        # fail badly if cli.py exists, but has no `cli` in it\n        if not hasattr(project_cli, \"cli\"):\n            raise KedroCliError(\n                f\"Cannot load commands from {self._metadata.package_name}.cli\"\n            )\n        user_defined = project_cli.cli\n        # return built-in commands, plugin commands and user defined commands\n        # (overriding happens as follows built-in < plugins < cli.py)\n        return [*built_in, *plugins, user_defined]\n\n\ndef main() -> None:  # pragma: no cover\n    \"\"\"Main entry point. Look for a ``cli.py``, and, if found, add its\n    commands to `kedro`'s before invoking the CLI.\n    \"\"\"\n    _init_plugins()\n    cli_collection = KedroCLI(project_path=Path.cwd())\n    cli_collection()\n", "path": "kedro/framework/cli/cli.py"}], "after_files": [{"content": "\"\"\"``kedro.framework.cli`` implements commands available from Kedro's CLI.\n\"\"\"\n\n# The constant need to be defined first otherwise it causes circular depdencies\nORANGE = (255, 175, 0)\nBRIGHT_BLACK = (128, 128, 128)\n\nfrom .cli import main  # noqa: E402\nfrom .utils import command_with_verbosity, load_entry_points  # noqa: E402\n\n__all__ = [\"main\", \"command_with_verbosity\", \"load_entry_points\"]\n", "path": "kedro/framework/cli/__init__.py"}, {"content": "\"\"\"kedro is a CLI for managing Kedro projects.\n\nThis module implements commands available from the kedro CLI.\n\"\"\"\nfrom __future__ import annotations\n\nimport importlib\nimport sys\nimport traceback\nfrom collections import defaultdict\nfrom pathlib import Path\nfrom typing import Any, Sequence\n\nimport click\n\nfrom kedro import __version__ as version\nfrom kedro.framework.cli import BRIGHT_BLACK, ORANGE\nfrom kedro.framework.cli.catalog import catalog_cli\nfrom kedro.framework.cli.hooks import get_cli_hook_manager\nfrom kedro.framework.cli.jupyter import jupyter_cli\nfrom kedro.framework.cli.micropkg import micropkg_cli\nfrom kedro.framework.cli.pipeline import pipeline_cli\nfrom kedro.framework.cli.project import project_group\nfrom kedro.framework.cli.registry import registry_cli\nfrom kedro.framework.cli.starters import create_cli\nfrom kedro.framework.cli.utils import (\n    CONTEXT_SETTINGS,\n    ENTRY_POINT_GROUPS,\n    CommandCollection,\n    KedroCliError,\n    _get_entry_points,\n    load_entry_points,\n)\nfrom kedro.framework.project import LOGGING  # noqa: F401\nfrom kedro.framework.startup import _is_project, bootstrap_project\n\nLOGO = rf\"\"\"\n _            _\n| | _____  __| |_ __ ___\n| |/ / _ \\/ _` | '__/ _ \\\n|   <  __/ (_| | | | (_) |\n|_|\\_\\___|\\__,_|_|  \\___/\nv{version}\n\"\"\"\n\n\n@click.group(context_settings=CONTEXT_SETTINGS, name=\"Kedro\")\n@click.version_option(version, \"--version\", \"-V\", help=\"Show version and exit\")\ndef cli() -> None:  # pragma: no cover\n    \"\"\"Kedro is a CLI for creating and using Kedro projects. For more\n    information, type ``kedro info``.\n\n    \"\"\"\n    pass\n\n\n@cli.command()\ndef info() -> None:\n    \"\"\"Get more information about kedro.\"\"\"\n    click.secho(LOGO, fg=\"green\")\n    click.echo(\n        \"Kedro is a Python framework for\\n\"\n        \"creating reproducible, maintainable\\n\"\n        \"and modular data science code.\"\n    )\n\n    plugin_versions = {}\n    plugin_entry_points = defaultdict(set)\n    for plugin_entry_point in ENTRY_POINT_GROUPS:\n        for entry_point in _get_entry_points(plugin_entry_point):\n            module_name = entry_point.module.split(\".\")[0]\n            plugin_versions[module_name] = entry_point.dist.version\n            plugin_entry_points[module_name].add(plugin_entry_point)\n\n    click.echo()\n    if plugin_versions:\n        click.echo(\"Installed plugins:\")\n        for plugin_name, plugin_version in sorted(plugin_versions.items()):\n            entrypoints_str = \",\".join(sorted(plugin_entry_points[plugin_name]))\n            click.echo(\n                f\"{plugin_name}: {plugin_version} (entry points:{entrypoints_str})\"\n            )\n    else:\n        click.echo(\"No plugins installed\")\n\n\ndef _init_plugins() -> None:\n    init_hooks = load_entry_points(\"init\")\n    for init_hook in init_hooks:\n        init_hook()\n\n\nclass KedroCLI(CommandCollection):\n    \"\"\"A CommandCollection class to encapsulate the KedroCLI command\n    loading.\n    \"\"\"\n\n    def __init__(self, project_path: Path):\n        self._metadata = None  # running in package mode\n        if _is_project(project_path):\n            self._metadata = bootstrap_project(project_path)\n        self._cli_hook_manager = get_cli_hook_manager()\n\n        super().__init__(\n            (\"Global commands\", self.global_groups),\n            (\"Project specific commands\", self.project_groups),\n        )\n\n    def main(\n        self,\n        args: Any | None = None,\n        prog_name: Any | None = None,\n        complete_var: Any | None = None,\n        standalone_mode: bool = True,\n        **extra: Any,\n    ) -> Any:\n        if self._metadata:\n            extra.update(obj=self._metadata)\n\n        # This is how click's internals parse sys.argv, which include the command,\n        # subcommand, arguments and options. click doesn't store this information anywhere\n        # so we have to re-do it.\n        args = sys.argv[1:] if args is None else list(args)\n        self._cli_hook_manager.hook.before_command_run(\n            project_metadata=self._metadata, command_args=args\n        )\n\n        try:\n            super().main(\n                args=args,\n                prog_name=prog_name,\n                complete_var=complete_var,\n                standalone_mode=standalone_mode,\n                **extra,\n            )\n        # click.core.main() method exits by default, we capture this and then\n        # exit as originally intended\n\n        except SystemExit as exc:\n            self._cli_hook_manager.hook.after_command_run(\n                project_metadata=self._metadata, command_args=args, exit_code=exc.code\n            )\n            # When CLI is run outside of a project, project_groups are not registered\n            catch_exception = \"click.exceptions.UsageError: No such command\"\n            # click convert exception handles to error message\n            if catch_exception in traceback.format_exc() and not self.project_groups:\n                warn = click.style(\n                    \"\\nKedro project not found in this directory. \",\n                    fg=ORANGE,\n                    bold=True,\n                )\n                result = (\n                    click.style(\"Project specific commands such as \")\n                    + click.style(\"'run' \", fg=\"cyan\")\n                    + \"or \"\n                    + click.style(\"'jupyter' \", fg=\"cyan\")\n                    + \"are only available within a project directory.\"\n                )\n                message = warn + result\n                hint = (\n                    click.style(\n                        \"\\nHint: Kedro is looking for a file called \", fg=BRIGHT_BLACK\n                    )\n                    + click.style(\"'pyproject.toml\", fg=\"magenta\")\n                    + click.style(\n                        \", is one present in your current working directory?\",\n                        fg=BRIGHT_BLACK,\n                    )\n                )\n                click.echo(message)\n                click.echo(hint)\n            sys.exit(exc.code)\n\n    @property\n    def global_groups(self) -> Sequence[click.MultiCommand]:\n        \"\"\"Property which loads all global command groups from plugins and\n        combines them with the built-in ones (eventually overriding the\n        built-in ones if they are redefined by plugins).\n        \"\"\"\n        return [cli, create_cli, *load_entry_points(\"global\")]\n\n    @property\n    def project_groups(self) -> Sequence[click.MultiCommand]:\n        \"\"\"Property which loads all project command groups from the\n        project and the plugins, then combines them with the built-in ones.\n        Built-in commands can be overridden by plugins, which can be\n        overridden by a custom project cli.py.\n        See https://kedro.readthedocs.io/en/stable/extend_kedro/common_use_cases.html#use-case-3-how-to-add-or-modify-cli-commands\n        on how to add this.\n        \"\"\"\n        if not self._metadata:\n            return []\n\n        built_in = [\n            catalog_cli,\n            jupyter_cli,\n            pipeline_cli,\n            micropkg_cli,\n            project_group,\n            registry_cli,\n        ]\n\n        plugins = load_entry_points(\"project\")\n\n        try:\n            project_cli = importlib.import_module(f\"{self._metadata.package_name}.cli\")\n            # fail gracefully if cli.py does not exist\n        except ModuleNotFoundError:\n            # return only built-in commands and commands from plugins\n            # (plugins can override built-in commands)\n            return [*built_in, *plugins]\n\n        # fail badly if cli.py exists, but has no `cli` in it\n        if not hasattr(project_cli, \"cli\"):\n            raise KedroCliError(\n                f\"Cannot load commands from {self._metadata.package_name}.cli\"\n            )\n        user_defined = project_cli.cli\n        # return built-in commands, plugin commands and user defined commands\n        # (overriding happens as follows built-in < plugins < cli.py)\n        return [*built_in, *plugins, user_defined]\n\n\ndef main() -> None:  # pragma: no cover\n    \"\"\"Main entry point. Look for a ``cli.py``, and, if found, add its\n    commands to `kedro`'s before invoking the CLI.\n    \"\"\"\n    _init_plugins()\n    cli_collection = KedroCLI(project_path=Path.cwd())\n    cli_collection()\n", "path": "kedro/framework/cli/cli.py"}]}