Skip to content

Commit 424eb10

Browse files
committed
cleanup old APIs
1 parent c9689d6 commit 424eb10

11 files changed

Lines changed: 251 additions & 1344 deletions

File tree

src/wily/commands/graph.py

Lines changed: 122 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,18 @@
44
Draw graph in HTML for a specific metric.
55
"""
66

7+
import sys
78
from pathlib import Path
89

910
import plotly.graph_objs as go
1011
import plotly.offline
1112

1213
from wily import format_datetime, logger
14+
from wily.backend import WilyIndex
15+
from wily.cache import get_default_metrics_path
1316
from wily.config.types import WilyConfig
17+
from wily.defaults import DEFAULT_ARCHIVER
1418
from wily.operators import Metric, resolve_metric, resolve_metric_as_tuple
15-
from wily.state import State
1619

1720

1821
def metric_parts(metric):
@@ -55,7 +58,13 @@ def graph(
5558
logger.debug("Running graph command")
5659

5760
data = []
58-
state = State(config)
61+
archiver = config.archiver or DEFAULT_ARCHIVER
62+
63+
# Get path to parquet index
64+
parquet_path = get_default_metrics_path(config, archiver)
65+
if not Path(parquet_path).exists():
66+
logger.error("Wily cache not found. Run 'wily build' first.")
67+
sys.exit(1)
5968

6069
if x_axis is None:
6170
x_axis = "history"
@@ -66,80 +75,123 @@ def graph(
6675
metrics_list = metrics.split(",")
6776

6877
y_metric = resolve_metric(metrics_list[0])
69-
70-
if not aggregate:
71-
tracked_files = set()
72-
for rev in state.index[state.default_archiver].revisions:
73-
tracked_files.update(rev.revision.tracked_files)
74-
paths = tuple(tracked_file for tracked_file in tracked_files if any(path_startswith(tracked_file, p) for p in path)) or path
75-
else:
76-
paths = path
77-
78-
title = f"{x_axis.capitalize()} of {y_metric.description}{(' for ' + paths[0]) if len(paths) == 1 else ''}{' aggregated' if aggregate else ''}"
7978
operator, key = metric_parts(metrics_list[0])
79+
8080
z_axis: Metric | str
8181
if len(metrics_list) == 1: # only y-axis
8282
z_axis = z_operator = z_key = ""
8383
else:
8484
z_axis = resolve_metric(metrics_list[1])
8585
z_operator, z_key = metric_parts(metrics_list[1])
86-
for path_ in paths:
87-
current_path = str(Path(path_))
88-
x = []
89-
y = []
90-
z = []
91-
labels = []
92-
last_y = None
93-
for rev in state.index[state.default_archiver].revisions:
94-
try:
95-
val = rev.get(config, state.default_archiver, operator, current_path, key)
96-
if val != last_y or not changes:
97-
y.append(val)
98-
if z_axis:
99-
z.append(
100-
rev.get(
101-
config,
102-
state.default_archiver,
103-
z_operator,
104-
current_path,
105-
z_key,
106-
)
107-
)
108-
if x_axis == "history":
109-
x.append(format_datetime(rev.revision.date))
110-
else:
111-
x.append(
112-
rev.get(
113-
config,
114-
state.default_archiver,
115-
x_operator,
116-
current_path,
117-
x_key,
118-
)
119-
)
120-
labels.append(f"{rev.revision.author_name} <br>{rev.revision.message}")
121-
last_y = val
122-
except KeyError:
123-
# missing data
124-
pass
125-
126-
# Create traces
127-
trace = go.Scatter(
128-
x=x,
129-
y=y,
130-
mode="lines+markers+text" if text else "lines+markers",
131-
name=f"{path_}",
132-
ids=state.index[state.default_archiver].revision_keys,
133-
text=labels,
134-
marker={
135-
"size": 0 if not z_axis else z,
136-
"color": list(range(len(y))),
137-
# "colorscale": "Viridis",
138-
},
139-
xcalendar="gregorian",
140-
hoveron="points+fills",
141-
) # type: ignore
142-
data.append(trace)
86+
87+
# Initialize title with a default - will be updated once we know paths
88+
title = f"{x_axis.capitalize()} of {y_metric.description}"
89+
90+
with WilyIndex(parquet_path, [operator]) as index:
91+
# Get all rows and organize by revision
92+
all_rows = list(index)
93+
if not all_rows:
94+
logger.error("No data in cache. Run 'wily build' first.")
95+
sys.exit(1)
96+
97+
# Get unique revisions sorted by date (oldest first for graph)
98+
revisions: dict[str, dict] = {}
99+
for row in all_rows:
100+
rev_key = row["revision"]
101+
if rev_key not in revisions:
102+
revisions[rev_key] = {
103+
"key": rev_key,
104+
"author": row.get("revision_author", "Unknown"),
105+
"message": row.get("revision_message", ""),
106+
"date": row.get("revision_date", 0),
107+
}
108+
sorted_revisions = sorted(revisions.values(), key=lambda r: r["date"])
109+
revision_keys = [r["key"] for r in sorted_revisions]
110+
111+
# Build lookup: {revision: {path: row_data}}
112+
revision_data: dict[str, dict[str, dict]] = {}
113+
tracked_files: set[str] = set()
114+
for row in all_rows:
115+
rev_key = row["revision"]
116+
file_path = row["path"]
117+
if rev_key not in revision_data:
118+
revision_data[rev_key] = {}
119+
revision_data[rev_key][file_path] = row
120+
tracked_files.add(file_path)
121+
122+
if not aggregate:
123+
paths = tuple(
124+
tracked_file
125+
for tracked_file in tracked_files
126+
if any(path_startswith(tracked_file, p) or tracked_file.startswith(p) for p in path)
127+
) or path
128+
else:
129+
paths = path
130+
131+
title = f"{x_axis.capitalize()} of {y_metric.description}{(' for ' + paths[0]) if len(paths) == 1 else ''}{' aggregated' if aggregate else ''}"
132+
133+
for path_ in paths:
134+
current_path = str(Path(path_))
135+
x = []
136+
y = []
137+
z = []
138+
labels = []
139+
last_y = None
140+
141+
for rev in sorted_revisions:
142+
rev_key = rev["key"]
143+
rev_paths = revision_data.get(rev_key, {})
144+
145+
# Try exact match or path starting with current_path
146+
row = rev_paths.get(current_path)
147+
if row is None:
148+
# Try matching path that starts with current_path (for directories)
149+
for p, r in rev_paths.items():
150+
if p.startswith(current_path) or current_path.startswith(p):
151+
row = r
152+
break
153+
154+
if row is None:
155+
continue
156+
157+
try:
158+
val = row.get(key)
159+
if val is None:
160+
continue
161+
162+
if val != last_y or not changes:
163+
y.append(val)
164+
if z_axis:
165+
z_val = row.get(z_key)
166+
z.append(z_val if z_val is not None else 0)
167+
if x_axis == "history":
168+
x.append(format_datetime(rev["date"]))
169+
else:
170+
x_val = row.get(x_key)
171+
x.append(x_val if x_val is not None else 0)
172+
labels.append(f"{rev['author']} <br>{rev['message']}")
173+
last_y = val
174+
except KeyError:
175+
# missing data
176+
pass
177+
178+
# Create traces
179+
trace = go.Scatter(
180+
x=x,
181+
y=y,
182+
mode="lines+markers+text" if text else "lines+markers",
183+
name=f"{path_}",
184+
ids=revision_keys,
185+
text=labels,
186+
marker={
187+
"size": 0 if not z_axis else z,
188+
"color": list(range(len(y))),
189+
},
190+
xcalendar="gregorian",
191+
hoveron="points+fills",
192+
) # type: ignore
193+
data.append(trace)
194+
143195
if output:
144196
filename = output
145197
auto_open = False
@@ -159,3 +211,4 @@ def graph(
159211
filename=filename,
160212
include_plotlyjs=plotlyjs, # type: ignore
161213
)
214+

src/wily/commands/index.py

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44
Print information about the wily cache and what is in the index.
55
"""
66

7+
import sys
8+
from pathlib import Path
9+
710
from wily import MAX_MESSAGE_WIDTH, format_date, format_revision, logger
11+
from wily.backend import WilyIndex
12+
from wily.cache import get_default_metrics_path, list_archivers
813
from wily.config.types import WilyConfig
914
from wily.defaults import DEFAULT_TABLE_STYLE
1015
from wily.helper import print_table
11-
from wily.state import State
1216

1317

1418
def index(
@@ -25,35 +29,61 @@ def index(
2529
:param wrap: Wrap long lines
2630
:param table_style: Table box style
2731
"""
28-
state = State(config=config)
29-
logger.debug("Running show command")
32+
logger.debug("Running index command")
3033
logger.info("--------Configuration---------")
3134
logger.info("Path: %s", config.path)
3235
logger.info("Archiver: %s", config.archiver)
3336
logger.info("Operators: %s", config.operators)
3437
logger.info("")
3538
logger.info("-----------History------------")
3639

40+
archivers = list_archivers(config)
41+
if not archivers:
42+
logger.error("No wily cache found. Run 'wily build' first.")
43+
sys.exit(1)
44+
3745
data: list[tuple[str, ...]] = []
38-
for archiver in state.archivers:
39-
for rev in state.index[archiver].revisions:
40-
if include_message:
41-
data.append(
42-
(
43-
format_revision(rev.revision.key),
44-
str(rev.revision.author_name),
45-
rev.revision.message[:MAX_MESSAGE_WIDTH],
46-
format_date(rev.revision.date),
46+
for archiver in archivers:
47+
parquet_path = get_default_metrics_path(config, archiver)
48+
if not Path(parquet_path).exists():
49+
continue
50+
51+
# Get unique revisions from parquet
52+
with WilyIndex(parquet_path, []) as idx:
53+
# Collect unique revisions (keyed by revision hash)
54+
revisions: dict[str, dict] = {}
55+
for row in idx:
56+
rev_key = row["revision"]
57+
if rev_key not in revisions:
58+
revisions[rev_key] = {
59+
"key": rev_key,
60+
"author": row.get("revision_author", "Unknown"),
61+
"message": row.get("revision_message", ""),
62+
"date": row.get("revision_date", 0),
63+
}
64+
65+
# Sort by date descending (most recent first)
66+
sorted_revisions = sorted(revisions.values(), key=lambda r: r["date"], reverse=True)
67+
68+
for rev in sorted_revisions:
69+
if include_message:
70+
message = rev["message"] or ""
71+
data.append(
72+
(
73+
format_revision(rev["key"]),
74+
str(rev["author"]),
75+
message[:MAX_MESSAGE_WIDTH],
76+
format_date(rev["date"]),
77+
)
4778
)
48-
)
49-
else:
50-
data.append(
51-
(
52-
format_revision(rev.revision.key),
53-
str(rev.revision.author_name),
54-
format_date(rev.revision.date),
79+
else:
80+
data.append(
81+
(
82+
format_revision(rev["key"]),
83+
str(rev["author"]),
84+
format_date(rev["date"]),
85+
)
5586
)
56-
)
5787

5888
headers: tuple[str, ...]
5989
if include_message:

0 commit comments

Comments
 (0)