Coverage for src/secchi/services/resolver.py: 78%

27 statements  

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

1"""Resolve direct CLI package names into registry package references.""" 

2 

3from __future__ import annotations 

4 

5from secchi.errors import ConfigError 

6from secchi.models import PackageRef, Registry 

7from secchi.services.search import PackageSearchService 

8 

9 

10def parse_package_spec(spec: str, registry: str | None = None) -> PackageRef: 

11 """Resolve ``name`` or ``registry:name`` with PyPI as the safe default.""" 

12 if ":" in spec and registry is None: 

13 candidate_registry, name = spec.split(":", 1) 

14 try: 

15 return PackageRef(name=name, registry=Registry(candidate_registry)) 

16 except ValueError: 

17 pass 

18 try: 

19 selected = Registry(registry) if registry else Registry.PYPI 

20 except ValueError as exc: 

21 supported = ", ".join(item.value for item in Registry) 

22 raise ConfigError( 

23 f"Unknown registry '{registry}'. Supported: {supported}" 

24 ) from exc 

25 return PackageRef(name=spec, registry=selected) 

26 

27 

28async def resolve_package( 

29 spec: str, 

30 *, 

31 configured_refs: list[PackageRef] | None = None, 

32 registry: str | None = None, 

33) -> list[PackageRef]: 

34 """Resolve a package spec from config first, then exact registry matches.""" 

35 explicit = parse_package_spec(spec, registry) 

36 if registry is not None or ":" in spec: 

37 return [explicit] 

38 

39 configured = [ 

40 ref for ref in configured_refs or [] if ref.name.casefold() == spec.casefold() 

41 ] 

42 if configured: 

43 return configured 

44 

45 matches = await PackageSearchService().search(spec) 

46 exact = [result for result in matches if result.exact] 

47 return [PackageRef(result.name, result.registry) for result in exact]