Unified AUR/Pacman backend library for Arch Linux
Project description
pkgmanager
A single, reliable Python interface for package management on Arch Linux —
official repos via pacman, and the AUR via whichever helper (yay, paru,
trizen, pikaur) is installed. A lighter, AUR-helper-agnostic backend in
the spirit of pamac.
Install
pip install -e .
Quick start
from pkgmanager import PackageManager
pm = PackageManager()
print(pm.helper_info(), pm.supports_aur())
for pkg in pm.search("firefox"):
print(pkg.repository, pkg.name, pkg.version, "[installed]" if pkg.installed else "")
pm.install(["firefox"], callback=print) # streams pacman/helper output line by line
detail = pm.info("firefox")
print(detail.depends, detail.license)
Every method is safe to call from a background thread — nothing touches UI
state, and install/remove/update/refresh stream output via callback
so a Tkinter/Qt window can update a log widget live instead of blocking.
pm.refresh(callback=print) # sync package databases (-Sy)
for pkg in pm.list_upgradable(): # installed packages with a newer version
print(pkg.name, pkg.version, "->", pkg.new_version)
for pkg in pm.list_repo_packages("extra"): # every package in one official repo
print(pkg.name, pkg.version)
GUI
pkgmanager_gui.pyw is a tabbed Tkinter front-end (Search/Gallery, Installed,
Updates) with a Settings dialog:
- Search — text search, an All/Official/AUR source filter, and a gallery (shown until the first search) of recent searches, popular packages, and repo-browse chips for core/extra/community/multilib/aur.
- Installed — every installed package with a live name filter and a Remove button.
- Updates —
list_upgradable()results with an Update All button. - Settings — force a specific helper (or
auto) and toggle--noconfirm, writing to~/.config/pkgmanager/config.json. The chosen helper is validated by actually constructing aPackageManagerbefore saving -- picking one that isn't installed shows an error and leaves your current, working backend untouched rather than silently breaking it.
Security model
- Every command is built and run as an argument list
(
subprocess.Popen(..., shell=False)) — nothing is ever concatenated into a shell string. search/info/list_installedrun unprivileged.install/remove/updateare elevated viapkexecin a graphical session (withDISPLAY/XAUTHORITY/WAYLAND_DISPLAYforwarded, since pkexec starts a clean environment) orsudootherwise.- If no AUR helper is installed, AUR operations are unavailable and
supports_aur()reportsFalse; official-repo operations keep working through plainpacman.
Structure
pkgmanager/
├── __init__.py # public exports
├── models.py # Package, PackageDetail
├── exceptions.py # exception hierarchy
├── helpers.py # HELPER_COMMANDS, detect_helper(), output parsers
├── permissions.py # command execution + pkexec/sudo elevation
├── backend.py # PackageManager -- the public API
└── config.py # optional ~/.config/pkgmanager/config.json loader
tests/
├── test_helpers.py
├── test_backend.py
└── test_permissions.py
Where this deviates from the original spec
A few places where I made a judgment call or fixed something rather than transcribing literally:
- Forcing an unmapped helper no longer silently becomes pacman. The
original fallback (
HELPER_COMMANDS.get(self.helper, HELPER_COMMANDS['pacman'])) meant that forcing a helper with no command mapping (e.g.pacaur, which the spec explicitly allows forcing despite excluding it from auto-detection) would silently run pacman's commands whilehelper_info()kept reporting the forced name andsupports_aur()returnedFalseregardless of what that helper actually supports.detect_helper()now raisesHelperNotFoundErrorimmediately for any forced name with no entry inHELPER_COMMANDS. list_installedparsing accepts real pacman output, not just the spec's example. The spec's example (local/firefox 120.0.1-1) has alocal/prefix; realpacman -Qoutput has none — it's justfirefox 120.0.1-1. The parser accepts both, defaulting to'local'when no prefix is present.- Search results now flag already-installed packages. Real
pacman/AUR-helper
-Ssoutput tags installed entries with[installed]; this is now parsed intoPackage.installed, so a GUI search view can badge them without a separatelist_installed()call. AURHelperMissingErrornow has an actual trigger. The spec left this open ("we can't know without checking – we'll trust the user").install()takes an opt-inknown_aur=Trueflag for callers that already know a package is AUR-only (e.g. from asearch()result'sis_aurflag), raising immediately instead of leaving pacman to fail confusingly. Not added toremove()/update()— neither needs the AUR helper to operate on already-installed packages.- The streaming generator distinguishes early-exit from failure. If a
caller stops consuming
install/remove/updateoutput partway through (rather than the process actually failing), it cleans up without raising a spuriousCommandFailedError. - Repo-category browsing resolved with a real listing, not a stub. The
follow-up spec explicitly punted on this ("pragmatic: skip category
filtering for now, but keep the buttons for future extension"). Rather
than ship dead buttons,
list_repo_packages(repo)wrapspacman -Sl <repo>to actually list everything in an official repo. It always callspacmandirectly regardless of the active AUR helper, since this is a pure sync-database read with no helper-specific behavior. There's no AUR equivalent (no bulk-listing endpoint exists), so the 'aur' category chip instead sets the source filter to AUR and re-renders the current result set client-side. - Settings validates before it commits. The follow-up spec's
_save_settingssketch replacedself.pmand wrote the config file unconditionally. If the chosen helper isn't actually installed, that would silently leave the app with a broken backend and a config file that fails on next launch too. The dialog now constructs the newPackageManagerfirst; on failure it shows an error and leaves the previous, working backend and config untouched. - Tab-switching no longer assumes the backend has finished initializing.
Switching to Installed/Updates before
PackageManager()detection completes would otherwise handNoneto a background thread, crash it silently, and leave the UI stuck in a "busy" state (buttons disabled, spinner running) forever.
Tests
python -m unittest discover -s tests -t . -v
47 tests, all mocked (subprocess.Popen / shutil.which) — there's no live
pacman or AUR helper in scope here, so nothing actually shells out during
the test run. The GUI itself is checked with a headless smoke test under
Xvfb during development (not shipped -- it needs tkinter and a display,
neither guaranteed on a CI box) that builds the full tabbed UI, exercises
tab-switching, gallery/filter logic, and the settings dialog.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file aur_gui-0.1.0.tar.gz.
File metadata
- Download URL: aur_gui-0.1.0.tar.gz
- Upload date:
- Size: 16.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4c8f74fb3e450bf919b39cf6fb963839aed0d70f71196fa2881eda32d7e21b89
|
|
| MD5 |
b6888aea3ce5923630603749a1f081a3
|
|
| BLAKE2b-256 |
c83fbe25b141cd6820c681ce24168cf9703570d3b918b84638449fa91e1c2057
|
File details
Details for the file aur_gui-0.1.0-py3-none-any.whl.
File metadata
- Download URL: aur_gui-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b854b219e14db3ee11f976b60271a5c1291887b395763181dbf335b6f2d2d400
|
|
| MD5 |
e4dba19ae1e325cef658c61c4f0eab80
|
|
| BLAKE2b-256 |
3f1453baacef7c3a2c6f54166e2456a91d45f0ed4a32c5ac491366324b6bc96e
|