Coverage for src/secchi/workflows/dashboard.py: 60%

47 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-04 22:15 +0000

1"""Dashboard project-resolution workflow.""" 

2 

3from dataclasses import dataclass 

4from pathlib import Path 

5 

6from secchi.config import find_config, load_project, load_projects 

7from secchi.errors import ConfigError, PackageNotFoundError 

8from secchi.models import PackageRef, Project 

9from secchi.services.resolver import parse_package_spec, resolve_package 

10 

11 

12@dataclass(frozen=True) 

13class DashboardRequest: 

14 project: Project 

15 config_path: Path 

16 workspace: list[Project] | None 

17 refresh: bool 

18 

19 

20def _workspace_project(config_path: Path) -> Project: 

21 projects = load_projects(config_path) 

22 refs: list[PackageRef] = [] 

23 for project in projects: 

24 refs.extend( 

25 PackageRef( 

26 ref.name, 

27 ref.registry, 

28 ref.favorite or project.favorite, 

29 project.name, 

30 ) 

31 for ref in project.packages 

32 ) 

33 return Project( 

34 name="Workspace", description="Configured Secchi workspace", packages=refs 

35 ) 

36 

37 

38async def run( 

39 *, 

40 package: str | None = None, 

41 registry: str | None = None, 

42 project_name: str | None = None, 

43 config: str | None = None, 

44 refresh: bool = False, 

45) -> DashboardRequest: 

46 config_path = find_config(config) 

47 workspace: list[Project] | None = None 

48 if package: 

49 ref = parse_package_spec(package, registry) 

50 configured = None 

51 if config_path and registry is None: 

52 for candidate in load_projects(config_path): 

53 if candidate.name.lower() == package.lower() or any( 

54 item.name.lower() == package.lower() for item in candidate.packages 

55 ): 

56 configured = candidate 

57 break 

58 if configured is not None: 

59 project = configured 

60 elif registry is None: 

61 refs = await resolve_package(package) 

62 if not refs: 

63 raise PackageNotFoundError( 

64 f"No exact package named '{package}' was found across registries." 

65 ) 

66 project = Project(name=ref.name, packages=refs) 

67 else: 

68 project = Project(name=ref.name, packages=[ref]) 

69 config_path = config_path or (Path.cwd() / "secchi.toml") 

70 else: 

71 if not config_path: 

72 raise ConfigError( 

73 "No config found. Use 'secchi dashboard PACKAGE' or create secchi.toml." 

74 ) 

75 if project_name: 

76 project = load_project(config_path, project_name) 

77 else: 

78 workspace = load_projects(config_path) 

79 project = _workspace_project(config_path) 

80 if not project.packages: 

81 raise ConfigError("The selected workspace has no packages.") 

82 return DashboardRequest( 

83 project, config_path, workspace if not package else None, refresh 

84 )