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

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

2#

3# SPDX-License-Identifier: MIT

4

5 from pathlib import Path

6 from typing import Any, Optional

7

8 import typer

9

10 from pipxl import compile as pipxl_compile

11 from pipxl import deptree as pipxl_deptree

12 from pipxl import deptreerev as pipxl_deptreerev

13 from pipxl import license as pipxl_license

14 from pipxl import sync as pipxl_sync

15 from pipxl.__about__ import __version__

16

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

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

19

20 app = typer.Typer()

21

22

23@app.command()

24 def 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()

38 def 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()

52 def 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()

65 def 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()

78 def 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

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

96 if value:

97 typer.echo(__version__)

98 raise typer.Exit()

99

100

101@app.callback()

102 def 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

111 def main() -> Any:

112 return app() # pragma: no cover