11"""Check whether the uv CLI is available."""
22
3+ from __future__ import annotations
4+
5+ from typing import TYPE_CHECKING
6+
37from packaging .requirements import InvalidRequirement , Requirement
48
59from usethis ._backend .uv .call import call_uv_subprocess
610from usethis ._backend .uv .errors import UVSubprocessFailedError
7- from usethis ._file .pyproject_toml .errors import PyprojectTOMLError
811from usethis ._file .pyproject_toml .io_ import PyprojectTOMLManager
12+ from usethis ._types .deps import Dependency
13+
14+ if TYPE_CHECKING :
15+ from tomlkit import TOMLDocument
916
1017
1118def is_uv_available () -> bool :
@@ -19,41 +26,76 @@ def is_uv_available() -> bool:
1926
2027
2128def _is_uv_a_dep () -> bool :
22- """Check if uv is declared as a project dependency or in a dependency group."""
29+ """Check if uv is declared as a project dependency or in a dependency group.
30+
31+ Note: we cannot use the higher-level ``get_project_deps`` /
32+ ``get_dep_groups`` helpers from ``usethis._deps`` because the
33+ ``_backend`` layer must not import from the ``_deps`` layer
34+ (enforced by import-linter). The logic below mirrors those
35+ functions using only ``_file`` and ``_types`` which are
36+ permitted.
37+ """
38+ uv_dep = Dependency (name = "uv" )
39+
2340 try :
24- content = PyprojectTOMLManager ().get ()
25- except ( FileNotFoundError , PyprojectTOMLError ) :
41+ pyproject = PyprojectTOMLManager ().get ()
42+ except Exception :
2643 return False
2744
28- # Gather all requirement strings from both sources
29- req_strs : list [str ] = []
45+ all_deps = _get_project_deps (pyproject ) + _get_dep_group_deps (pyproject )
46+ return any (dep .name == uv_dep .name for dep in all_deps )
47+
3048
31- # Collect from project dependencies
49+ def _get_project_deps (pyproject : TOMLDocument ) -> list [Dependency ]:
50+ """Extract dependencies from the [project.dependencies] section."""
3251 try :
33- project_section = content ["project" ]
34- if isinstance (project_section , dict ):
35- deps = project_section ["dependencies" ]
36- if isinstance (deps , list ):
37- req_strs .extend (s for s in deps if isinstance (s , str ))
38- except (KeyError , TypeError ):
39- pass
40-
41- # Collect from dependency groups
52+ project_section = pyproject ["project" ]
53+ except KeyError :
54+ return []
55+
56+ if not isinstance (project_section , dict ):
57+ return []
58+
4259 try :
43- groups = content ["dependency-groups" ]
44- if isinstance (groups , dict ):
45- for group_deps in groups .values ():
46- if isinstance (group_deps , list ):
47- req_strs .extend (s for s in group_deps if isinstance (s , str ))
48- except (KeyError , TypeError ):
49- pass
50-
51- # Check if any requirement is for 'uv'
52- for req_str in req_strs :
60+ dep_section = project_section ["dependencies" ]
61+ except KeyError :
62+ return []
63+
64+ if not isinstance (dep_section , list ):
65+ return []
66+
67+ deps : list [Dependency ] = []
68+ for item in dep_section :
69+ if not isinstance (item , str ):
70+ continue
5371 try :
54- if Requirement (req_str ).name == "uv" :
55- return True
72+ req = Requirement (item )
5673 except InvalidRequirement :
5774 continue
75+ deps .append (Dependency (name = req .name , extras = frozenset (req .extras )))
76+ return deps
77+
5878
59- return False
79+ def _get_dep_group_deps (pyproject : TOMLDocument ) -> list [Dependency ]:
80+ """Extract dependencies from all [dependency-groups] sections."""
81+ try :
82+ groups_section = pyproject ["dependency-groups" ]
83+ except KeyError :
84+ return []
85+
86+ if not isinstance (groups_section , dict ):
87+ return []
88+
89+ deps : list [Dependency ] = []
90+ for group_items in groups_section .values ():
91+ if not isinstance (group_items , list ):
92+ continue
93+ for item in group_items :
94+ if not isinstance (item , str ):
95+ continue
96+ try :
97+ req = Requirement (item )
98+ except InvalidRequirement :
99+ continue
100+ deps .append (Dependency (name = req .name , extras = frozenset (req .extras )))
101+ return deps
0 commit comments