apt

Abstractions for the system’s Debian/Ubuntu package information and repositories.

This module contains abstractions and wrappers around Debian/Ubuntu-style repositories and packages, in order to easily provide an idiomatic and Pythonic mechanism for adding packages and/or repositories to systems for use in machine charms.

A sane default configuration is attainable through nothing more than instantiation of the appropriate classes. DebianPackage objects provide information about the architecture, version, name, and status of a package.

DebianPackage will try to look up a package either from dpkg -L or from apt-cache when provided with a string indicating the package name. If it cannot be located, PackageNotFoundError will be raised, as apt and dpkg otherwise return 100 for all errors, and a meaningful error message if the package is not known is desirable.

To install packages with convenience methods:

try:
    # Run `apt-get update`
    apt.update()
    apt.add_package("zsh")
    apt.add_package(["vim", "htop", "wget"])
except PackageError as e:
    logger.error("could not install package. Reason: %s", e.message)

The convenience methods don’t raise PackageNotFoundError. If any packages aren’t found in the cache, apt.add_package raises PackageError with a message ‘Failed to install packages: foo, bar’.

To find details of a specific package:

try:
    vim = apt.DebianPackage.from_system("vim")

    # To find from the apt cache only
    # apt.DebianPackage.from_apt_cache("vim")

    # To find from installed packages only
    # apt.DebianPackage.from_installed_package("vim")

    vim.ensure(PackageState.Latest)
    logger.info("updated vim to version: %s", vim.fullversion)
except PackageNotFoundError:
    logger.error("a specified package not found in package cache or on system")
except PackageError as e:
    logger.error("could not install package. Reason: %s", e.message)

RepositoryMapping will return a dict-like object containing enabled system repositories and their properties (available groups, URI, GPG key). This class can add, disable, or manipulate repositories. Items can be retrieved as DebianRepository objects.

In order to add a new repository with explicit details for fields, a new DebianRepository can be added to RepositoryMapping

RepositoryMapping provides an abstraction around the existing repositories on the system, and can be accessed and iterated over like any Mapping object, to retrieve values by key, iterate, or perform other operations.

Keys are constructed as {repo_type}-{}-{release} in order to uniquely identify a repository.

Repositories can be added with explicit values through a Python constructor.

Example:

repositories = apt.RepositoryMapping()
if "deb-example.com-focal" not in repositories:
    repositories.add(
        DebianRepository(
            enabled=True,
            repotype="deb",
            uri="https://example.com",
            release="focal",
            groups=["universe"],
        )
    )

Alternatively, any valid sources.list line may be used to construct a new DebianRepository.

Example:

repositories = apt.RepositoryMapping()
if "deb-us.archive.ubuntu.com-xenial" not in repositories:
    line = "deb http://us.archive.ubuntu.com/ubuntu xenial main restricted"
    repo = DebianRepository.from_repo_line(line)
    repositories.add(repo)
exception Error

Bases: Exception

Base class of most errors raised by this library.

property name

Return a string representation of the model plus class.

property message

Return the message passed as an argument.

exception PackageError

Bases: Error

Raised when there’s an error installing or removing a package.

Additionally, apt.add_package raises PackageError if any packages aren’t found in the cache.

exception PackageNotFoundError

Bases: Error

Raised by DebianPackage methods if a requested package is not found.

class PackageState(*values)

Bases: Enum

A class to represent possible package states.

Present = 'present'
Absent = 'absent'
Latest = 'latest'
Available = 'available'
class DebianPackage(
name: str,
version: str,
epoch: str,
arch: str,
state: PackageState,
)

Bases: object

Represents a traditional Debian package and its utility functions.

DebianPackage wraps information and functionality around a known package, whether installed or available. The version, epoch, name, and architecture can be easily queried and compared against other DebianPackage objects to determine the latest version or to install a specific version.

The representation of this object as a string mimics the output from dpkg for familiarity.

Installation and removal of packages is handled through the state property or ensure method, with the following options:

When DebianPackage is initialised, the state of a given DebianPackage object will be set to Available, Present, or Latest, with Absent implemented as a convenience for removal (though it operates essentially the same as Available).

property name: str

Returns the name of the package.

ensure(state: PackageState)

Ensure that a package is in a given state.

Parameters:

state – a PackageState to reconcile the package to

Raises:

PackageError – from the underlying call to apt

property present: bool

Return whether or not a package is present.

property latest: bool

Return whether the package is the most recent version.

property state: PackageState

Return the current package state.

property version: Version

Return the version for a package.

property epoch: str

Return the epoch for a package. May be unset.

property arch: str

Return the architecture for a package.

property fullversion: str

Return the name+epoch for a package.

classmethod from_system(
package: str,
version: str | None = '',
arch: str | None = '',
) DebianPackage

Locate a package, either on the system or known to apt, and serialises the information.

Parameters:
  • package – a string representing the package

  • version – an optional string if a specific version is requested

  • arch – an optional architecture, defaulting to dpkg --print-architecture. If an architecture is not specified, this will be used for selection.

classmethod from_installed_package(
package: str,
version: str | None = '',
arch: str | None = '',
) DebianPackage

Check whether the package is already installed and return an instance.

Parameters:
  • package – a string representing the package

  • version – an optional string if a specific version is requested

  • arch – an optional architecture, defaulting to dpkg --print-architecture. If an architecture is not specified, this will be used for selection.

classmethod from_apt_cache(
package: str,
version: str | None = '',
arch: str | None = '',
) DebianPackage

Check whether the package is already installed and return an instance.

Parameters:
  • package – a string representing the package

  • version – an optional string if a specific version is requested

  • arch – an optional architecture, defaulting to dpkg --print-architecture. If an architecture is not specified, this will be used for selection.

class Version(version: str, epoch: str)

Bases: object

An abstraction around package versions.

This seems like it should be strictly unnecessary, except that apt_pkg is not usable inside a venv, and wedging version comparisons into DebianPackage would over complicate it.

This class implements the algorithm found here: https://www.debian.org/doc/debian-policy/ch-controlfields.html#version

property epoch

Return the epoch for a package. May be empty.

property number: str

Return the version number for a package.

__ne__(other: object) bool

Not equal to magic method implementation.

add_package(
package_names: str,
version: str | None = '',
arch: str | None = '',
update_cache: bool = False,
) DebianPackage
add_package(
package_names: list[str],
version: str | None = '',
arch: str | None = '',
update_cache: bool = False,
) DebianPackage | list[DebianPackage]

Add a package or list of packages to the system.

Parameters:
  • package_names – single package name, or list of package names

  • name – the name(s) of the package(s)

  • version – an (optional) version as a string. Defaults to the latest known

  • arch – an optional architecture for the package

  • update_cache – whether or not to run apt-get update prior to operating

Raises:
  • TypeError – if no package name is given, or explicit version is set for multiple packages

  • PackageError – if packages fail to install, including if any packages aren’t found in the cache

remove_package(package_names: str) DebianPackage
remove_package(
package_names: list[str],
) DebianPackage | list[DebianPackage]

Remove package(s) from the system.

Parameters:

package_names – the name of a package

Raises:

TypeError – if no packages are provided

update() None

Update the apt cache via apt-get update.

import_key(key: str) str

Import an ASCII Armor key.

A Radix64 format key ID is also supported for backwards compatibility. In this case Ubuntu key server will be queried for a key via HTTPS by its key ID. This method is less preferable because HTTPS proxy servers may require traffic decryption which is equivalent to a man-in-the-middle attack (a proxy server impersonates key server TLS certificates and has to be explicitly trusted by the system).

Parameters:

key – A GPG key in ASCII armor format, including BEGIN and END markers or a key ID.

Returns:

The GPG key filename written.

Raises:

GPGKeyError – if the key could not be imported

exception InvalidSourceError

Bases: Error

Exception for invalid source entries.

exception GPGKeyError

Bases: Error

Exception for GPG keys.

class DebianRepository(
enabled: bool,
repotype: str,
uri: str,
release: str,
groups: list[str],
filename: str = '',
gpg_key_filename: str = '',
options: dict[str, str] | None = None,
)

Bases: object

An abstraction to represent a repository.

property enabled

Return whether or not the repository is enabled.

property repotype

Return whether it is binary or source.

property uri

Return the URI.

property release

Return which Debian/Ubuntu releases it is valid for.

property groups

Return the enabled package groups.

property filename

Returns the filename for a repository.

property gpg_key

Returns the path to the GPG key for this repository.

property options

Returns any additional repo options which are set.

make_options_string(include_signed_by: bool = True) str

Generate the complete one-line-style options string for a repository.

Combining gpg_key, if set (and include_signed_by is True), with any other provided options to form the options section of a one-line-style definition.

static prefix_from_uri(uri: str) str

Get a repo list prefix from the URI, depending on whether a path is set.

static from_repo_line(
repo_line: str,
write_file: bool | None = True,
) DebianRepository

Instantiate a new DebianRepository from a sources.list entry line.

Parameters:
  • repo_line – a string representing a repository entry

  • write_file – Boolean to enable writing the new repo to disk. True by default. Expect it to result in an add-apt-repository call under the hood, like add-apt-repository --no-update --sourceslist="$repo_line"

disable() None

Remove this repository by disabling it in the source file.

WARNING: This method does NOT alter the DebianRepository.enabled flag.

WARNING: disable is currently not implemented for repositories defined by a deb822 stanza. Raises a NotImplementedError in this case.

import_key(key: str) None

Import an ASCII Armor key.

A Radix64 format key ID is also supported for backwards compatibility. In this case Ubuntu key server will be queried for a key via HTTPS by its key ID. This method is less preferable because HTTPS proxy servers may require traffic decryption which is equivalent to a man-in-the-middle attack (a proxy server impersonates key server TLS certificates and has to be explicitly trusted by the system).

Parameters:

key – A GPG key in ASCII armor format, including BEGIN and END markers or a key ID.

Raises:

GPGKeyError – if the key could not be imported

class RepositoryMapping

Bases: Mapping[str, DebianRepository]

An representation of known repositories.

Instantiation of RepositoryMapping will iterate through the filesystem, parse out repository files in /etc/apt/…, and create DebianRepository objects in this list.

Typical usage:

repositories = apt.RepositoryMapping()
repositories.add(
    DebianRepository(
        enabled=True,
        repotype="deb",
        uri="https://example.com",
        release="focal",
        groups=["universe"],
    )
)
__contains__(key: Any) bool

Magic method for checking presence of repo in mapping.

Checks against the string names used to identify repositories.

__len__() int

Return number of repositories in map.

__iter__() Iterator[DebianRepository]

Return iterator for RepositoryMapping.

Iterates over the DebianRepository values rather than the string names.

Note: this breaks the expectations of the Mapping abstract base class for example when it provides methods like keys and items

__getitem__(repository_uri: str) DebianRepository

Return a given DebianRepository.

__setitem__(
repository_uri: str,
repository: DebianRepository,
) None

Add a DebianRepository to the cache.

load_deb822(filename: str) None

Load a deb822 format repository source file into the cache.

In contrast to one-line-style, the deb822 format specifies a repository using a multi-line stanza. Stanzas are separated by white space, and each definition consists of lines that are either key: value pairs, or continuations of the previous value.

Read more about the deb822 format here:

https://manpages.ubuntu.com/manpages/noble/en/man5/sources.list.5.html

For instance, Ubuntu 24.04 (noble) lists its sources using deb822 style in:

/etc/apt/sources.list.d/ubuntu.sources

load(filename: str)

Load a one-line-style format repository source file into the cache.

Parameters:

filename – the path to the repository file

add(
repo: DebianRepository,
default_filename: bool | None = False,
) None

Add a new repository to the system using add-apt-repository.

Parameters:

repo – a DebianRepository object if DebianRepository.enabled is false, will return without adding the repo

Raises:

CalledProcessError – if there’s an error running apt-add-repository

WARNING: Does not associate the repository with a signing key. Use import_key to add a signing key globally.

WARNING: if DebianRepository.enabled is false, will return without adding the repo

WARNING: Don’t forget to call update before installing any packages! Or call add_package with update_cache=True.

WARNING: the default_filename keyword argument is provided for backwards compatibility only. It is not used, and was not used in the previous revision of this library.

disable(repo: DebianRepository) None

Remove a repository by disabling it in the source file.

WARNING: disable is currently not implemented for repositories defined by a deb822 stanza, and will raise a NotImplementedError if called on one.

WARNING: This method does NOT alter the DebianRepository.enabled flag.

exception MissingRequiredKeyError(
message: str = '',
*,
file: str,
line: int | None,
key: str,
)

Bases: InvalidSourceError

Missing a required value in a source file.

exception BadValueError(
message: str = '',
*,
file: str,
line: int | None,
key: str,
value: str,
)

Bases: InvalidSourceError

Bad value for an entry in a source file.