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

1"""Package and project report workflow.""" 

2 

3from dataclasses import dataclass 

4from pathlib import Path 

5 

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 

17 

18 

19@dataclass(frozen=True) 

20class ReportOutput: 

21 content: str 

22 format_name: str 

23 target: Path 

24 

25 

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)