Skip to content

Commit

Permalink
[OPIK-50] Add cli.py (#182)
Browse files Browse the repository at this point in the history
* Add cli.py

* make opik-installer dynamically install ansible-playbill, and have opik cli invoke the click group directly

* Fix lint errors

---------

Co-authored-by: Diego Fernando Carrión <[email protected]>
  • Loading branch information
alexkuzmik and CRThaze authored Sep 8, 2024
1 parent 02e86ea commit bded09e
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 8 deletions.
77 changes: 71 additions & 6 deletions deployment/installer/opik_installer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
import time
import traceback

from typing import Callable, Tuple, cast
from functools import update_wrapper
from typing import Callable, Tuple, cast, Dict, Optional
from importlib import metadata

import click

from ansible_playbill import AnsibleRunner, PlaybookConfig
from semver import VersionInfo as semver

import opik_installer.opik_constants as c
from .version import __version__
Expand All @@ -22,15 +23,21 @@

__called_with_debug: bool = False

AnsibleRunner = None # pylint: disable=invalid-name
PlaybookConfig = None # pylint: disable=invalid-name


def debug_set() -> bool:
"""Return the debug flag.
Must be called within a Click command.
"""
debug: bool = click.get_current_context().find_root().params.get(
"debug", False,
)
ctx = click.get_current_context()
while ctx.params.get("debug") is None and (
ctx_parent := ctx.parent
) is not None:
ctx = ctx_parent
debug: bool = ctx.params.get("debug", False)
return debug


Expand Down Expand Up @@ -81,6 +88,61 @@ def run_external_subroutine(
time.sleep(1/12)


def require_playbill(
func: Callable[..., None]
) -> Callable[..., None]: # noqa: D202, D301
"""require_playbill decorator.
If a Click command requires anisble-playbill, this decorator will
ensure that it is available.
\f
Args:
f (Callable[..., None]): Function to be decorated.
Returns:
Callable[..., None]: Transparently decorated function.
"""

# Documented technique for adding a decorator to a click command:
# https://web.archive.org/web/20230302175114/https://click.palletsprojects.com/en/8.1.x/commands/#decorating-commands
@click.pass_context
def decorator(ctx: click.Context, *args: Tuple, **kwargs: Dict) -> None:
global AnsibleRunner, PlaybookConfig # pylint: disable=global-statement,invalid-name # noqa: E501
try:
from ansible_playbill import __package__ as playbill_pkg # noqa: F401,E501 # pylint: disable=unused-import,import-outside-toplevel
playbill_version = metadata.version(playbill_pkg)
try:
if debug_set():
click.echo(f"Playbill Version: {playbill_version}")
playbill_semver = semver.parse(playbill_version)
if playbill_semver < c.MINIMUM_PLAYBILL_VERSION:
if not str(playbill_semver.build).startswith("dev"):
raise ImportError
except ValueError:
pass
except ImportError:
subprocess.run(
[
sys.executable,
"-m", "pip",
"install",
"--upgrade",
"ansible-playbill",
],
check=True,
)
finally:
from ansible_playbill import AnsibleRunner as ar # noqa: F401,E501 # pylint: disable=unused-import,import-outside-toplevel
from ansible_playbill import PlaybookConfig as pc # noqa: F401,E501 # pylint: disable=unused-import,import-outside-toplevel
AnsibleRunner = ar
PlaybookConfig = pc

closure: None = ctx.invoke(func, *args, **kwargs)
return closure

return update_wrapper(decorator, func)


@click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
@click.version_option(__version__, *("--version", "-v"))
@click.option(
Expand Down Expand Up @@ -170,6 +232,7 @@ def opik_server(ctx: click.Context, debug: bool) -> None: # noqa: D301
is_flag=True,
help="Skip installation of dependencies",
)
@require_playbill
def install( # noqa: C901
helm_repo_name: str,
helm_repo_url: str,
Expand Down Expand Up @@ -322,8 +385,10 @@ def run_all():
debug=debug,
ansible_bin_path=ansible_path,
).run_all()
except Exception: # pylint: disable=broad-except
except Exception as ex: # pylint: disable=broad-except
failure_sentinel.set()
if debug:
raise ex

click.echo()
run_external_subroutine(
Expand Down
4 changes: 4 additions & 0 deletions deployment/installer/opik_installer/opik_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

from typing import Final, Dict, Any, Union, List

from semver import VersionInfo as semver

from .version import __version__

MINIMUM_PLAYBILL_VERSION: Final[semver] = semver.parse("0.4.0")

ANSI_GREEN: Final[str] = "\033[32m"
ANSI_YELLOW: Final[str] = "\033[33m"
ANSI_RESET: Final[str] = "\033[0m"
Expand Down
3 changes: 2 additions & 1 deletion deployment/installer/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ keywords = [
]
requires-python = ">=3.8.1,<4.0"
dependencies = [
"ansible-playbill>=0.4.0",
# "ansible-playbill>=0.4.0",
"click>=8.1.3",
"semver>=2.8.1",
]

[project.urls]
Expand Down
5 changes: 4 additions & 1 deletion sdks/python/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@
"tqdm",
"uuid7<1.0.0",
"rich",
"click",
"opik-installer",
],
entry_points={
"pytest11": [
"opik = opik.plugins.pytest.hooks",
]
],
"console_scripts": ["opik = opik.cli:cli"],
},
include_package_data=True,
keywords="opik",
Expand Down
26 changes: 26 additions & 0 deletions sdks/python/src/opik/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""CLI tool for Opik."""

from importlib import metadata

import click

from opik_installer import opik_server

__version__: str = "0.0.0+dev"
if __package__:
__version__ = metadata.version(__package__)

CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}


@click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS)
@click.version_option(__version__, *("--version", "-v"))
def cli() -> None:
"""CLI tool for Opik."""


cli.add_command(opik_server, name="server")


if __name__ == "__main__":
cli()

0 comments on commit bded09e

Please sign in to comment.