Coverage for pipxl/cli/__init__.py: 100%

39 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-28 20:56 +0100

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

2# 

3# SPDX-License-Identifier: MIT 

4 

5from pathlib import Path 

6from typing import Any, Optional 

7 

8import typer 

9 

10from pipxl import compile as pipxl_compile 

11from pipxl import deptree as pipxl_deptree 

12from pipxl import deptreerev as pipxl_deptreerev 

13from pipxl import license as pipxl_license 

14from pipxl import sync as pipxl_sync 

15from pipxl.__about__ import __version__ 

16 

17PACKAGE_SPEC_HELP = "Package specification as accepted by pip, for instance `pandas==1.4.3 httpx=0.23.1`." 

18R_HELP = "Requirements file. Can specify this argument multiple times to take the union of the requirements files." 

19 

20app = typer.Typer() 

21 

22 

23@app.command() 

24def compile( 

25 package_spec: Optional[list[str]] = typer.Argument(None, help=PACKAGE_SPEC_HELP), 

26 r: list[Path] = typer.Option(None, help=R_HELP), 

27 o: Path = typer.Option(Path("requirements_lock.txt")), 

28) -> None: 

29 """ 

30 Create pip-compliant requirements file with dependencies and dependencies-of-dependencies pinned. 

31 

32 Use `pipxl sync` for updating your Python (virtual) environment with this requirements file. 

33 """ 

34 pipxl_compile(r, package_spec, o) 

35 

36 

37@app.command() 

38def sync( 

39 package_spec: Optional[list[str]] = typer.Argument(None, help=PACKAGE_SPEC_HELP), 

40 r: Optional[list[Path]] = typer.Option(None, help=R_HELP), 

41 dry_run: bool = typer.Option(False), 

42) -> None: 

43 """ 

44 Bring the active (virtual) environment in sync with the definition. 

45 

46 Installs package versions not currently in the environment, and uninstalls what is not needed. 

47 """ 

48 pipxl_sync(r, package_spec, dry_run=dry_run) 

49 

50 

51@app.command() 

52def deptree( 

53 package_spec: Optional[list[str]] = typer.Argument(None, help=PACKAGE_SPEC_HELP), 

54 r: Optional[list[Path]] = typer.Option(None, help=R_HELP), 

55) -> None: 

56 """ 

57 Show dependency tree. 

58 

59 In square brackets, the definition of the dependency, such as version and platform specifiers, is shown. 

60 """ 

61 typer.echo(pipxl_deptree(r, package_spec)) 

62 

63 

64@app.command() 

65def deptreerev( 

66 package_spec: Optional[list[str]] = typer.Argument(None, help=PACKAGE_SPEC_HELP), 

67 r: Optional[list[Path]] = typer.Option(None, help=R_HELP), 

68) -> None: 

69 """ 

70 Show dependency tree in reverse, listing for each package the packages that depend on it. 

71 

72 In square brackets, the definition of the dependency, such as version and platform specifiers, is shown. 

73 """ 

74 typer.echo(pipxl_deptreerev(r, package_spec)) 

75 

76 

77@app.command() 

78def license( 

79 package_spec: Optional[list[str]] = typer.Argument(None, help=PACKAGE_SPEC_HELP), 

80 r: Optional[list[Path]] = typer.Option(None, help=R_HELP), 

81) -> None: 

82 """ 

83 Show licenses of packages, including all dependencies and dependencies of dependencies. 

84 

85 Information is pulled from PyPi's metadata. Some packages may not have a license uploaded, 

86 those will be flagged with `*UNKNOWN*`. 

87 """ 

88 by_lic = pipxl_license(r, package_spec) 

89 for lic, packages in by_lic.items(): 

90 if lic is None: 

91 lic = "*UNKNOWN*" 

92 typer.echo(f"{lic:<20}: {', '.join(packages)}") 

93 

94 

95def version_callback(value: bool) -> None: 

96 if value: 

97 typer.echo(__version__) 

98 raise typer.Exit() 

99 

100 

101@app.callback() 

102def version( 

103 version: bool = typer.Option( 

104 None, "--version", callback=version_callback, is_eager=True, help="List the pipxl package version" 

105 ), 

106) -> Any: 

107 # Do other global stuff, handle other global options here 

108 return 

109 

110 

111def main() -> Any: 

112 return app() # pragma: no cover