From f05f502fa7e21b4531b5d69aca69ceb27e8b3698 Mon Sep 17 00:00:00 2001 From: Kivi Kaitaniemi Date: Sat, 27 Dec 2025 03:58:42 +0200 Subject: [PATCH] Split plugins to seperate packages --- DEVELOPMENT.md | 18 ++++- docs/README.md | 8 +-- docs/migrate-to-v1.md | 26 +------- packages/decman-flatpak/pyproject.toml | 20 ++++++ .../src}/decman/plugins/flatpak.py | 0 packages/decman-pacman/pyproject.toml | 25 +++++++ .../src}/decman/plugins/aur/__init__.py | 14 ++-- .../src}/decman/plugins/aur/commands.py | 2 +- .../src}/decman/plugins/aur/error.py | 0 .../src}/decman/plugins/aur/fpm.py | 9 +-- .../src}/decman/plugins/aur/package.py | 6 +- .../src}/decman/plugins/aur/resolver.py | 0 .../src}/decman/plugins/pacman.py | 0 .../tests}/test_decman_plugins_aur.py | 0 .../tests}/test_decman_plugins_aur_package.py | 1 - .../test_decman_plugins_aur_resolver.py | 1 - .../tests}/test_decman_plugins_pacman.py | 0 packages/decman-systemd/pyproject.toml | 20 ++++++ .../src}/decman/plugins/systemd.py | 0 .../tests}/test_decman_plugins_systemd.py | 0 pyproject.toml | 31 ++++++--- src/decman/__init__.py | 66 +++++++++++-------- uv.lock | 66 +++++++++++++++++-- 23 files changed, 223 insertions(+), 90 deletions(-) create mode 100644 packages/decman-flatpak/pyproject.toml rename {src => packages/decman-flatpak/src}/decman/plugins/flatpak.py (100%) create mode 100644 packages/decman-pacman/pyproject.toml rename {src => packages/decman-pacman/src}/decman/plugins/aur/__init__.py (100%) rename {src => packages/decman-pacman/src}/decman/plugins/aur/commands.py (100%) rename {src => packages/decman-pacman/src}/decman/plugins/aur/error.py (100%) rename {src => packages/decman-pacman/src}/decman/plugins/aur/fpm.py (99%) rename {src => packages/decman-pacman/src}/decman/plugins/aur/package.py (100%) rename {src => packages/decman-pacman/src}/decman/plugins/aur/resolver.py (100%) rename {src => packages/decman-pacman/src}/decman/plugins/pacman.py (100%) rename {tests => packages/decman-pacman/tests}/test_decman_plugins_aur.py (100%) rename {tests => packages/decman-pacman/tests}/test_decman_plugins_aur_package.py (99%) rename {tests => packages/decman-pacman/tests}/test_decman_plugins_aur_resolver.py (99%) rename {tests => packages/decman-pacman/tests}/test_decman_plugins_pacman.py (100%) create mode 100644 packages/decman-systemd/pyproject.toml rename {src => packages/decman-systemd/src}/decman/plugins/systemd.py (100%) rename {tests => packages/decman-systemd/tests}/test_decman_plugins_systemd.py (100%) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 26e85bd..5309fc0 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -7,15 +7,27 @@ Before committing ensure all tests pass and format files. Run decman as root to test all changes: ```sh -sudo uv run decman +sudo uv run --all-packages decman +``` + +## Python shell + +Running a python shell with all the packages. + +```sh +sudo uv run --all-packages python +sudo uv run --exact --package decman python ``` ## Testing -Run all unit tests (`-s` disables output capturing): +Run all unit tests (`-s` disables output capturing, needed for PTY test): ```sh -uv run pytest -s +uv run --package decman pytest -s tests/ +uv run --package decman-pacman pytest packages/decman-pacman/tests/ +uv run --package decman-systemd pytest packages/decman-systemd/tests/ +uv run --package decman-flatpak pytest packages/decman-flatpak/tests/ ``` ## Formatting diff --git a/docs/README.md b/docs/README.md index 392fe52..024e1b8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -391,7 +391,7 @@ Create your own modules by subclassing `Plugin`. Then override the methods docum from decman.plugins import Plugin class MyPlugin(Plugin): - # Plugins should be singletons. + # Plugins should be singletons. (Only one instance exists ever.) # This name should be the same as the key used in decman.plugins dict. NAME = "my-plugin" ``` @@ -454,10 +454,8 @@ In `pyproject.toml` set: ```toml [project.entry-points."decman.plugins"] -systemd = "decman.plugins.systemd:Systemd" pacman = "decman.plugins.pacman:Pacman" aur = "decman.plugins.aur:AUR" -flatpak = "decman.plugins.flatpak:Flatpak" ``` ## Useful utilities @@ -474,6 +472,7 @@ decman.prg( ["nvim", "--headless", "+Lazy! sync", "+qa"], user = "user", env_overrides = {"EXAMPLE": "value"}, + pass_environment = True, mimic_login = True, pty = True, check = True, @@ -484,7 +483,8 @@ decman.prg( - `cmd: list[str]`: Command to execute. - `user: str`: User name to run the command as. If set, the command is executed after dropping privileges to this user. -- `env_overrides dict[str, str]`: Environment variables to override or add for the command execution. These values are merged on top of the current process environment. +- `pass_environment: bool`: Copy decman's execution environment variables and pass them to the subprocess. +- `env_overrides: dict[str, str]`: Environment variables to override or add for the command execution. These values are merged on top of the current process environment. - `mimic_login: bool`: If mimic_login is True, will set the following environment variables according to the given user's passwd file details. This only happens when user is set. - `HOME` - `USER` diff --git a/docs/migrate-to-v1.md b/docs/migrate-to-v1.md index 7498653..ebbec13 100644 --- a/docs/migrate-to-v1.md +++ b/docs/migrate-to-v1.md @@ -6,7 +6,8 @@ I recommend reading decman's new documentation. This document is supposed to be This change is mostly architectural and doesn't change decman's behavior, but there are a few exceptions. -- The pacman plugin will now remove orphan packages +- The pacman plugin will now remove orphan packages. + - **Packages that are only optionally required by other packages are considered orphans.** - Explicitly installed packages that are required by other explicitly installed packages are no longer uninstalled when removed from the source. - Module's `on_disable` will now be executed when the module is no longer present in `decman.modules`. - It will be executed even when the module is removed completely from the source @@ -596,7 +597,7 @@ class MyCommands(decman.config.Commands): #### New -AUR and pacman commands are a seperate setting, but they share the same subclass, so it's possible to set them in a one place. New commands have also been added but it is better to look at the plugin documentation for those options. Notable changes are: `list_pkgs` have been split to `list_explicit_native` and `list_explicit_foreign`. +AUR and pacman commands are a seperate setting, but they share the same subclass, so it's possible to set them in a one place. Many pacman query commands have been deleted since pyalpm is used now. New commands have also been added but it is better to look at the plugin documentation for those options. These values are the new defaults. @@ -608,18 +609,6 @@ decman.aur.commands = MyAurAndPacmanCommands() decman.pacman.commands = MyAurAndPacmanCommands() class MyAurAndPacmanCommands(aur.AurCommands): - def list_explicit_native(self) -> list[str]: - return ["pacman", "-Qeqn", "--color=never"] - - def list_explicit_foreign(self) -> list[str]: - return ["pacman", "-Qeqm", "--color=never"] - - def list_orphans_native(self) -> list[str]: - return ["pacman", "-Qndtq", "--color=never"] - - def list_dependants(self, pkg: str) -> list[str]: - return ["pacman", "-Rc", "--print", "--print-format", "%n", pkg] - def install(self, pkgs: set[str]) -> list[str]: return ["pacman", "-S", "--needed"] + list(pkgs) @@ -635,15 +624,6 @@ class MyAurAndPacmanCommands(aur.AurCommands): def remove(self, pkgs: set[str]) -> list[str]: return ["pacman", "-Rs"] + list(pkgs) - def list_orphans_foreign(self) -> list[str]: - return ["pacman", "-Qmdtq", "--color=never"] - - def list_foreign_versioned(self) -> list[str]: - return ["pacman", "-Qm", "--color=never"] - - def is_installable(self, pkg: str) -> list[str]: - return ["pacman", "-Sddp", pkg] - def install_as_dependencies(self, pkgs: set[str]) -> list[str]: return ["pacman", "-S", "--needed", "--asdeps"] + list(pkgs) diff --git a/packages/decman-flatpak/pyproject.toml b/packages/decman-flatpak/pyproject.toml new file mode 100644 index 0000000..60957e1 --- /dev/null +++ b/packages/decman-flatpak/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "decman-flatpak" +version = "1.0.0" +requires-python = ">=3.13" +dependencies = ["decman==1.0.0"] + +[project.entry-points."decman.plugins"] +flatpak = "decman.plugins.flatpak:Flatpak" + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["src"] +namespaces = true +include = ["decman.plugins*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/src/decman/plugins/flatpak.py b/packages/decman-flatpak/src/decman/plugins/flatpak.py similarity index 100% rename from src/decman/plugins/flatpak.py rename to packages/decman-flatpak/src/decman/plugins/flatpak.py diff --git a/packages/decman-pacman/pyproject.toml b/packages/decman-pacman/pyproject.toml new file mode 100644 index 0000000..19dd272 --- /dev/null +++ b/packages/decman-pacman/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "decman-pacman" +version = "1.0.0" +requires-python = ">=3.13" +dependencies = [ + "decman==1.0.0", + "pyalpm", + "requests", +] + +[project.entry-points."decman.plugins"] +pacman = "decman.plugins.pacman:Pacman" +aur = "decman.plugins.aur:AUR" + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["src"] +namespaces = true +include = ["decman.plugins*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/src/decman/plugins/aur/__init__.py b/packages/decman-pacman/src/decman/plugins/aur/__init__.py similarity index 100% rename from src/decman/plugins/aur/__init__.py rename to packages/decman-pacman/src/decman/plugins/aur/__init__.py index 755b6ad..9f04737 100644 --- a/src/decman/plugins/aur/__init__.py +++ b/packages/decman-pacman/src/decman/plugins/aur/__init__.py @@ -2,13 +2,6 @@ import os import shutil import pyalpm - -import decman.config as config -import decman.core.error as errors -import decman.core.module as module -import decman.core.output as output -import decman.core.store as _store -import decman.plugins as plugins from decman.plugins.aur.commands import AurCommands, AurPacmanInterface from decman.plugins.aur.error import ( AurRPCError, @@ -19,6 +12,13 @@ from decman.plugins.aur.error import ( from decman.plugins.aur.fpm import ForeignPackageManager from decman.plugins.aur.package import CustomPackage, PackageSearch +import decman.config as config +import decman.core.error as errors +import decman.core.module as module +import decman.core.output as output +import decman.core.store as _store +import decman.plugins as plugins + # Re-exports __all__ = [ "AUR", diff --git a/src/decman/plugins/aur/commands.py b/packages/decman-pacman/src/decman/plugins/aur/commands.py similarity index 100% rename from src/decman/plugins/aur/commands.py rename to packages/decman-pacman/src/decman/plugins/aur/commands.py index 8936fa2..ca33720 100644 --- a/src/decman/plugins/aur/commands.py +++ b/packages/decman-pacman/src/decman/plugins/aur/commands.py @@ -1,8 +1,8 @@ +import decman.plugins.pacman as pacman import pyalpm import decman.config as config import decman.core.command as command -import decman.plugins.pacman as pacman class AurCommands(pacman.PacmanCommands): diff --git a/src/decman/plugins/aur/error.py b/packages/decman-pacman/src/decman/plugins/aur/error.py similarity index 100% rename from src/decman/plugins/aur/error.py rename to packages/decman-pacman/src/decman/plugins/aur/error.py diff --git a/src/decman/plugins/aur/fpm.py b/packages/decman-pacman/src/decman/plugins/aur/fpm.py similarity index 99% rename from src/decman/plugins/aur/fpm.py rename to packages/decman-pacman/src/decman/plugins/aur/fpm.py index b6af772..c51cee3 100644 --- a/src/decman/plugins/aur/fpm.py +++ b/packages/decman-pacman/src/decman/plugins/aur/fpm.py @@ -3,15 +3,16 @@ import shutil import time import typing +from decman.plugins.aur.commands import AurCommands +from decman.plugins.aur.error import ForeignPackageManagerError +from decman.plugins.aur.package import AurPacmanInterface, PackageSearch +from decman.plugins.aur.resolver import DepGraph, ForeignPackage + import decman.config as config import decman.core.command as command import decman.core.error as errors import decman.core.output as output import decman.core.store as _store -from decman.plugins.aur.commands import AurCommands -from decman.plugins.aur.error import ForeignPackageManagerError -from decman.plugins.aur.package import AurPacmanInterface, PackageSearch -from decman.plugins.aur.resolver import DepGraph, ForeignPackage def find_latest_cached_package(store: _store.Store, package: str) -> tuple[str, str] | None: diff --git a/src/decman/plugins/aur/package.py b/packages/decman-pacman/src/decman/plugins/aur/package.py similarity index 100% rename from src/decman/plugins/aur/package.py rename to packages/decman-pacman/src/decman/plugins/aur/package.py index fa414e5..aa105d9 100644 --- a/src/decman/plugins/aur/package.py +++ b/packages/decman-pacman/src/decman/plugins/aur/package.py @@ -4,15 +4,15 @@ import pathlib import shutil import tempfile +import decman.plugins.pacman as pacman_module import requests # type: ignore +from decman.plugins.aur.commands import AurCommands, AurPacmanInterface +from decman.plugins.aur.error import AurRPCError, PKGBUILDParseError import decman.config as config import decman.core.command as command import decman.core.error as errors import decman.core.output as output -import decman.plugins.pacman as pacman_module -from decman.plugins.aur.commands import AurCommands, AurPacmanInterface -from decman.plugins.aur.error import AurRPCError, PKGBUILDParseError @dataclasses.dataclass(frozen=True, slots=True) diff --git a/src/decman/plugins/aur/resolver.py b/packages/decman-pacman/src/decman/plugins/aur/resolver.py similarity index 100% rename from src/decman/plugins/aur/resolver.py rename to packages/decman-pacman/src/decman/plugins/aur/resolver.py diff --git a/src/decman/plugins/pacman.py b/packages/decman-pacman/src/decman/plugins/pacman.py similarity index 100% rename from src/decman/plugins/pacman.py rename to packages/decman-pacman/src/decman/plugins/pacman.py diff --git a/tests/test_decman_plugins_aur.py b/packages/decman-pacman/tests/test_decman_plugins_aur.py similarity index 100% rename from tests/test_decman_plugins_aur.py rename to packages/decman-pacman/tests/test_decman_plugins_aur.py diff --git a/tests/test_decman_plugins_aur_package.py b/packages/decman-pacman/tests/test_decman_plugins_aur_package.py similarity index 99% rename from tests/test_decman_plugins_aur_package.py rename to packages/decman-pacman/tests/test_decman_plugins_aur_package.py index 0954fac..2f7f353 100644 --- a/tests/test_decman_plugins_aur_package.py +++ b/packages/decman-pacman/tests/test_decman_plugins_aur_package.py @@ -1,7 +1,6 @@ import pathlib import pytest - from decman.plugins.aur import package as pkg_mod from decman.plugins.aur.error import AurRPCError, PKGBUILDParseError from decman.plugins.aur.package import ( diff --git a/tests/test_decman_plugins_aur_resolver.py b/packages/decman-pacman/tests/test_decman_plugins_aur_resolver.py similarity index 99% rename from tests/test_decman_plugins_aur_resolver.py rename to packages/decman-pacman/tests/test_decman_plugins_aur_resolver.py index 64d1731..733911b 100644 --- a/tests/test_decman_plugins_aur_resolver.py +++ b/packages/decman-pacman/tests/test_decman_plugins_aur_resolver.py @@ -1,5 +1,4 @@ import pytest - from decman.plugins.aur.error import DependencyCycleError from decman.plugins.aur.resolver import DepGraph, ForeignPackage diff --git a/tests/test_decman_plugins_pacman.py b/packages/decman-pacman/tests/test_decman_plugins_pacman.py similarity index 100% rename from tests/test_decman_plugins_pacman.py rename to packages/decman-pacman/tests/test_decman_plugins_pacman.py diff --git a/packages/decman-systemd/pyproject.toml b/packages/decman-systemd/pyproject.toml new file mode 100644 index 0000000..23a51ba --- /dev/null +++ b/packages/decman-systemd/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "decman-systemd" +version = "1.0.0" +requires-python = ">=3.13" +dependencies = ["decman==1.0.0"] + +[project.entry-points."decman.plugins"] +systemd = "decman.plugins.systemd:Systemd" + +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +where = ["src"] +namespaces = true +include = ["decman.plugins*"] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/src/decman/plugins/systemd.py b/packages/decman-systemd/src/decman/plugins/systemd.py similarity index 100% rename from src/decman/plugins/systemd.py rename to packages/decman-systemd/src/decman/plugins/systemd.py diff --git a/tests/test_decman_plugins_systemd.py b/packages/decman-systemd/tests/test_decman_plugins_systemd.py similarity index 100% rename from tests/test_decman_plugins_systemd.py rename to packages/decman-systemd/tests/test_decman_plugins_systemd.py diff --git a/pyproject.toml b/pyproject.toml index 5b8ed74..4e763df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,20 +7,15 @@ authors = [ {name = "Kivi Kaitaniemi"} ] requires-python = ">=3.13" -dependencies = [ - "requests", - "pyalpm", -] + +[project.optional-dependencies] +pacman = ["decman-pacman"] +systemd = ["decman-systemd"] +flatpak = ["decman-flatpak"] [project.scripts] decman = "decman.app:main" -[project.entry-points."decman.plugins"] -systemd = "decman.plugins.systemd:Systemd" -pacman = "decman.plugins.pacman:Pacman" -aur = "decman.plugins.aur:AUR" -flatpak = "decman.plugins.flatpak:Flatpak" - [dependency-groups] dev = [ "ruff>=0.14.9", @@ -31,6 +26,22 @@ dev = [ requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" +[tool.uv.workspace] +members = [ + "packages/decman-pacman", + "packages/decman-systemd", + "packages/decman-flatpak", +] + +[tool.uv.sources] +decman = { workspace = true } +decman-pacman = { workspace = true } +decman-systemd = { workspace = true } +decman-flatpak = { workspace = true } + +[tool.pytest.ini_options] +testpaths = ["tests"] + [tool.ruff] line-length = 100 target-version = "py313" diff --git a/src/decman/__init__.py b/src/decman/__init__.py index 3727b5a..bebb7b0 100644 --- a/src/decman/__init__.py +++ b/src/decman/__init__.py @@ -8,11 +8,44 @@ from decman.core.module import Module from decman.core.store import Store from decman.plugins import Plugin, available_plugins -# Plugin types -from decman.plugins.aur import AUR -from decman.plugins.flatpak import Flatpak -from decman.plugins.pacman import Pacman -from decman.plugins.systemd import Systemd +plugins: dict[str, Plugin] = available_plugins() + +# Quick access for default plugins +try: + from decman.plugins.aur import AUR + from decman.plugins.pacman import Pacman + + pacman: None | Pacman = None + _pacman = plugins.get("pacman", None) + if isinstance(_pacman, Pacman): + pacman = _pacman + + aur: None | AUR = None + _aur = plugins.get("aur", None) + if isinstance(_aur, AUR): + aur = _aur +except ModuleNotFoundError: + pass + +try: + from decman.plugins.flatpak import Flatpak + + flatpak: None | Flatpak = None + _flatpak = plugins.get("flatpak", None) + if isinstance(_flatpak, Flatpak): + flatpak = _flatpak +except ModuleNotFoundError: + pass + +try: + from decman.plugins.systemd import Systemd + + systemd: None | Systemd = None + _systemd = plugins.get("systemd", None) + if isinstance(_systemd, Systemd): + systemd = _systemd +except ModuleNotFoundError: + pass __all__ = [ "SourceError", @@ -31,7 +64,6 @@ __all__ = [ files: dict[str, File] = {} directories: dict[str, Directory] = {} modules: set[Module] = set() -plugins: dict[str, Plugin] = available_plugins() execution_order: list[str] = [ "files", "pacman", @@ -39,28 +71,6 @@ execution_order: list[str] = [ "systemd", ] -# Default plugins get quick access -pacman: None | Pacman = None -aur: None | AUR = None -systemd: None | Systemd = None -flatpak: None | Flatpak = None - -_pacman = plugins.get("pacman", None) -if isinstance(_pacman, Pacman): - pacman = _pacman - -_aur = plugins.get("aur", None) -if isinstance(_aur, AUR): - aur = _aur - -_systemd = plugins.get("systemd", None) -if isinstance(_systemd, Systemd): - systemd = _systemd - -_flatpak = plugins.get("flatpak", None) -if isinstance(_flatpak, Flatpak): - flatpak = _flatpak - def sh( sh_cmd: str, diff --git a/uv.lock b/uv.lock index 1bda282..c5b7ad3 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,14 @@ version = 1 revision = 3 requires-python = ">=3.13" +[manifest] +members = [ + "decman", + "decman-flatpak", + "decman-pacman", + "decman-systemd", +] + [[package]] name = "certifi" version = "2025.11.12" @@ -65,9 +73,16 @@ wheels = [ name = "decman" version = "1.0.0" source = { editable = "." } -dependencies = [ - { name = "pyalpm" }, - { name = "requests" }, + +[package.optional-dependencies] +flatpak = [ + { name = "decman-flatpak" }, +] +pacman = [ + { name = "decman-pacman" }, +] +systemd = [ + { name = "decman-systemd" }, ] [package.dev-dependencies] @@ -78,9 +93,11 @@ dev = [ [package.metadata] requires-dist = [ - { name = "pyalpm" }, - { name = "requests" }, + { name = "decman-flatpak", marker = "extra == 'flatpak'", editable = "packages/decman-flatpak" }, + { name = "decman-pacman", marker = "extra == 'pacman'", editable = "packages/decman-pacman" }, + { name = "decman-systemd", marker = "extra == 'systemd'", editable = "packages/decman-systemd" }, ] +provides-extras = ["pacman", "systemd", "flatpak"] [package.metadata.requires-dev] dev = [ @@ -88,6 +105,45 @@ dev = [ { name = "ruff", specifier = ">=0.14.9" }, ] +[[package]] +name = "decman-flatpak" +version = "1.0.0" +source = { editable = "packages/decman-flatpak" } +dependencies = [ + { name = "decman" }, +] + +[package.metadata] +requires-dist = [{ name = "decman", editable = "." }] + +[[package]] +name = "decman-pacman" +version = "1.0.0" +source = { editable = "packages/decman-pacman" } +dependencies = [ + { name = "decman" }, + { name = "pyalpm" }, + { name = "requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "decman", editable = "." }, + { name = "pyalpm" }, + { name = "requests" }, +] + +[[package]] +name = "decman-systemd" +version = "1.0.0" +source = { editable = "packages/decman-systemd" } +dependencies = [ + { name = "decman" }, +] + +[package.metadata] +requires-dist = [{ name = "decman", editable = "." }] + [[package]] name = "idna" version = "3.11"