diff --git a/src/poetry_plugin_bundle/bundlers/archive_bundler.py b/src/poetry_plugin_bundle/bundlers/archive_bundler.py new file mode 100644 index 0000000..3118146 --- /dev/null +++ b/src/poetry_plugin_bundle/bundlers/archive_bundler.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import tempfile + +from pathlib import Path +from typing import TYPE_CHECKING + +from cleo.io.null_io import NullIO + +from poetry_plugin_bundle.bundlers.venv_bundler import VenvBundler + + +if TYPE_CHECKING: + from cleo.io.io import IO + from poetry.poetry import Poetry + from typing_extensions import Self + + +class ArchiveBundler(VenvBundler): + name = "archive" + + def __init__(self) -> None: + super().__init__() + self._ar_path = Path("output") + self._format = "zip" + self._site_packages_only = False + + def set_path(self, path: Path) -> Self: + self._ar_path = path + return self + + def set_format(self, ar_format: str) -> Self: + self._format = ar_format + + return self + + def set_site_packages_only(self, value: bool) -> Self: + self._site_packages_only = value + + return self + + def bundle(self, poetry: Poetry, io: IO) -> bool: + with tempfile.TemporaryDirectory() as temp_dir: + self._path = Path(temp_dir) + + if not super().bundle(poetry, NullIO()): + return False + + import shutil + + dir_path = self._path + if self._site_packages_only: + dir_path = dir_path.joinpath("Lib", "site-packages") + + self._write( + io, f"Creating archive {self._ar_path} using format {self._format}" + ) + + try: + shutil.make_archive(str(self._ar_path), self._format, dir_path) + except (ValueError, PermissionError) as e: + self._write(io, f"Error while creating archive: {e!s}") + return False + + return True diff --git a/src/poetry_plugin_bundle/bundlers/bundler_manager.py b/src/poetry_plugin_bundle/bundlers/bundler_manager.py index 353afe7..3576b75 100644 --- a/src/poetry_plugin_bundle/bundlers/bundler_manager.py +++ b/src/poetry_plugin_bundle/bundlers/bundler_manager.py @@ -11,12 +11,14 @@ class BundlerManager: def __init__(self) -> None: + from poetry_plugin_bundle.bundlers.archive_bundler import ArchiveBundler from poetry_plugin_bundle.bundlers.venv_bundler import VenvBundler self._bundler_classes: dict[str, type[Bundler]] = {} # Register default bundlers self.register_bundler_class(VenvBundler) + self.register_bundler_class(ArchiveBundler) def bundler(self, name: str) -> Bundler: if name.lower() not in self._bundler_classes: diff --git a/src/poetry_plugin_bundle/console/commands/bundle/archive.py b/src/poetry_plugin_bundle/console/commands/bundle/archive.py new file mode 100644 index 0000000..f6019ba --- /dev/null +++ b/src/poetry_plugin_bundle/console/commands/bundle/archive.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +from cleo.helpers import argument +from cleo.helpers import option + +from poetry_plugin_bundle.console.commands.bundle.bundle_command import BundleCommand + + +if TYPE_CHECKING: + from poetry_plugin_bundle.bundlers.archive_bundler import ArchiveBundler + + +class BundleArchiveCommand(BundleCommand): + name = "bundle archive" + description = "Bundle the current project into an archive (.zip, .tar)" + + arguments = [argument("path", "Path to save archive in")] # noqa: RUF012 + + options = [ # noqa: RUF012 + *BundleCommand._group_dependency_options(), + option( + "python", + "p", + "The Python executable to use to create the virtual environment. " + "Defaults to the current Python executable", + flag=False, + value_required=True, + ), + option( + "clear", + None, + "Clear the existing virtual environment if it exists. ", + flag=True, + ), + option( + "format", + "f", + "Archive format as supported by shutil.make_archive", + flag=False, + default="zip", + ), + option( + "site-packages-only", + None, + "Only archive the Lib/site-packages directory", + flag=True, + ), + ] + + bundler_name = "archive" + + def configure_bundler( + self, bundler: ArchiveBundler # type: ignore[override] + ) -> None: + bundler.set_path(Path(self.argument("path"))) + bundler.set_executable(self.option("python")) + bundler.set_remove(self.option("clear")) + bundler.set_activated_groups(self.activated_groups) + + bundler.set_format(self.option("format")) + bundler.set_site_packages_only(self.option("site-packages-only")) diff --git a/src/poetry_plugin_bundle/plugin.py b/src/poetry_plugin_bundle/plugin.py index 0900f91..8dd1ca1 100644 --- a/src/poetry_plugin_bundle/plugin.py +++ b/src/poetry_plugin_bundle/plugin.py @@ -7,6 +7,7 @@ from cleo.events.console_events import COMMAND from poetry.plugins.application_plugin import ApplicationPlugin +from poetry_plugin_bundle.console.commands.bundle.archive import BundleArchiveCommand from poetry_plugin_bundle.console.commands.bundle.venv import BundleVenvCommand @@ -19,7 +20,7 @@ class BundleApplicationPlugin(ApplicationPlugin): @property def commands(self) -> list[type[Command]]: - return [BundleVenvCommand] + return [BundleVenvCommand, BundleArchiveCommand] def activate(self, application: Application) -> None: assert application.event_dispatcher