Intro
A common use case for DNF5 API is installing packages, which is quite straightfoward.
First, we need to create a Base
, the core object that holds a runtime
environment. We load its configuration from the system and run the setup
method to prepare the environment.
Next, we can prepare a repository sack, which holds information about configured repositories and the state of local and remote packages, while potentially refreshing metadata from remote servers if needed.
With the setup complete, we can tell DNF5 what we want to do, in this case,
install our package. This is done by configuring the Goal
object.
After defining our intention, we can proceed to calculate the transaction, determining the necessary actions to achieve our goal. Finally, we can perform the resulting action, which is downloading and installing the packages.
An example Python script that accomplishes this might look like this:
import libdnf5
base = libdnf5.base.Base()
base.load_config_from_file()
base.setup()
sack = base.get_repo_sack()
sack.create_repos_from_system_configuration()
sack.update_and_load_enabled_repos(True)
goal = libdnf5.base.Goal(base)
goal.add_install('my-awesome-package')
transaction = goal.resolve()
transaction.download()
transaction.run()
After this point, if everything went well, the package should be successfully installed on the system.
What could be tricky is when trying to use the API after the transaction. The problem is that the existing repository sack does not reflect the updated state after the transaction was executed. This is because managing that state with connected third-party libraries would be very difficult.
Use information from the Transaction object
If you only need to query information about the post-transaction state, you can use the data
provided by the Transaction
object.
The get_transaction_packages()
method can be particularly useful for this purpose. It allows
us to query which packages were involved in the transaction, the specific actions taken with
these packages, and any packages they may have been replaced with.
For example, let’s say we want to retrieve a list of new files that were installed during the transaction. In this case, we’ll need to pre-load the filelists metadata in DNF5 before loading the repository sack. We can do this by adding the following code:
base.get_config().get_optional_metadata_types_option().add_item('filelists')
Then, you can use the helper function transaction_item_action_is_inbound
to filter only
inbound packages from the transaction. Finally, you can query the package files contained
in the transaction:
newly_installed_files = set()
for transaction_package in transaction.get_transaction_packages():
action = transaction_package.get_action()
if libdnf5.base.transaction.transaction_item_action_is_inbound(action):
package_files = transaction_package.get_package().get_files()
newly_installed_files |= set(package_files)
Start with a new Base
The easiest and most robust way is probably to create a new Base
each time
we need a fresh state. Although some work is done repeatedly, there is no
additional unnecessary network trafic as the metadata is already refreshed
during the first attempt. This approach allows us to perform any task as if
we were executing a new separate script for each one.
import libdnf5
def create_base():
base = libdnf5.base.Base()
base.load_config_from_file()
base.setup()
sack = base.get_repo_sack()
sack.create_repos_from_system_configuration()
sack.update_and_load_enabled_repos(True)
return base
def install_package(spec):
base = create_base()
goal = libdnf5.base.Goal(base)
goal.add_install(spec)
transaction = goal.resolve()
transaction.download()
transaction.run()
def query_installed():
base = create_base()
query = libdnf5.rpm.PackageQuery(base)
query.filter_installed()
return [package.get_nevra() for package in query]
install_package('my-awesome-package')
print(query_installed())
Use the RPM API
Another alternative is to use the underlying RPM API. This should be the most effective way for querying information about installed packages, as we are directly reading the data from the SQLite database:
import rpm
# Prepare the transaction set while ignoring package signatures verification
transaction_set = rpm.TransactionSet()
transaction_set.setVSFlags(rpm._RPMVSF_NOSIGNATURES)
# Find the newest package in the database
last_package = max(transaction_set.dbMatch(),
key=lambda package: package[rpm.RPMTAG_INSTALLTIME])
# Get packages only related to the latest transaction
last_packages = transaction_set.dbMatch(rpm.RPMTAG_INSTALLTID,
last_package[rpm.RPMTAG_INSTALLTID])
# Aggregate all related files
files = set()
for package in last_packages:
files |= set(package[rpm.RPMTAG_FILENAMES])