Coverage for pipxl/deptree.py: 98%

1# SPDX-FileCopyrightText: 2022-present Jeroen van Zundert <mail@jeroenvanzundert.nl>

2#

3# SPDX-License-Identifier: MIT

4

5 from __future__ import annotations

6

7 import textwrap

8 from pathlib import Path

9

10 from pipxl.data import ReqFileEntry

11 from pipxl.resolver import pip_resolve

12

13

14 def deptree(files_in: list[Path] | None = None, package_spec: list[str] | None = None) -> str:

15 reqs, _ = pip_resolve(files_in, package_spec)

16

17 top_level = [req for req in reqs if req.requested]

18

19 output = ""

20 for req in top_level:

21 output += f"{req.name}=={req.version}\n"

22 output += textwrap.indent(_deps_to_string(req.requires, reqs), prefix="\t")

23

24 return output

25

26

27 def deptreerev(files_in: list[Path] | None = None, package_spec: list[str] | None = None) -> str:

28 reqs, _ = pip_resolve(files_in, package_spec)

29

30 output = ""

31 for req in reqs:

32 output += f"{req.name}=={req.version}\n"

33 output += textwrap.indent(_reverse_deps_to_string(req.required_by, reqs), prefix="\t")

34 return output

35

36

37 def _deps_to_string(deps: dict[str, str], reqs: list[ReqFileEntry]) -> str:

38 output = ""

39 for dep_name, dep_spec in deps.items():

40 try:

41 info = next(t for t in reqs if t.name == dep_name)

42 output += f"{dep_name}=={info.version} [{dep_spec}]\n"

43 output += textwrap.indent(_deps_to_string(info.requires, reqs), prefix="\t")

44 except StopIteration:

45 # StopIteration is raised if the dependency is not found in reqs.

46 # If a package is not in reqs, it means that it would not be installed by pip.

47 # This is usually the case for dependencies that are only required on other Python versions

48 # or platforms, but not on the specific combination this function is run.

49 continue

50

51 return output

52

53

54 def _reverse_deps_to_string(rev_deps: dict[str, str], reqs: list[ReqFileEntry]) -> str:

55 output = ""

56 for dep_name, dep_spec in rev_deps.items():

57 info = next(t for t in reqs if t.name == dep_name) 57 ↛ exitline 57 didn't finish the generator expression on line 57

58 output += f"{dep_name}=={info.version} [{dep_spec}]\n"

59 output += textwrap.indent(_reverse_deps_to_string(info.required_by, reqs), prefix="\t")

60 return output