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

Complete the config command and json data export #4

Merged
merged 5 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions opendigger_pycli/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@
from .commands.config_cmd import config
from .commands.display_cmd import display
from .commands.export_cmd import export
from .commands.monitor_cmd import monitor
from .commands.report_cmd import report

opendigger.add_command(config)

query.add_command(display)
query.add_command(export)
query.add_command(report)
query.add_command(monitor)
107 changes: 103 additions & 4 deletions opendigger_pycli/cli/commands/config_cmd.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,107 @@
import typing as t # noqa: F401
import typing as t
from dataclasses import fields

import click
from click.shell_completion import CompletionItem

from opendigger_pycli.console import CONSOLE

@click.command("config")
def config():
pass
from ..base import pass_environment
from ..config import ALL_CONFIGS

if t.TYPE_CHECKING:
from click.core import Context, Parameter

from opendigger_pycli.datatypes.config import BaseConfig

from ..base import Environment


def config_shell_completion(
ctx: t.Optional["Context"], param: t.Optional["Parameter"], incomplete: str
) -> t.List[CompletionItem]:
incomplete_splited = incomplete.rsplit(".", 1)[0]
if incomplete_splited == "":
return [
CompletionItem(config_dataclass_key) for config_dataclass_key in ALL_CONFIGS
]
is_key = False
last_config_dataclass: t.Optional[BaseConfig] = None
for config_dataclass_key in ALL_CONFIGS:
if incomplete_splited.startswith(config_dataclass_key):
if incomplete_splited != config_dataclass_key:
return [CompletionItem(config_dataclass_key + ".")]
else:
is_key = True
last_config_dataclass = ALL_CONFIGS[config_dataclass_key] # type: ignore
break
else:
continue

if not is_key and last_config_dataclass is None:
return []

if incomplete[:-1] == incomplete_splited:
return [CompletionItem(f"{incomplete_splited}.{field.name}") for field in fields(last_config_dataclass)] # type: ignore

for field in fields(last_config_dataclass): # type: ignore
if incomplete in f"{incomplete_splited}.{field.name}":
return [CompletionItem(f"{incomplete_splited}.{field.name}")]

return []


def parse_config_key(key: str) -> t.Tuple[str, str]:
section_name, config_key = key.rsplit(".", 1)
return section_name, config_key


def check_config_setting(
ctx: "Context", param: "Parameter", values: t.List[t.Tuple[str, str]]
) -> t.List[t.Tuple[str, str]]:
for value in values:
try:
section_name, config_key = parse_config_key(value[0])
except Exception:
raise click.BadParameter(f"{value[0]} is not a valid config key")
else:
if section_name not in ALL_CONFIGS or config_key not in [
field.name for field in fields(ALL_CONFIGS[section_name])
]:
raise click.BadParameter(f"{value[0]} is not a valid config key")
return values


@click.command("config") # type: ignore
@click.option(
"--set",
"-s",
"config_settings",
type=(str, str),
multiple=True,
callback=check_config_setting,
shell_complete=config_shell_completion,
help="Set config value",
required=True,
)
@click.option(
"--persist/--no-persist",
"-p/-n",
is_flag=True,
default=True,
help="Set config value persistently",
)
@pass_environment
def config(
env: "Environment", config_settings: t.List[t.Tuple[str, str]], persist: bool
) -> None:
"""Set config value"""
for config_setting in config_settings:
key, value = config_setting
section_name, key = parse_config_key(key)
env.dlog(f"set config: {section_name}.{key}={value}")
env.set_config(section_name, key, value, is_persist=persist)
env.dlog(f"finished to set config: {section_name}.{key}={value}")

CONSOLE.print("[green]Config set successfully[/]")
CONSOLE.print(env.cli_config)
2 changes: 1 addition & 1 deletion opendigger_pycli/cli/commands/display_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from opendigger_pycli.console.print_indicator import SURPPORTED_DISPLAY_FORMAT_TYPE
from opendigger_pycli.results.query import QueryResults

from .base import Environment
from ..base import Environment


@click.command("display", help="Display metrics")
Expand Down
36 changes: 30 additions & 6 deletions opendigger_pycli/cli/commands/export_cmd.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,53 @@
import typing as t
from pathlib import Path

import click

from opendigger_pycli.exporters import (
SURPPORTED_EXPORT_FORMAT_TYPE,
SURPPORTED_EXPORT_FORMATS,
)
from opendigger_pycli.results.export import ExportResult
from opendigger_pycli.utils.decorators import processor

from ..base import pass_environment

if t.TYPE_CHECKING:
from opendigger_pycli.results.query import QueryResults

from ..base import Environment


@click.command("export", help="Export metrics")
@click.option(
"--format",
"-f",
"format_name",
type=click.Choice(["csv", "json", "mhtml"]),
type=click.Choice(SURPPORTED_EXPORT_FORMATS),
required=True,
help="Format to export",
)
@click.option(
"--save-dir",
"-s",
"save_dir",
type=click.Path(file_okay=False, resolve_path=True, path_type=Path),
required=True,
help="Directory to save indicators",
)
@click.option(
"--split/--no-split",
"is_split",
default=True,
help="Save indicators in separate files",
)
@click.option("--filename", "-o", "filename", type=str, required=True)
@processor
@pass_environment
def export(
env: "Environment",
format_name: t.Literal["csv", "json", "mhtml"],
filename: click.Path,
results: "QueryResults",
format: SURPPORTED_EXPORT_FORMAT_TYPE,
save_dir: Path,
is_split: bool,
):
pass
ExportResult(results, format, save_dir, is_split).export()
yield from results
8 changes: 0 additions & 8 deletions opendigger_pycli/cli/commands/monitor_cmd.py

This file was deleted.

8 changes: 0 additions & 8 deletions opendigger_pycli/cli/commands/report_cmd.py

This file was deleted.

14 changes: 11 additions & 3 deletions opendigger_pycli/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ def __init__(self):
def user_config_file_path(self) -> str:
config_dir_str = click.get_app_dir("opendigger-pycli")
config_dir = Path(config_dir_str)
config_dir.mkdir(parents=True, exist_ok=True)
user_config = config_dir / "config.ini"
if not user_config.exists():
user_config.touch()
return str(user_config)

@property
Expand All @@ -40,7 +43,9 @@ def __load_config(self):
parser = configparser.RawConfigParser()
parser.read(self.config_file_paths)

for config_dataclass in ALL_CONFIGS:
for config_dataclass_key in ALL_CONFIGS:
config_dataclass = ALL_CONFIGS[config_dataclass_key]
config_dataclass = ALL_CONFIGS[config_dataclass_key]
if not is_dataclass(config_dataclass):
raise TypeError(f"{config_dataclass} is not a dataclass")

Expand All @@ -54,8 +59,10 @@ def __load_config(self):

def update_config(self):
parser = configparser.RawConfigParser()
parser.read(self.config_file_paths)

for config_dataclass in ALL_CONFIGS:
for config_dataclass_key in ALL_CONFIGS:
config_dataclass = ALL_CONFIGS[config_dataclass_key]
if not is_dataclass(config_dataclass):
raise TypeError(f"{config_dataclass} is not a dataclass")

Expand All @@ -75,7 +82,8 @@ def __rich_console__(
self, console: "Console", options: "ConsoleOptions"
) -> "RenderResult":
yield "[b]OpenDigger Python CLI Configs:[/b]"
for config_dataclass in ALL_CONFIGS:
for config_dataclass_key in ALL_CONFIGS:
config_dataclass = ALL_CONFIGS[config_dataclass_key]
if not is_dataclass(config_dataclass):
raise TypeError(f"{config_dataclass} is not a dataclass")

Expand Down
2 changes: 1 addition & 1 deletion opendigger_pycli/cli/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import click
from click.shell_completion import CompletionItem

from opendigger_pycli.dataloader import (
from opendigger_pycli.dataloaders import (
DeveloperNetworkRepoDataloader,
ProjectOpenRankNetworkRepoDataloader,
RepoNetworkRepoDataloader,
Expand Down
11 changes: 11 additions & 0 deletions opendigger_pycli/cli/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,14 @@ def set_log_level(
self.log("[bold green]verbose mode enabled")

self.load_configs()

def set_config(
self, section_name: str, key: str, value: str, *, is_persist: bool = True
) -> None:
config = getattr(self.cli_config, section_name.replace(".", "_"))
setattr(config, key, value)
if is_persist:
self.cli_config.update_config()
self.dlog(
f"[bold green]set config {section_name}.{key} to {value} successfully"
)
2 changes: 1 addition & 1 deletion opendigger_pycli/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import click

from opendigger_pycli.dataloader import filter_dataloader
from opendigger_pycli.dataloaders import filter_dataloader

if t.TYPE_CHECKING:
from click import Context
Expand Down
2 changes: 1 addition & 1 deletion opendigger_pycli/console/print_base_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rich import box
from rich.table import Table

from opendigger_pycli.dataloader import filter_dataloader
from opendigger_pycli.dataloaders import filter_dataloader
from opendigger_pycli.utils import THREAD_POOL
from opendigger_pycli.utils.gtihub_api import (
RepoInfoType,
Expand Down
24 changes: 1 addition & 23 deletions opendigger_pycli/console/print_indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .print_indicator_graph import print_base_data_graph, print_base_network_data_graph
from .print_indicator_json import print_base_data_json, print_base_network_data_json
from .print_indicator_table import print_base_data_table, print_base_network_data_table
from .utils import print_failed_query

if t.TYPE_CHECKING:
from opendigger_pycli.datatypes import (
Expand All @@ -24,29 +25,6 @@ def format_indicator_name(indicator_name: str) -> str:
return indicator_name.replace("_", " ").title()


def print_failed_query(
indicator_name: str, failed_query: t.Optional["IndicatorQuery"]
) -> None:
if failed_query is None:
return

if failed_query.years:
CONSOLE.print(
f"[red]No {indicator_name} Indicator Data in years: "
f"{list(failed_query.years)}"
)
if failed_query.months:
CONSOLE.print(
f"[red]No {indicator_name} Indicator Data in months: "
f"{list(failed_query.months)}"
)
if failed_query.year_months:
CONSOLE.print(
f"[red]No {indicator_name} Indicator Data in year_months: "
f"{[f'{year_month[0]}-{year_month[1]:02}' for year_month in failed_query.year_months]}"
)


def print_trivial_indicator(
indicator_name: str,
indicator_data: "TrivialIndicatorData",
Expand Down
26 changes: 26 additions & 0 deletions opendigger_pycli/console/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import typing as t

from . import CONSOLE

if t.TYPE_CHECKING:
from opendigger_pycli.datatypes import NameAndValue, NameNameAndValue
from opendigger_pycli.datatypes.query import IndicatorQuery


def if_prettey(value: t.Any) -> bool:
Expand All @@ -13,3 +16,26 @@ def if_prettey(value: t.Any) -> bool:
value = t.cast(t.List["NameNameAndValue"], value)
return True
return False


def print_failed_query(
indicator_name: str, failed_query: t.Optional["IndicatorQuery"]
) -> None:
if failed_query is None:
return

if failed_query.years:
CONSOLE.print(
f"[red]No {indicator_name} Indicator Data in years: "
f"{list(failed_query.years)}"
)
if failed_query.months:
CONSOLE.print(
f"[red]No {indicator_name} Indicator Data in months: "
f"{list(failed_query.months)}"
)
if failed_query.year_months:
CONSOLE.print(
f"[red]No {indicator_name} Indicator Data in year_months: "
f"{[f'{year_month[0]}-{year_month[1]:02}' for year_month in failed_query.year_months]}"
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from opendigger_pycli.dataloader import (
from opendigger_pycli.dataloaders import (
OpenRankRepoDataloader,
OpenRankUserDataLoader,
ProjectOpenRankNetworkRepoDataloader,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from opendigger_pycli.dataloader.indices import (
from opendigger_pycli.dataloaders.indices import (
ActivityRepoDataloader,
ActivityUserDataLoader,
AttentionRepoDataloader,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from opendigger_pycli.dataloader.metrics import (
from opendigger_pycli.dataloaders.metrics import (
AcceptedChangeRequestRepoDataloader,
ActiveDateAndTimeRepoDataloader,
AddedCodeChangeLineRepoDataloader,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from opendigger_pycli.dataloader.networks import (
from opendigger_pycli.dataloaders.networks import (
DeveloperNetworkRepoDataloader,
DeveloperNetworkUserDataloader,
ProjectOpenRankNetworkRepoDataloader,
Expand Down
Loading