reyk-cli 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
reyk_cli/__init__.py ADDED
File without changes
reyk_cli/cli.py ADDED
@@ -0,0 +1,79 @@
1
+ from pathlib import Path
2
+ from typing import Annotated, cast
3
+
4
+ import typer
5
+
6
+ from reyk_cli.configuration_reader import DEFAULT_VENDOR_GROUP, read_reyk_configuration
7
+ from reyk_cli.uv_vendorizer import UVBasedVendorizer
8
+
9
+ app = typer.Typer(
10
+ help="Manage isolated environment dependencies.",
11
+ no_args_is_help=True,
12
+ )
13
+
14
+ group_option = typer.Option(
15
+ default=DEFAULT_VENDOR_GROUP,
16
+ help="Dependency group to to vendor.", # https://docs.astral.sh/uv/concepts/projects/dependencies/#dependency-groups
17
+ )
18
+
19
+
20
+ @app.callback()
21
+ def main(
22
+ ctx: typer.Context,
23
+ config: Annotated[
24
+ Path,
25
+ typer.Option(
26
+ help="Where to find pyproject.toml containing vendored dependencies",
27
+ file_okay=True,
28
+ dir_okay=False,
29
+ exists=True,
30
+ readable=True,
31
+ ),
32
+ ] = Path("./pyproject.toml"),
33
+ ) -> None:
34
+ reyk_config = read_reyk_configuration(config)
35
+ ctx.obj = UVBasedVendorizer(
36
+ project_root=config.parent,
37
+ vendor_groups=reyk_config.vendor_groups,
38
+ libraries_target_path=Path(reyk_config.libraries_path),
39
+ )
40
+
41
+
42
+ @app.command(help="Vendor the isolated environment dependencies.")
43
+ def sync(ctx: typer.Context) -> None:
44
+ """Install the isolated environment dependencies."""
45
+ cast(UVBasedVendorizer, ctx.obj).sync_from_group_to_target_path()
46
+
47
+
48
+ @app.command(
49
+ add_help_option=False,
50
+ # Allow any extra args to be passed to `uv`
51
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
52
+ )
53
+ def add(
54
+ ctx: typer.Context,
55
+ group: str = group_option,
56
+ ) -> None:
57
+ """Add packages to the isolated environment."""
58
+ vendorizer = cast(UVBasedVendorizer, ctx.obj)
59
+ vendorizer.add_new_requirement_to_pyproject(group, ctx.args)
60
+ vendorizer.sync_from_group_to_target_path()
61
+
62
+
63
+ @app.command(
64
+ add_help_option=False,
65
+ # Allow any extra args to be passed to `uv`
66
+ context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
67
+ )
68
+ def remove(
69
+ ctx: typer.Context,
70
+ group: str = group_option,
71
+ ) -> None:
72
+ """Remove packages from the isolated environment."""
73
+ vendorizer = cast(UVBasedVendorizer, ctx.obj)
74
+ vendorizer.remove_requirement_from_pyproject(group, ctx.args)
75
+ vendorizer.sync_from_group_to_target_path()
76
+
77
+
78
+ if __name__ == "__main__": # pragma: no cover
79
+ app()
@@ -0,0 +1,13 @@
1
+ import subprocess
2
+ import logging
3
+ import typer
4
+
5
+
6
+ LOGGER = logging.getLogger(__name__)
7
+
8
+
9
+ def run_command_exit_on_fail(cmd: list[str]) -> None:
10
+ result = subprocess.run(cmd, check=False) # noqa: S603
11
+ LOGGER.debug(f"Executed {cmd=} -> {result.returncode=}")
12
+ if result.returncode != 0:
13
+ raise typer.Exit(result.returncode)
@@ -0,0 +1,33 @@
1
+ from dataclasses import dataclass
2
+ from pathlib import Path
3
+ from typing import Any, cast
4
+
5
+ try:
6
+ import tomllib
7
+ except ImportError:
8
+ import tomli as tomllib # type: ignore[reportMissingImports]
9
+
10
+
11
+ TOOLS_CONFIGURATION_NAME = "tool"
12
+ REYK_CONFIGURATION_NAME = "reyk"
13
+
14
+ DEFAULT_LIBRARIES_TARGET_PATH = "libs" # Path the vendored libraries will be in
15
+ DEFAULT_VENDOR_GROUP = "vendor-libs"
16
+ DEFAULT_VENDOR_GROUPS = {DEFAULT_VENDOR_GROUP}
17
+
18
+
19
+ @dataclass
20
+ class ReykConfiguration:
21
+ libraries_path: str
22
+ vendor_groups: set[str]
23
+
24
+
25
+ def read_reyk_configuration(toml_path: Path) -> ReykConfiguration:
26
+ toml_data = tomllib.loads(toml_path.read_text())
27
+ configuration = cast(
28
+ dict[str, Any],
29
+ toml_data.get(TOOLS_CONFIGURATION_NAME, {}).get(REYK_CONFIGURATION_NAME, {}),
30
+ )
31
+ configuration["libraries_path"] = configuration.pop("libraries-path", DEFAULT_LIBRARIES_TARGET_PATH)
32
+ configuration["vendor_groups"] = set(configuration.pop("vendor-groups", DEFAULT_VENDOR_GROUPS))
33
+ return ReykConfiguration(**configuration)
@@ -0,0 +1,70 @@
1
+ import itertools
2
+ from collections.abc import Sequence
3
+ from pathlib import Path
4
+
5
+ import typer
6
+
7
+ from reyk_cli.command_executor import run_command_exit_on_fail
8
+
9
+ LOCK_FILE_NAME = "vendor.lock"
10
+
11
+
12
+ class UVBasedVendorizer:
13
+ def __init__(self, project_root: Path, vendor_groups: set[str], libraries_target_path: Path) -> None:
14
+ self._project_root = project_root
15
+ self._vendor_groups = vendor_groups
16
+ self._libraries_target_path = libraries_target_path
17
+
18
+ def add_new_requirement_to_pyproject(self, vendor_group: str, command_args: Sequence[str]) -> None:
19
+ # The `uv add` command doesn't support installing to a custom target,
20
+ # so we use `--frozen` just to append the package to the `pyproject.toml`.
21
+ self._ensure_group_exists(vendor_group)
22
+ run_command_exit_on_fail(["uv", "add", "--frozen", "--group", vendor_group, *command_args])
23
+
24
+ def remove_requirement_from_pyproject(self, vendor_group: str, command_args: Sequence[str]) -> None:
25
+ # The `uv remove` command doesn't support installing to a custom target,
26
+ # so we use `--frozen` just to remove the package from the `pyproject.toml`.
27
+ self._ensure_group_exists(vendor_group)
28
+ run_command_exit_on_fail(["uv", "remove", "--frozen", "--group", vendor_group, *command_args])
29
+
30
+ def _ensure_group_exists(self, vendor_group: str) -> None:
31
+ if vendor_group not in self._vendor_groups:
32
+ raise typer.BadParameter(f"{vendor_group!s} not in {self._vendor_groups=}")
33
+
34
+ def sync_from_group_to_target_path(self) -> None:
35
+ # `uv pip sync` is the only command that supports installing to a custom target,
36
+ # but it requires a lock file. Therefore, we first export the lock file of the libs group
37
+ # and then use it to install the packages to the desired target.
38
+ self.create_lock_file_on_group()
39
+ self.install_requirements_from_lock_file()
40
+
41
+ def create_lock_file_on_group(self) -> None:
42
+ run_command_exit_on_fail(
43
+ [
44
+ "uv",
45
+ "export",
46
+ *itertools.chain.from_iterable([("--only-group", group) for group in self._vendor_groups]),
47
+ "--no-header",
48
+ "--quiet",
49
+ "--no-emit-project",
50
+ "--output-file",
51
+ str(self.lock_file_path),
52
+ ]
53
+ )
54
+
55
+ def install_requirements_from_lock_file(self) -> None:
56
+ run_command_exit_on_fail(
57
+ [
58
+ "uv",
59
+ "pip",
60
+ "sync",
61
+ str(self.lock_file_path),
62
+ "--target",
63
+ str(self._libraries_target_path),
64
+ "--allow-empty-requirements",
65
+ ]
66
+ )
67
+
68
+ @property
69
+ def lock_file_path(self) -> Path:
70
+ return self._project_root / LOCK_FILE_NAME
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.3
2
+ Name: reyk-cli
3
+ Version: 0.0.1
4
+ Summary: Manage isolated environment dependencies.
5
+ Requires-Dist: typer>=0.20.0
6
+ Requires-Dist: tomli>=2.4.0 ; python_full_version < '3.11'
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+
@@ -0,0 +1,9 @@
1
+ reyk_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ reyk_cli/cli.py,sha256=xq8OpANKQVPaJhOwGJAgghCboNolGPlL6FbWg5USLZ8,2331
3
+ reyk_cli/command_executor.py,sha256=i7mY24oOTunDseuz3lZjmyb9PT9HgyQWtQR1tsJarWY,337
4
+ reyk_cli/configuration_reader.py,sha256=hVkQUjZBDK2wHc_Jjaz0KCGU-cqwpYoGlrXlYrWfIF4,1039
5
+ reyk_cli/uv_vendorizer.py,sha256=Tx0KDPqCcgU7_926HnwXouOOITa3aE5tFt_cHVFG8Bw,2835
6
+ reyk_cli-0.0.1.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
7
+ reyk_cli-0.0.1.dist-info/entry_points.txt,sha256=fKkQvt7LnGJ0v3CIKxwXn_R4h-Y3SGiTJ833PjnYF3w,47
8
+ reyk_cli-0.0.1.dist-info/METADATA,sha256=vKHqW6x7pDNPx9JTdJBcb2bGDhTsjPuHieUcPVWGF50,255
9
+ reyk_cli-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.8.24
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ reyk-cli = reyk_cli.cli:app
3
+