Coverage for /home/runner/work/coxbuild/coxbuild/src/coxbuild/extensions/python/package.py: 0%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

77 statements  

1import shutil 

2from pathlib import Path 

3from typing import Tuple 

4 

5from coxbuild.schema import depend, group, precond, run, task 

6 

7from .. import ProjectSettings, withProject 

8from . import Settings, grouped, withSettings 

9 

10subgrouped = group("package") 

11 

12 

13def loadFromRequirements(lines: list[str]) -> dict[str, str]: 

14 results: dict[str, str] = {} 

15 for line in lines: 

16 sl = line.split("==") 

17 if len(sl) != 2: 

18 continue 

19 k, v = sl 

20 results[k] = v 

21 return results 

22 

23 

24def installedPackages() -> dict[str, str]: 

25 output = run(["python", "-m", "pip", "freeze"], pipe=True).stdout 

26 return loadFromRequirements(output.splitlines()) 

27 

28 

29def hasPackages(packages: dict[str, str]): 

30 installed = installedPackages() 

31 for p, v in packages.items(): 

32 if p not in installed or (v != "*" and installed[p] != v): 

33 return False 

34 return True 

35 

36 

37def upgradePackages(*packages: str): 

38 run(["python", "-m", "pip", "install", "--upgrade", *packages], retry=3) 

39 

40 

41@grouped 

42@subgrouped 

43@withSettings 

44@task 

45def restore(requirements: Path | None = None, *, settings: Settings): 

46 """Restore Python packages from requirements.txt.""" 

47 run(["python", "-m", "pip", "install", "-r", 

48 str(requirements or settings.requirements)], retry=3) 

49 

50 

51@restore.precond 

52def needRestore(*, settings: Settings): 

53 req: Path = settings.requirements 

54 if not req.exists(): 

55 return False 

56 reqs = loadFromRequirements(req.read_text("utf-8").splitlines()) 

57 return not hasPackages(reqs) 

58 

59 

60@grouped 

61@subgrouped 

62@precond(lambda: not hasPackages({"build": "*", "twine": "*"})) 

63@task 

64def prebuild(): 

65 """Restore Python package build tools.""" 

66 upgradePackages("build", "twine") 

67 

68 

69@grouped 

70@subgrouped 

71@withProject 

72@depend(prebuild) 

73@task 

74def build(src: Path | None = None, dist: Path | None = None, *, project: ProjectSettings): 

75 """Build Python package.""" 

76 src = src or project.src 

77 run(["python", "-m", "build", "-o", str(dist or project.package)], 

78 cwd=src) 

79 for item in src.glob("*.egg-info"): 

80 if not item.is_dir(): 

81 continue 

82 shutil.rmtree(item) 

83 

84 

85@grouped 

86@subgrouped 

87@withProject 

88@task 

89def installBuilt(dist: Path | None = None, *, project: ProjectSettings): 

90 """Install the built package.""" 

91 run(["python", "-m", "pip", "install", 

92 str(list((dist or project.package).glob("*.whl"))[0])]) 

93 

94 

95@grouped 

96@subgrouped 

97@withProject 

98@task 

99def uninstallBuilt(dist: Path | None = None, *, project: ProjectSettings): 

100 """Uninstall the built package.""" 

101 run(["python", "-m", "pip", "uninstall", 

102 str(list((dist or project.package).glob("*.whl"))[0]), "-y"]) 

103 

104 

105@grouped 

106@subgrouped 

107@withProject 

108@depend(build) 

109@task 

110def deploy(dist: Path | None = None, *, project: ProjectSettings): 

111 """Upload the package to PYPI.""" 

112 run(["python", "-m", "twine", "upload", 

113 "--skip-existing", "--repository", "pypi", str(dist or project.package) + "/*"])