Coverage for src/iso_freeze/sync.py: 67%

24 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-08-01 11:38 +0200

1"""Sync environment with pip install --report output.""" 

2 

3import json 

4from pathlib import Path 

5from typing import Optional 

6 

7from iso_freeze.lib import PyPackage, run_pip 

8 

9 

10def sync(requirements: list[PyPackage], python_exec: Path) -> None: 

11 """Sync environment with pip install --report output. 

12 

13 Arguments: 

14 requirements -- List of dependencies to sync with (list[PyPackage]) 

15 python_exec -- Path to Python interpreter to use (Path) 

16 """ 

17 installed_packages: list[PyPackage] = get_installed_packages( 

18 pip_list_output=get_pip_list_output(python_exec=python_exec) 

19 ) 

20 additional_packages: Optional[list[str]] = get_additional_packages( 

21 installed_packages=installed_packages, 

22 to_install=requirements, 

23 ) 

24 if additional_packages: 

25 remove_additional_packages( 

26 additional_packages=additional_packages, 

27 python_exec=python_exec, 

28 ) 

29 install_pip_report_output( 

30 to_install=format_package_list(packages=requirements), 

31 python_exec=python_exec, 

32 ) 

33 

34 

35def get_pip_list_output(python_exec: Path) -> list[dict[str, str]]: 

36 """Run pip list --format json output. 

37 

38 Arguments: 

39 python_exec -- Path to Python interpreter to use (Path) 

40 

41 Returns: 

42 Pip list output as JSON (list[dict[str, str]]) 

43 """ 

44 return json.loads( 

45 run_pip( 

46 command=[ 

47 python_exec, 

48 "-m", 

49 "pip", 

50 "list", 

51 "--format", 

52 "json", 

53 "--exclude-editable", 

54 ], 

55 check_output=True, 

56 ) 

57 ) 

58 

59 

60def get_installed_packages(pip_list_output: list[dict[str, str]]) -> list[PyPackage]: 

61 """Return pip list output as PyPackage objects. 

62 

63 Arguments: 

64 pip_list_output -- Pip list output as JSON (list[dict[str, str]]) 

65 

66 Returns: 

67 List of packages from current environment (list[PyPackage]) 

68 """ 

69 return [ 

70 PyPackage(name=package["name"], version=package["version"]) 

71 for package in pip_list_output 

72 ] 

73 

74 

75def get_additional_packages( 

76 installed_packages: list[PyPackage], to_install: list[PyPackage] 

77) -> Optional[list[str]]: 

78 """Filter out pip packages installed in current environment but not in pip report. 

79 

80 Arguments: 

81 installed_packages -- List of packages installed in current environment 

82 (list[PyPackage]) 

83 to_install -- List of packages taken from pip install --report 

84 (list[PyPackages]) 

85 

86 Returns: 

87 List of installed packages not in pip report (Optional[list[str]]) 

88 """ 

89 # Create two lists with packages names only for easy comparison 

90 installed_names_only: list[str] = [package.name for package in installed_packages] 

91 to_install_names_only: list[str] = [package.name for package in to_install] 

92 return [ 

93 package 

94 for package in installed_names_only 

95 if package not in to_install_names_only 

96 # Don't remove default packages 

97 if package not in ["pip", "setuptools"] 

98 ] 

99 

100 

101def remove_additional_packages( 

102 additional_packages: Optional[list[str]], python_exec: Path 

103) -> None: 

104 """Remove installed packages not included in pip install --report. 

105 

106 Arguments: 

107 additional_packages -- List of packages to remove (Optional[list[str]]) 

108 python_exec -- Path to Python executable (Path) 

109 """ 

110 run_pip( 

111 command=[python_exec, "-m", "pip", "uninstall", "-y", *additional_packages], 

112 check_output=False, 

113 ) 

114 

115 

116def format_package_list(packages: list[PyPackage]) -> list[str]: 

117 """Create list in the format ["package1==versionX"] to pass that to `pip install`. 

118 

119 Arguments: 

120 packages -- List of packages to install (list[PyPackage]) 

121 

122 Returns: 

123 List of packages to be passed to pip install (list[str]) 

124 """ 

125 return [f"{package.name}=={package.version}" for package in packages] 

126 

127 

128def install_pip_report_output(to_install: list[str], python_exec: Path) -> None: 

129 """Install packages with pinned versions from pip install --report output. 

130 

131 Arguments: 

132 to_install -- List of packages taken from pip install --report (list[str]) 

133 python_exec -- Path to Python executable (Path) 

134 """ 

135 run_pip( 

136 command=[python_exec, "-m", "pip", "install", "--upgrade", *to_install], 

137 check_output=False, 

138 )