Coverage for src/secchi/workflows/report.py: 36%
39 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 23:28 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 23:28 +0000
1"""Package and project report workflow."""
3from dataclasses import dataclass
4from pathlib import Path
6from secchi.config import find_config, load_project
7from secchi.errors import ConfigError, ReportError
8from secchi.renderers.reports import (
9 build_project_report,
10 default_report_path,
11 render_project_report,
12 render_report,
13)
14from secchi.services.intelligence import PackageIntelligenceService
15from secchi.services.resolver import parse_package_spec
16from secchi.workflows.common import require_package
19@dataclass(frozen=True)
20class ReportOutput:
21 content: str
22 format_name: str
23 target: Path
26async def run(
27 *,
28 package: str | None = None,
29 project_name: str | None = None,
30 config: str | None = None,
31 registry: str | None = None,
32 format_name: str = "json",
33 output: str | None = None,
34 refresh: bool = False,
35 security_refresh: bool = False,
36 service: PackageIntelligenceService | None = None,
37) -> ReportOutput:
38 normalized_format = "md" if format_name == "markdown" else format_name
39 if project_name:
40 config_path = find_config(config)
41 if not config_path:
42 raise ConfigError("No config found for project report.")
43 project = load_project(config_path, project_name)
44 pipeline = service or PackageIntelligenceService()
45 if security_refresh:
46 intelligence = await pipeline.fetch_project(
47 project.packages,
48 force_refresh=refresh,
49 force_security_refresh=True,
50 )
51 else:
52 intelligence = await pipeline.fetch_project(
53 project.packages, force_refresh=refresh
54 )
55 project_report = build_project_report(project, intelligence.results)
56 content = render_project_report(normalized_format, project_report)
57 subject = project.title or project.name
58 target = (
59 Path(output)
60 if output and output != "-"
61 else default_report_path(subject, normalized_format, project=True)
62 )
63 else:
64 if not package:
65 raise ReportError(
66 "Provide a package name or --project PROJECT for a report."
67 )
68 ref = parse_package_spec(package, registry)
69 if security_refresh:
70 result = await require_package(
71 ref, refresh, security_refresh=True, service=service
72 )
73 elif service is None:
74 result = await require_package(ref, refresh)
75 else:
76 result = await require_package(ref, refresh, service=service)
77 content = render_report(
78 normalized_format,
79 result.info,
80 result.derived,
81 ref,
82 ref.name,
83 result.warnings,
84 )
85 target = (
86 Path(output)
87 if output and output != "-"
88 else default_report_path(ref.name, normalized_format)
89 )
90 return ReportOutput(content, normalized_format, target)