Skip to content

Commit 721dc89

Browse files
author
chenjunjie
committed
✨ feat: add export json
1 parent 2845434 commit 721dc89

File tree

6 files changed

+260
-10
lines changed

6 files changed

+260
-10
lines changed
Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,53 @@
11
import typing as t
2+
from pathlib import Path
23

34
import click
45

6+
from opendigger_pycli.exporters import (
7+
SURPPORTED_EXPORT_FORMAT_TYPE,
8+
SURPPORTED_EXPORT_FORMATS,
9+
)
10+
from opendigger_pycli.results.export import ExportResult
511
from opendigger_pycli.utils.decorators import processor
612

713
from ..base import pass_environment
814

915
if t.TYPE_CHECKING:
16+
from opendigger_pycli.results.query import QueryResults
17+
1018
from ..base import Environment
1119

1220

1321
@click.command("export", help="Export metrics")
1422
@click.option(
1523
"--format",
1624
"-f",
17-
"format_name",
18-
type=click.Choice(["csv", "json", "mhtml"]),
25+
type=click.Choice(SURPPORTED_EXPORT_FORMATS),
1926
required=True,
27+
help="Format to export",
28+
)
29+
@click.option(
30+
"--save-dir",
31+
"-s",
32+
"save_dir",
33+
type=click.Path(file_okay=False, resolve_path=True, path_type=Path),
34+
required=True,
35+
help="Directory to save indicators",
36+
)
37+
@click.option(
38+
"--split/--no-split",
39+
"is_split",
40+
default=True,
41+
help="Save indicators in separate files",
2042
)
21-
@click.option("--filename", "-o", "filename", type=str, required=True)
2243
@processor
2344
@pass_environment
2445
def export(
2546
env: "Environment",
26-
format_name: t.Literal["csv", "json", "mhtml"],
27-
filename: click.Path,
47+
results: "QueryResults",
48+
format: SURPPORTED_EXPORT_FORMAT_TYPE,
49+
save_dir: Path,
50+
is_split: bool,
2851
):
29-
pass
52+
ExportResult(results, format, save_dir, is_split).export()
53+
yield from results

opendigger_pycli/datatypes/indicators/base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ def __str__(self) -> str:
113113
def __repr__(self) -> str:
114114
return f"\033[34m{self.name}\033[0m: {self.value}"
115115

116+
@property
117+
def tuple(self) -> t.Tuple[str, float]:
118+
return (self.name, self.value)
119+
116120

117121
class NameNameAndValue(NamedTuple):
118122
name0: str
@@ -125,6 +129,10 @@ def __str__(self) -> str:
125129
def __repr__(self) -> str:
126130
return f"\033[34m{self.name0}\033[0m>>\033[31m{self.name1}\033[0m: {self.value}"
127131

132+
@property
133+
def tuple(self) -> t.Tuple[str, str, float]:
134+
return (self.name0, self.name1, self.value)
135+
128136

129137
AvgDataType = BaseData[float]
130138
LevelDataType = BaseData[List[int]]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import typing as t
2+
3+
SURPPORTED_EXPORT_FORMATS = ["echarts", "json"]
4+
SURPPORTED_EXPORT_FORMAT_TYPE = t.Literal["echarts", "json"]
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import json
2+
import typing as t
3+
from dataclasses import asdict
4+
5+
if t.TYPE_CHECKING:
6+
from opendigger_pycli.datatypes import (
7+
BaseData,
8+
BaseNetworkData,
9+
NameAndValue,
10+
NonTrivalNetworkInciatorData,
11+
NonTrivialIndicatorData,
12+
TrivialIndicatorData,
13+
TrivialNetworkIndicatorData,
14+
)
15+
16+
17+
def export_trivial_network_to_json(
18+
indicator_data: "TrivialNetworkIndicatorData",
19+
) -> str:
20+
result = {}
21+
warmup = getattr(indicator_data.value, "nodes")[0]
22+
if hasattr(warmup, "tuple"):
23+
nodes: t.List[t.Tuple[str, float]] = []
24+
edges: t.List[t.Tuple[str, str, float]] = []
25+
base_network_data = t.cast("TrivialNetworkIndicatorData", indicator_data).value
26+
for node in base_network_data.nodes:
27+
nodes.append(node.tuple)
28+
for edge in base_network_data.edges:
29+
edges.append(edge.tuple)
30+
result = {"nodes": nodes, "edges": edges}
31+
else:
32+
result = asdict(indicator_data)
33+
34+
return json.dumps(result, indent=2, sort_keys=True)
35+
36+
37+
def export_non_trivial_indicator_to_json(
38+
indicator_data: "NonTrivialIndicatorData",
39+
) -> str:
40+
result: t.Dict[str, t.Dict[str, t.Any]] = {}
41+
indicator_data = t.cast("NonTrivialIndicatorData", indicator_data)
42+
for key, values in indicator_data.value.items():
43+
result[key] = {}
44+
for base_data in values: # type: ignore
45+
value = t.cast("BaseData", base_data)
46+
sub_key = (
47+
f"{value.year}-{value.month:02}-raw"
48+
if value.is_raw
49+
else f"{value.year}-{value.month:02}"
50+
)
51+
result[key][sub_key] = value.value # type: ignore
52+
return json.dumps(result, indent=2, sort_keys=True)
53+
54+
55+
def export_indicator_to_json(
56+
indicator_data: t.Union[
57+
"TrivialIndicatorData",
58+
"NonTrivialIndicatorData",
59+
"TrivialNetworkIndicatorData",
60+
"NonTrivalNetworkInciatorData",
61+
],
62+
) -> str:
63+
if hasattr(indicator_data.value, "nodes"):
64+
indicator_data = t.cast("TrivialNetworkIndicatorData", indicator_data)
65+
return export_trivial_network_to_json(indicator_data)
66+
67+
if isinstance(indicator_data.value, dict):
68+
indicator_data = t.cast("NonTrivialIndicatorData", indicator_data)
69+
return export_non_trivial_indicator_to_json(indicator_data)
70+
71+
values = t.cast("t.List", indicator_data.value)
72+
warmup = values[0]
73+
result: t.Dict[str, t.Any] = {}
74+
if hasattr(warmup.value, "nodes") or warmup.value is None:
75+
base_data_list = t.cast("t.List[BaseData[BaseNetworkData]]", values)
76+
for base_data in base_data_list:
77+
key = (
78+
f"{base_data.year}-{base_data.month:02}-raw"
79+
if base_data.is_raw
80+
else f"{base_data.year}-{base_data.month:02}"
81+
)
82+
result[key] = asdict(base_data.value) if base_data.value else None
83+
else:
84+
values = t.cast("t.List[BaseData]", values)
85+
for value in values:
86+
key = (
87+
f"{value.year}-{value.month:02}-raw"
88+
if value.is_raw
89+
else f"{value.year}-{value.month:02}"
90+
)
91+
if isinstance(value.value, list) and hasattr(value.value[0], "name"):
92+
value.value = t.cast("t.List[NameAndValue]", value.value)
93+
result[key] = [v.tuple for v in value.value]
94+
else:
95+
result[key] = value.value
96+
97+
return json.dumps(result, indent=2, sort_keys=True)

opendigger_pycli/results/display.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,12 @@ def _handle_save_path(
129129
query_result = t.cast(RepoQueryResult, query_result)
130130
save_path = str(
131131
self.save_path
132-
/ f"{query_result.org_name}-{query_result.repo_name}-{self.mode}.html"
132+
/ f"repo-{query_result.org_name}-{query_result.repo_name}-{self.mode}.html"
133133
)
134134
else:
135135
query_result = t.cast(UserQueryResult, query_result)
136136
save_path = str(
137-
self.save_path / f"{query_result.username}-{self.mode}.html"
137+
self.save_path / f"user-{query_result.username}-{self.mode}.html"
138138
)
139139
return save_path
140140

opendigger_pycli/results/export.py

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,124 @@
1+
import json
2+
import typing as t
3+
14
from opendigger_pycli.console import CONSOLE
5+
from opendigger_pycli.console.utils import print_failed_query
6+
from opendigger_pycli.exporters.json_exporter import export_indicator_to_json
7+
8+
from .query import QueryResults, RepoQueryResult, UserQueryResult
29

3-
from .query import RepoQueryResult, UserQueryResult
10+
if t.TYPE_CHECKING:
11+
from pathlib import Path
12+
13+
from opendigger_pycli.exporters import (
14+
SURPPORTED_EXPORT_FORMAT_TYPE,
15+
SURPPORTED_EXPORT_FORMATS,
16+
)
417

518

619
class ExportResult:
7-
pass
20+
query_results: "QueryResults"
21+
format: "SURPPORTED_EXPORT_FORMAT_TYPE"
22+
save_path: "Path"
23+
is_split: bool
24+
25+
def __init__(
26+
self,
27+
query_results: "QueryResults",
28+
format: "SURPPORTED_EXPORT_FORMAT_TYPE",
29+
save_path: "Path",
30+
is_split: bool,
31+
**kwargs,
32+
) -> None:
33+
self.query_results = query_results
34+
self.format = format
35+
self.save_path = save_path
36+
self.is_split = is_split
37+
38+
def _query_result_to_json(
39+
self, query_result: t.Union["RepoQueryResult", "UserQueryResult"]
40+
) -> t.Dict[str, str]:
41+
result: t.Dict[str, str] = {}
42+
queried_indicators_data = query_result.queried_data
43+
failed_queries = query_result.failed_query
44+
for (
45+
indicator_name,
46+
indicator_dataloder_result,
47+
) in queried_indicators_data.items():
48+
indicator_name_formated = indicator_name.replace("_", " ").title()
49+
if (
50+
not indicator_dataloder_result.is_success
51+
or not indicator_dataloder_result.data
52+
):
53+
CONSOLE.print(
54+
f"[red]Failed to load {indicator_name_formated} indicator data: {indicator_dataloder_result.desc}"
55+
)
56+
continue
57+
58+
failed_query = failed_queries.get(indicator_name)
59+
if failed_query:
60+
if isinstance(failed_query, dict):
61+
for key, value in failed_query.items():
62+
if value is not None:
63+
continue
64+
print_failed_query(indicator_name + "." + key, value)
65+
else:
66+
print_failed_query(indicator_name, failed_query)
67+
68+
result[indicator_name] = export_indicator_to_json(
69+
indicator_dataloder_result.data
70+
)
71+
72+
return result
73+
74+
def _handle_save_path(
75+
self, query_result: t.Union["RepoQueryResult", "UserQueryResult"]
76+
) -> t.Optional["Path"]:
77+
if self.save_path is None:
78+
return None
79+
80+
self.save_path.mkdir(parents=True, exist_ok=True)
81+
if query_result.__class__ is RepoQueryResult:
82+
query_result = t.cast(RepoQueryResult, query_result)
83+
base_path = (
84+
self.save_path
85+
/ f"repo-{query_result.org_name}-{query_result.repo_name}"
86+
)
87+
else:
88+
query_result = t.cast(UserQueryResult, query_result)
89+
base_path = self.save_path / f"user-{query_result.username}"
90+
91+
if self.is_split:
92+
save_path = base_path / self.format
93+
save_path.mkdir(parents=True, exist_ok=True)
94+
else:
95+
if self.format == "json":
96+
save_path = base_path.with_suffix(".json")
97+
else:
98+
save_path = base_path.with_suffix(".html")
99+
100+
return save_path
101+
102+
def export(self) -> None:
103+
if not self.query_results:
104+
CONSOLE.print("[red]No results to export")
105+
return
106+
107+
for query_result in self.query_results:
108+
if self.format == "json":
109+
result = self._query_result_to_json(query_result)
110+
save_path = self._handle_save_path(query_result)
111+
if save_path is None:
112+
raise ValueError("Save path is None")
113+
if self.is_split:
114+
for indicator_name, indicator_json_data in result.items():
115+
save_path_splited = save_path / f"{indicator_name}.json"
116+
save_path_splited.write_text(indicator_json_data)
117+
CONSOLE.print(
118+
f"[green]Save Indicator {indicator_name} Data to {save_path}"
119+
)
120+
else:
121+
save_path.write_text(json.dumps(result, indent=2, sort_keys=True))
122+
CONSOLE.print(f"[green]Save All Indicator Data to {save_path}")
123+
else:
124+
raise NotImplementedError

0 commit comments

Comments
 (0)