Skip to content
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
5 changes: 3 additions & 2 deletions src/copilot_usage/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
render_summary,
session_display_name,
)
from copilot_usage.vscode_parser import get_vscode_summary
from copilot_usage.vscode_report import render_vscode_summary

type _View = Literal["home", "detail", "cost"]

Expand Down Expand Up @@ -661,6 +659,9 @@ def live(ctx: click.Context, path: Path | None) -> None:
)
def vscode(vscode_logs: Path | None) -> None:
"""Show usage from VS Code Copilot Chat logs."""
from copilot_usage.vscode_parser import get_vscode_summary
from copilot_usage.vscode_report import render_vscode_summary

_print_version_header()
summary = get_vscode_summary(vscode_logs)
if summary.total_requests == 0:
Expand Down
10 changes: 8 additions & 2 deletions tests/copilot_usage/test_vscode_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,10 @@ def test_all_files_error_shows_correct_message(self) -> None:
summary = VSCodeLogSummary(
log_files_found=2, log_files_parsed=0, total_requests=0
)
with patch("copilot_usage.cli.get_vscode_summary", return_value=summary):
with patch(
"copilot_usage.vscode_parser.get_vscode_summary",
return_value=summary,
):
runner = CliRunner()
result = runner.invoke(main, ["vscode"])
assert result.exit_code == 1
Expand All @@ -870,7 +873,10 @@ def test_no_files_shows_no_requests_message(self) -> None:
summary = VSCodeLogSummary(
log_files_found=0, log_files_parsed=0, total_requests=0
)
with patch("copilot_usage.cli.get_vscode_summary", return_value=summary):
with patch(
"copilot_usage.vscode_parser.get_vscode_summary",
return_value=summary,
):
runner = CliRunner()
result = runner.invoke(main, ["vscode"])
assert result.exit_code == 1
Expand Down
31 changes: 31 additions & 0 deletions tests/test_packaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,34 @@ def test_ccreq_re_not_in_vscode_parser_all() -> None:

dunder_all = vscode_mod.__all__
assert "CCREQ_RE" not in dunder_all, "CCREQ_RE must not be in vscode_parser.__all__"


def test_cli_does_not_import_vscode_modules_at_module_level() -> None:
"""``vscode_parser`` and ``vscode_report`` must be lazy-imported.

Regression guard for issue #890: these modules are only needed by the
``vscode`` subcommand and should not be imported at ``cli`` module level
to avoid loading ``re``, ``stat``, ``types``, and running ``re.compile``
on every invocation.
"""
import sys

original_sys_modules = sys.modules.copy()
try:
# Purge any previously imported copilot_usage modules so we get a
# clean import of cli.
for mod_name in list(sys.modules):
if mod_name == "copilot_usage" or mod_name.startswith("copilot_usage."):
del sys.modules[mod_name]

importlib.import_module("copilot_usage.cli")

assert "copilot_usage.vscode_parser" not in sys.modules, (
"vscode_parser must not be imported at cli module level"
)
assert "copilot_usage.vscode_report" not in sys.modules, (
"vscode_report must not be imported at cli module level"
)
finally:
sys.modules.clear()
sys.modules.update(original_sys_modules)
Loading