Source code for meta_package_manager.managers.snap

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

from __future__ import annotations

import re

from extra_platforms import UNIX_WITHOUT_MACOS

from ..capabilities import search_capabilities, version_not_implemented
from ..manager import PackageManager

TYPE_CHECKING = False
if TYPE_CHECKING:
    from collections.abc import Iterator

    from ..package import Package


[docs] class Snap(PackageManager): """Canonical's snap installs sandboxed, self-updating packages. snaps refresh themselves on a schedule, so `upgrade` merely forces a `snap refresh` sooner. Mutating operations escalate through `sudo` by default: the privileged work happens in the `snapd` daemon, but the daemon refuses state changes from an unprivileged client (`snap install` as a plain user is denied with a hint to retry with `sudo`), which is why the Snap store documents `sudo snap install` as the canonical invocation. A host authenticated against the store with `snap login` (or granted a polkit rule) can drop the wrap with `--no-sudo` or a `[mpm.managers.snap] sudo = false` override. ```{note} snap localizes and colorizes its table headers with no terminal detection. mpm pins nothing to English: `--color=never` strips the ANSI, the header row is dropped, and every row is split on whitespace and read by column position, so the translated headers never reach the parser. ``` ```{note} `snap refresh --list` reports only the available version, so `outdated` looks each installed version up by ID from the cached installed set. `search` runs `snap find`, which matches summaries as well as names, so mpm refilters the results. ``` """ homepage_url = "https://snapcraft.io" logo = "snapcraft" platforms = UNIX_WITHOUT_MACOS default_sudo = True requirement = ">=2.0.0" post_args = ("--color=never",) _SEARCH_REGEXP = re.compile( r"^(?P<package_id>\S+)\s+(?P<version>\S+)\s+\S+\s+\S+\s+(?P<description>.+)$", re.MULTILINE, ) version_regexes = (r"snap\s+(?P<version>\S+)",) """ ```{code-block} shell-session $ snap --version snap 2.44.1 snapd 2.44.1 series 16 linuxmint 19.3 kernel 4.15.0-91-generic ``` """ @property def installed(self) -> Iterator[Package]: """Fetch installed packages. ```{code-block} shell-session $ snap list --color=never Name Version Rev Aufzeichnung Herausgeber Hinweise core 16-2.44.1 8935 latest/stable canonical✓ core wechat 2.0 7 latest/stable ubuntu-dawndiy - pdftk 2.02-4 9 latest/stable smoser - ``` """ output = self.run_cli("list") for package in output.splitlines()[1:]: parts = package.split() yield self.package(id=parts[0], installed_version=parts[1]) @property def outdated(self) -> Iterator[Package]: """Fetch outdated packages. ```{code-block} shell-session $ snap refresh --list --color=never Name Version Rev Herausgeber Hinweise standard-notes 3.3.5 8 standardnotes✓ - ``` """ output = self.run_cli("refresh", "--list") installed_versions = self.installed_version_map for package in output.splitlines()[1:]: parts = package.split() package_id = parts[0] yield self.package( id=package_id, latest_version=parts[1], installed_version=installed_versions.get(package_id), )
[docs] @search_capabilities(extended_support=False, exact_support=False) def search(self, query: str, extended: bool, exact: bool) -> Iterator[Package]: """Fetch matching packages. ```{caution} Search is extended by default. So we return the best subset of results and let {meth}`meta_package_manager.manager.PackageManager.refiltered_search` refine them. ``` ```{code-block} shell-session $ snap find doc --color=never Name Version Herausgeber Hinweise Zusammenfassung journey 2.14.3 2appstudio - Your private diary. nextcloud 17.0.5snap1 nextcloud✓ - Nextcloud Server skype 8.58.0.93 skype✓ classic One Skype for all. ``` """ output = self.run_cli("find", query) headerless_table = None if output: table = output.split("\n", 1) if len(table) > 1: headerless_table = table[1] if headerless_table: for package_id, version, description in self._SEARCH_REGEXP.findall( headerless_table, ): yield self.package( id=package_id, description=description, latest_version=version, )
[docs] @version_not_implemented def install(self, package_id: str, version: str | None = None) -> str: """Install one package. ```{code-block} shell-session $ sudo snap install standard-notes --color=never ``` """ return self.run_cli("install", package_id, sudo=True)
[docs] def upgrade_all_cli(self) -> tuple[str, ...]: """Generates the CLI to upgrade all outdated packages. ```{code-block} shell-session $ sudo snap refresh --color=never ``` """ return self.build_cli("refresh", sudo=True)
[docs] @version_not_implemented def upgrade_one_cli( self, package_id: str, version: str | None = None, ) -> tuple[str, ...]: """Generates the CLI to upgrade the provided package. ```{code-block} shell-session $ sudo snap refresh standard-notes --color=never ``` """ return self.build_cli("refresh", package_id, sudo=True)
[docs] def remove(self, package_id: str) -> str: """Remove one package. Unlike `list`, `find`, `refresh` and `install`, snap's `remove` subcommand rejects the global `--color` flag (`error: unknown flag 'color'`). Its output is not parsed, so the `--color=never` post-arg is dropped here with `auto_post_args=False`. ```{code-block} shell-session $ sudo snap remove standard-notes ``` """ return self.run_cli("remove", package_id, sudo=True, auto_post_args=False)