Skip to content

Commit 7e9a59b

Browse files
committed
feat: add ctypes-based Python bindings
1 parent d325f4c commit 7e9a59b

26 files changed

Lines changed: 1865 additions & 1 deletion

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ compile_commands.json
9191
# Meson private directory
9292
.mesonpy-*
9393

94+
# python
95+
# @detect: bindings/python
96+
__pycache__/
97+
*.pyc
98+
*.egg-info/
99+
.pytest_cache/
100+
.venv/
101+
94102
# nix
95103
# @detect: *.nix flake.nix default.nix
96104
# Nix build outputs

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ All notable changes to this project will be documented in this file.
99
- `dep_graph_add_installed()` (`include/apg/graph.h`): identical to `dep_graph_add()`, but marks the node as installed so alias/`provides` resolution can prefer it (see Fixed below). `trans_prepare()` (`src/transaction/prepare.c`) now uses it for the caller's already-installed packages instead of `dep_graph_add()`
1010
- `trans_prefer_provider(trans, name, pkg_name)` (`include/apg/transaction.h`): forces resolution of a dependency or virtual (`provides`) name to a specific package for a given transaction, for callers that want to prompt the user when a name has multiple providers (pacman-style), rather than rely on `trans_prepare()`'s automatic choice. Must be called before `trans_prepare()`. Backed internally by `dep_graph_prefer()` (`src/graph/graph_priv.h`, not public — the transaction's graph is never exposed to callers)
1111
- `dep_graph_export_dot()` (`include/apg/graph.h`, `src/graph/dot.c`): renders the graph to Graphviz DOT — every package as a node (installed ones filled green), every dependency as an edge to whatever `dep_graph_lookup()` currently resolves it to (labeled with the version constraint, red/dashed if unresolved), and every virtual/`provides` name as a diamond node with an edge to each of its providers (bold for the one that would currently win, dashed for the rest) — useful for visualizing exactly which provider a multi-provider name resolves to. Wrapped as `DependencyGraph::export_dot()` in the C++ bindings. Output verified against a real `dot` binary (renders to SVG without errors)
12+
- ctypes-based Python bindings (`bindings/python/apg/`), enabled with `-Dpython_bindings=enabled`. Pure stdlib, no compiled extension: loads the built `.so` directly (`APG_LIBRARY_PATH` env var, else `ctypes.util.find_library`), so there's no separate build step, mirroring the C++ bindings' philosophy. Covers the full public API — `Database`, `Transaction`, `DependencyGraph`, `Keyring`, `Package`/`PackageMetadata`, `version`/`journal`/`audit`/`sign`/`archive`/`install`/`copy`/`scripts` — with RAII-style `close()`/`__del__`/context-manager wrappers. Installable via `bindings/python/pyproject.toml` (hatchling). Covered by `bindings/python/tests/test_bindings.py`, run via `meson test`
13+
14+
### Fixed
15+
16+
- (Python bindings, new in this release, no prior public API affected) `Database.list()`/`Database.search()` could return `Package` objects wrapping garbage pointers: ctypes indexing into a `POINTER(POINTER(package))` array (`raw[i]`) returns a lazy view into that array's backing memory rather than a copied pointer value, so freeing the array (`db_list()`/`db_search()` require the caller to free the returned array) invalidated every `raw[i]` still referenced by the wrapped `Package` objects. Fixed by extracting each pointer's address via `ctypes.cast(raw[i], ctypes.c_void_p).value` and reconstructing an independent pointer object before freeing the array
1217

1318
### Changed
1419

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
- [x] ABI stabilization and documentation
66
- [x] C++ bindings (header-only, `bindings/cpp/`)
7-
- [ ] Python bindings
7+
- [x] Python bindings
88
- [x] Correctness tests: installation unit tests, fuzzing (package JSON, dep constraints)
99
- [x] pkg-config and CMake find module
1010
- [x] Drop the gpgme (OpenPGP) signing backend entirely; libsodium (Ed25519) becomes the only one

bindings/python/apg/__init__.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# SPDX-License-Identifier: GPL-3.0-only
2+
# SPDX-FileCopyrightText: 2026 AnmiTaliDev <anmitalidev@nuros.org>
3+
4+
from . import archive, audit, copy, install, keyring, scripts, sign
5+
from .config import InstallPolicy
6+
from .db import Database, DbVerifyIssue
7+
from .error import ApgError
8+
from .graph import DependencyGraph
9+
from .journal import JournalEntry, JournalOp, JournalStatus
10+
from .package import Package, PackageMetadata, PackageMetadataView
11+
from .transaction import (
12+
Transaction,
13+
TransBlockedRemoveView,
14+
TransConflictView,
15+
TransError,
16+
TransFileConflictView,
17+
TransHeldPkgView,
18+
TransOp,
19+
TransStepView,
20+
)
21+
from .version import DepConstraint, VerOp, dep_constraint_parse, dep_constraint_to_str, ver_compare, ver_satisfies
22+
23+
__all__ = [
24+
"ApgError",
25+
"InstallPolicy",
26+
"Database",
27+
"DbVerifyIssue",
28+
"DependencyGraph",
29+
"JournalEntry",
30+
"JournalOp",
31+
"JournalStatus",
32+
"Package",
33+
"PackageMetadata",
34+
"PackageMetadataView",
35+
"Transaction",
36+
"TransBlockedRemoveView",
37+
"TransConflictView",
38+
"TransError",
39+
"TransFileConflictView",
40+
"TransHeldPkgView",
41+
"TransOp",
42+
"TransStepView",
43+
"DepConstraint",
44+
"VerOp",
45+
"dep_constraint_parse",
46+
"dep_constraint_to_str",
47+
"ver_compare",
48+
"ver_satisfies",
49+
"archive",
50+
"audit",
51+
"copy",
52+
"install",
53+
"keyring",
54+
"scripts",
55+
"sign",
56+
]

0 commit comments

Comments
 (0)