Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor CLI with lazy subcommands and deferring imports #1920

Merged
merged 11 commits into from
Aug 29, 2024
418 changes: 0 additions & 418 deletions package/kedro_viz/launchers/cli.py

This file was deleted.

1 change: 1 addition & 0 deletions package/kedro_viz/launchers/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""`kedro_viz.launchers.cli` launches the viz server as a CLI app."""
24 changes: 24 additions & 0 deletions package/kedro_viz/launchers/cli/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""`kedro_viz.launchers.cli.build` provides a cli command to build
a Kedro-Viz instance"""
# pylint: disable=import-outside-toplevel
import click

from kedro_viz.launchers.cli.main import viz


@viz.command(context_settings={"help_option_names": ["-h", "--help"]})
@click.option(
"--include-hooks",
is_flag=True,
help="A flag to include all registered hooks in your Kedro Project",
)
@click.option(
"--include-previews",
is_flag=True,
help="A flag to include preview for all the datasets",
)
def build(include_hooks, include_previews):
"""Create build directory of local Kedro Viz instance with Kedro project data"""
from kedro_viz.launchers.cli.utils import create_shareableviz_process

create_shareableviz_process("local", include_previews, include_hooks=include_hooks)
69 changes: 69 additions & 0 deletions package/kedro_viz/launchers/cli/deploy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""`kedro_viz.launchers.cli.deploy` provides a cli command to deploy
a Kedro-Viz instance on cloud platforms"""
# pylint: disable=import-outside-toplevel
import click

from kedro_viz.constants import SHAREABLEVIZ_SUPPORTED_PLATFORMS
from kedro_viz.launchers.cli.main import viz


@viz.command(context_settings={"help_option_names": ["-h", "--help"]})
@click.option(
"--platform",
type=str,
required=True,
help=f"Supported Cloud Platforms like {*SHAREABLEVIZ_SUPPORTED_PLATFORMS,} to host Kedro Viz",
)
@click.option(
"--endpoint",
type=str,
required=True,
help="Static Website hosted endpoint."
"(eg., For AWS - http://<bucket_name>.s3-website.<region_name>.amazonaws.com/)",
)
@click.option(
"--bucket-name",
type=str,
required=True,
help="Bucket name where Kedro Viz will be hosted",
)
@click.option(
"--include-hooks",
is_flag=True,
help="A flag to include all registered hooks in your Kedro Project",
)
@click.option(
"--include-previews",
is_flag=True,
help="A flag to include preview for all the datasets",
)
def deploy(platform, endpoint, bucket_name, include_hooks, include_previews):
"""Deploy and host Kedro Viz on provided platform"""
from kedro_viz.launchers.cli.utils import (
create_shareableviz_process,
display_cli_message,
)

if not platform or platform.lower() not in SHAREABLEVIZ_SUPPORTED_PLATFORMS:
display_cli_message(
"ERROR: Invalid platform specified. Kedro-Viz supports \n"
f"the following platforms - {*SHAREABLEVIZ_SUPPORTED_PLATFORMS,}",
"red",
)
return

if not endpoint:
display_cli_message(
"ERROR: Invalid endpoint specified. If you are looking for platform \n"
"agnostic shareable viz solution, please use the `kedro viz build` command",
"red",
)
return

create_shareableviz_process(
platform,
include_previews,
endpoint,
bucket_name,
include_hooks,
)
76 changes: 76 additions & 0 deletions package/kedro_viz/launchers/cli/lazy_default_group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""`kedro_viz.launchers.cli.lazy_default_group` provides a custom mutli-command
ravi-kumar-pilla marked this conversation as resolved.
Show resolved Hide resolved
subclass for a lazy subcommand loader"""

# pylint: disable=import-outside-toplevel
from typing import Any, Union

import click


class LazyDefaultGroup(click.Group):
"""A click Group that supports lazy loading of subcommands and a default command"""

def __init__(
self,
*args: Any,
**kwargs: Any,
):
if not kwargs.get("ignore_unknown_options", True):
raise ValueError("Default group accepts unknown options")
self.ignore_unknown_options = True

# lazy_subcommands is a map of the form:
#
# {command-name} -> {module-name}.{command-object-name}
#
self.lazy_subcommands = kwargs.pop("lazy_subcommands", {})

self.default_cmd_name = kwargs.pop("default", None)
self.default_if_no_args = kwargs.pop("default_if_no_args", False)

super().__init__(*args, **kwargs)

def list_commands(self, ctx: click.Context) -> list[str]:
return sorted(self.lazy_subcommands.keys())

def get_command( # type: ignore[override]
self, ctx: click.Context, cmd_name: str
) -> Union[click.BaseCommand, click.Command, None]:
if cmd_name in self.lazy_subcommands:
return self._lazy_load(cmd_name)
return super().get_command(ctx, cmd_name)

def _lazy_load(self, cmd_name: str) -> click.BaseCommand:
from importlib import import_module

# lazily loading a command, first get the module name and attribute name
import_path = self.lazy_subcommands[cmd_name]
modname, cmd_object_name = import_path.rsplit(".", 1)

# do the import
mod = import_module(modname)

# get the Command object from that module
cmd_object = getattr(mod, cmd_object_name)

return cmd_object

def parse_args(self, ctx, args):
# If no args are provided and default_command_name is specified,
# use the default command
if not args and self.default_if_no_args:
args.insert(0, self.default_cmd_name)
return super().parse_args(ctx, args)

def resolve_command(self, ctx: click.Context, args):
# Attempt to resolve the command using the parent class method
try:
cmd_name, cmd, args = super().resolve_command(ctx, args)
return cmd_name, cmd, args
except click.UsageError as exc:
if self.default_cmd_name and not ctx.invoked_subcommand:
# No command found, use the default command
default_cmd = self.get_command(ctx, self.default_cmd_name)
if default_cmd:
return default_cmd.name, default_cmd, args
raise exc
32 changes: 32 additions & 0 deletions package/kedro_viz/launchers/cli/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""`kedro_viz.launchers.cli.main` is an entry point for Kedro-Viz cli commands."""

import click

from kedro_viz.launchers.cli.lazy_default_group import LazyDefaultGroup


@click.group(
name="Kedro-Viz",
cls=LazyDefaultGroup,
lazy_subcommands={
"viz": "kedro_viz.launchers.cli.main.viz",
},
)
def viz_cli(): # pylint: disable=missing-function-docstring
pass


@viz_cli.group(
name="Kedro-Viz",
cls=LazyDefaultGroup,
lazy_subcommands={
"run": "kedro_viz.launchers.cli.run.run",
"deploy": "kedro_viz.launchers.cli.deploy.deploy",
"build": "kedro_viz.launchers.cli.build.build",
},
default="run",
default_if_no_args=True,
)
@click.pass_context
def viz(ctx): # pylint: disable=unused-argument
ravi-kumar-pilla marked this conversation as resolved.
Show resolved Hide resolved
"""Visualise a Kedro pipeline using Kedro viz."""
Loading
Loading