Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
kiara / interfaces / cli / context / commands.py
Size: Mime:
# -*- coding: utf-8 -*-

#  Copyright (c) 2021, Markus Binsteiner
#
#  Mozilla Public License, version 2.0 (see LICENSE or https://www.mozilla.org/en-US/MPL/2.0/)
import sys
from typing import TYPE_CHECKING, Tuple, Union

import rich_click as click
from rich import box
from rich.panel import Panel

from kiara.interfaces import KiaraAPIWrap, get_console
from kiara.utils import log_exception
from kiara.utils.cli import (
    dict_from_cli_args,
    output_format_option,
    terminal_print,
    terminal_print_model,
)

if TYPE_CHECKING:
    from kiara.api import Kiara, KiaraAPI, KiaraConfig


@click.group("context")
@click.pass_context
def context(ctx):
    """Kiara context related sub-commands."""


@context.command("list")
@click.pass_context
def list_contexts(ctx) -> None:
    """List existing contexts."""
    kiara_api: KiaraAPI = ctx.obj.kiara_api

    summaries = kiara_api.retrieve_context_infos()

    terminal_print(summaries)


@context.command("explain")
@click.argument("context_name", nargs=-1, required=False)
@click.option("--value-ids", "-i", help="Show value ids.", is_flag=True, default=False)
@click.option(
    "--show-config", "-c", help="Also show kiara config.", is_flag=True, default=False
)
@output_format_option()
@click.pass_context
def explain_context(
    ctx,
    format: str,
    value_ids: bool,
    context_name: Union[Tuple[str], None] = None,
    show_config: bool = False,
):
    """Print details for one or several contexts."""
    kiara_config: KiaraConfig = ctx.obj.kiara_config

    if not context_name:
        cn = ctx.obj.kiara_context_name
        contexts = [cn]
    else:
        contexts = list(context_name)

    from kiara.models.context import ContextInfo

    render_config = {
        "show_lines": False,
        "show_header": False,
        "show_description": False,
    }

    if show_config:
        from rich.table import Table

        config = kiara_config.create_renderable(**render_config)
        table = Table(show_header=False, show_lines=False, box=box.SIMPLE)
        table.add_column("key", style="i")
        table.add_column("value")
        if kiara_config._config_path:
            table.add_row("config file", f"  {kiara_config._config_path}")
        table.add_row("config", config)
        terminal_print(table, in_panel="Kiara config")

    if len(contexts) == 1:

        kcc = kiara_config.get_context_config(contexts[0])
        cs = ContextInfo.create_from_context_config(
            kcc, context_name=contexts[0], runtime_config=kiara_config.runtime_config
        )
        terminal_print_model(
            cs,
            format=format,
            full_details=True,
            show_value_ids=value_ids,
            in_panel=f"Context '{contexts[0]}'",
        )

    else:
        summaries = []
        for c in contexts:
            cc = kiara_config.get_context_config(c)
            cs = ContextInfo.create_from_context_config(
                cc, context_name=c, runtime_config=kiara_config.runtime_config
            )
            summaries.append(cs)
        terminal_print_model(
            *summaries, format=format, full_details=True, show_value_ids=value_ids
        )


@context.command("delete")
@click.argument("context_name", nargs=1, required=False)
@click.option(
    "--force", "-f", help="Delete without prompt.", is_flag=True, default=False
)
@click.option("--all", "-a", help="Delete all contexts.", is_flag=True, default=False)
@click.pass_context
def delete_context(
    ctx,
    context_name: Union[str, None] = None,
    force: bool = False,
    all: bool = False,
):
    """Delete a context and all its stored values."""
    kiara_config: KiaraConfig = ctx.obj.kiara_config

    if not context_name:
        if all:
            _context_name = "ALL_CONTEXTS"
        else:
            _context_name = ctx.obj.kiara_context_name
    else:
        if all:
            if context_name != "ALL_CONTEXTS":
                terminal_print()
                terminal_print(
                    f"Context name '{context_name}' specified, as well as '--all-contexts', this is not valid."
                )
                sys.exit(1)
        _context_name = context_name

    confirmed = False

    if _context_name == "ALL_CONTEXTS":
        if not force:
            from kiara.models.context import ContextInfos

            summaries = ContextInfos.create_context_infos(
                contexts=kiara_config.context_configs
            )
            terminal_print_model(summaries, in_panel="All contexts:")
            user_input = get_console().input(
                f"Deleting all contexts, are you sure? \[yes/no]: "  # noqa
            )

            if user_input.lower() == "yes":
                confirmed = True
        else:
            confirmed = True

        if not confirmed:
            terminal_print("\nDoing nothing...")
            sys.exit(0)

        terminal_print("Deleting contexts...")
        for _context_name in kiara_config.context_configs.keys():
            terminal_print(f"  - {_context_name}")
            kiara_config.delete(context_name=_context_name, dry_run=False)

        terminal_print("Done.")

    else:

        if not force:

            context_summary = kiara_config.delete(
                context_name=_context_name, dry_run=True
            )
            terminal_print()
            if not context_summary:
                terminal_print(
                    f"Can't determine context details for context '{_context_name}'."
                )
            else:
                terminal_print_model(
                    context_summary,
                    full_details=True,
                    in_panel=f"Context details: {_context_name}",
                )
            terminal_print()
            user_input = get_console().input(
                f"Deleting context '[b i]{_context_name}[/b i]', are you sure? \[yes/no]: "
            )

            if user_input.lower() == "yes":
                confirmed = True
        else:
            confirmed = True

        if not confirmed:
            terminal_print("\nDoing nothing...")
            sys.exit(0)

        terminal_print("Deleting context...")
        kiara_config.delete(context_name=_context_name, dry_run=False)

        terminal_print("Done.")


@context.group("info")
@click.pass_context
def info(ctx):
    """Information about several aspects of the current/specified kiara context."""


@info.group("config")
@click.pass_context
def config(ctx):
    """Information about the current context configuration."""


@config.command("print")
@output_format_option()
@click.pass_context
def print_config(ctx, format) -> None:
    """Print the (current) kiara context configuration."""
    kiara_obj: Kiara = ctx.obj.kiara

    terminal_print_model(
        kiara_obj.context_config,
        format=format,
        in_panel=f"kiara context config: [b i]{kiara_obj.context_config.context_id}[/b i]",
    )


@config.command("help")
@click.pass_context
def config_help(ctx):
    """Print available configuration options and information about them."""
    from kiara.context import KiaraContextConfig
    from kiara.utils.output import create_table_from_base_model_cls

    table = create_table_from_base_model_cls(model_cls=KiaraContextConfig)
    terminal_print()
    terminal_print(Panel(table))


@info.group(name="runtime")
@click.pass_context
def runtime(ctx):
    """Information about the current runtime (Python environment, available metadata models, etc)."""


@runtime.command("print")
@output_format_option()
@click.pass_context
def print_context(ctx, format: str):
    """Print all relevant models within the current runtime environment."""
    kiara_obj: Kiara = ctx.obj.kiara

    terminal_print_model(
        kiara_obj.context_info,
        format=format,
        in_panel=f"Context info for kiara id: {kiara_obj.id}",
    )


@runtime.group(name="environment")
@click.pass_context
def env_group(ctx):
    """Environment-related sub-commands."""


@env_group.command("list")
@click.pass_context
def list_envs(ctx):
    """List available runtime environment information."""
    from kiara.registries.environment import EnvironmentRegistry

    env_reg = EnvironmentRegistry.instance()

    terminal_print(env_reg)


@env_group.command("explain")
@click.argument("env_type", metavar="ENVIRONMENT_TYPE", nargs=1, required=True)
@output_format_option()
@click.pass_context
def explain_env(ctx, env_type: str, format: str) -> None:

    from kiara.registries.environment import EnvironmentRegistry

    env_reg = EnvironmentRegistry.instance()

    env = env_reg.environments.get(env_type, None)
    if env is None:
        terminal_print()
        terminal_print(
            f"No environment with name '{env_type}' available. Available types: {', '.join(env_reg.environments.keys())}"
        )
        sys.exit()

    terminal_print_model(
        env,
        format=format,
        in_panel=f"Details for environment: [b i]{env_type}[/b i]",
        summary=False,
    )


@runtime.group()
@click.pass_context
def metadata(ctx):
    """Metadata-related sub-commands."""


@metadata.command(name="list")
@output_format_option()
@click.pass_context
def list_metadata(ctx, format) -> None:
    """List available metadata schemas."""
    kiara_obj: Kiara = ctx.obj.kiara
    from kiara.models.values.value_metadata import ValueMetadata

    metadata_types = kiara_obj.kiara_model_registry.get_models_of_type(ValueMetadata)

    terminal_print_model(
        metadata_types, format=format, in_panel="Available metadata types"
    )


@metadata.command(name="explain")
@click.argument("metadata_key", nargs=1, required=True)
# @click.option(
#     "--details",
#     "-d",
#     help="Print more metadata schema details (for 'terminal' format).",
#     is_flag=True,
# )
@output_format_option()
@click.pass_context
def explain_metadata(ctx, metadata_key, format) -> None:
    """Print details for a specific metadata schema."""
    kiara_obj: Kiara = ctx.obj.kiara
    from kiara.models.values.value_metadata import ValueMetadata

    metadata_types = kiara_obj.kiara_model_registry.get_models_of_type(ValueMetadata)

    if metadata_key not in metadata_types.item_infos.keys():
        terminal_print()
        terminal_print(f"No metadata schema for key '{metadata_key}' found...")
        sys.exit(1)

    info_obj = metadata_types.item_infos[metadata_key]

    terminal_print_model(
        info_obj,
        format=format,
        in_panel=f"Details for metadata type: [b i]{metadata_key}[/b i]",
    )


@runtime.group()
@click.pass_context
def api(ctx):
    """API-related sub-commands."""


@api.command()
@click.argument("filter", nargs=-1, required=False)
@click.option(
    "--full-doc",
    "-d",
    is_flag=True,
    help="Display the full doc for all operations (when using 'terminal' as format).",
)
@click.pass_context
def list_endpoints(ctx, filter, full_doc):
    """List all available API endpoints."""
    from kiara.interfaces.python_api import KiaraAPI
    from kiara.interfaces.python_api.proxy import ApiEndpoints

    exclude = ["get_runtime_config", "retrieve_workflow_info"]
    endpoints = ApiEndpoints(api_cls=KiaraAPI, filters=filter, exclude=exclude)

    terminal_print()
    terminal_print(endpoints, in_panel="API endpoints", full_doc=full_doc)


@context.group("service")
@click.pass_context
def service(ctx):
    """Service-related sub-commands."""


@service.command("start")
@click.option("--host", help="The host to bind to.", default="localhost")
@click.option("--port", help="The port to bind to.", required=False, default=0)
@click.option(
    "--monitor",
    help="Monitor the service.",
    required=False,
    default=False,
    is_flag=True,
)
@click.option(
    "--stdout",
    "-o",
    help="Write output logs to this file.",
    required=False,
    default=None,
)
@click.option(
    "--stderr",
    "-e",
    help="Write error logs to this file.",
    required=False,
    default=None,
)
@click.option(
    "--timeout",
    "-t",
    help="If set, the service shuts down if not request happens within the specified timeout (in milliseconds).",
    required=False,
    default=0,
)
@click.pass_context
def start_service(
    ctx,
    host: str,
    port: int,
    monitor: bool = False,
    stdout: Union[str, None] = None,
    stderr: Union[str, None] = None,
    timeout: int = 0,
):
    """Start a kiara zmq service for this context."""

    from kiara.utils.output import create_table_from_model_object
    from kiara.zmq import start_zmq_service

    api_wrap: KiaraAPIWrap = ctx.obj

    try:
        details = start_zmq_service(
            api_wrap=api_wrap,
            host=host,
            port=port,
            monitor=monitor,
            stdout=stdout,
            stderr=stderr,
            timeout=timeout,
        )

        if not monitor:
            assert details is not None
            if not details.newly_started:
                terminal_print()
                terminal_print(
                    f"Service for context '{api_wrap.kiara_context_name}' already running, doing nothing..."
                )
            else:
                terminal_print()
                terminal_print("Started service in background process:")
                terminal_print()
                table = create_table_from_model_object(
                    details,
                    render_config={"show_header": False, "show_type_column": False},
                )
                terminal_print(table, in_panel="Service details")

    except Exception as e:
        import traceback

        traceback.print_exc()
        log_exception(e)
        terminal_print()
        terminal_print(f"Error starting service: {e}")
        sys.exit(1)


@service.command("stop")
@click.argument("context_name", nargs=1, required=False)
@click.pass_context
def stop_service(ctx, context_name: Union[None, str]):
    """Stop a running zmq service for this context."""

    from kiara.zmq import get_context_details
    from kiara.zmq.client import KiaraZmqClient

    if not context_name:
        context_name = ctx.obj.kiara_context_name

    context_details = get_context_details(context_name=context_name)  # type: ignore
    if not context_details:
        terminal_print()
        terminal_print(
            f"No service running for context '{context_name}'. Doing nothing..."
        )
        sys.exit(0)

    host = context_details["host"]
    port = context_details["port"]
    zmq_client = KiaraZmqClient(host=host, port=port)
    zmq_client.request(endpoint_name="stop", args={})

    terminal_print()
    terminal_print(f"Stopped context: {context_name}")

    sys.exit(0)


@service.command("list")
@click.pass_context
def list_services(ctx):
    """List all contexts that have a currently running service."""

    from kiara.zmq import list_registered_contexts

    contexts = list_registered_contexts()
    if not contexts:
        terminal_print()
        terminal_print("No services running.")
        sys.exit(0)

    terminal_print()
    terminal_print("Running services:")
    for c in contexts:
        terminal_print(f" - {c}")


@service.command("request")
@click.option(
    "--context", "-c", help="The context to use.", required=False, default=None
)
@click.argument("endpoint", required=True, nargs=1)
@click.argument("arguments", required=False, nargs=-1)
@click.pass_context
def request(
    ctx, endpoint: str, arguments: Tuple[str], context: Union[None, str] = None
):
    """Send a request to a kiara zmq service."""

    from kiara.zmq import get_context_details
    from kiara.zmq.client import KiaraZmqClient

    if not context:
        context = ctx.obj.kiara_context_name

    context_details = get_context_details(context_name=context)  # type: ignore
    if not context_details:
        terminal_print()
        terminal_print(f"No service running for context '{context}'. Doing nothing...")
        sys.exit(1)

    zmq_client = KiaraZmqClient(
        host=context_details["host"], port=context_details["port"]
    )

    args = dict_from_cli_args(*arguments)

    try:
        result = zmq_client.request(endpoint_name=endpoint, args=args)
        print(result)  # noqa
    except KeyboardInterrupt:
        terminal_print("\nInterrupted by user, closing connection to service...")
    finally:
        zmq_client.close()


CLI_CLIENT_CLICK_CONTEXT_SETTINGS = {
    "help_option_names": [],
    "ignore_unknown_options": True,
}


@service.command("request-cli", context_settings=CLI_CLIENT_CLICK_CONTEXT_SETTINGS)
@click.option(
    "--context", "-c", help="The context to use.", required=False, default=None
)
@click.argument("arguments", required=False, nargs=-1)
@click.pass_context
def request_cli(ctx, arguments: Tuple[str], context: Union[None, str] = None):
    """Send a request to a kiara zmq service."""
    from kiara.zmq import get_context_details
    from kiara.zmq.client import KiaraZmqClient

    if not context:
        context = ctx.obj.kiara_context_name

    context_details = get_context_details(context_name=context)  # type: ignore
    if not context_details:
        terminal_print()
        terminal_print(f"No service running for context '{context}'. Doing nothing...")
        sys.exit(1)

    zmq_client = KiaraZmqClient(
        host=context_details["host"], port=context_details["port"]
    )

    try:
        zmq_client.request_cli(args=arguments)
    except KeyboardInterrupt:
        terminal_print("\nInterrupted by user, closing connection to service...")
    finally:
        zmq_client.close()