Source code for repoman.models
"""Data models: Repository, WizardState, FileFormat, and AvailabilityStatus."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass, field
from enum import Enum, auto
from pathlib import Path
[docs]
class AvailabilityStatus(Enum):
"""Availability of a repository for a target Ubuntu codename.
CHECKING is an in-progress sentinel used only by the wizard UI — it is
never written back to the Repository object. The background thread transitions
directly from UNKNOWN to AVAILABLE, UNAVAILABLE, or (on network failure) UNKNOWN.
"""
UNKNOWN = auto() # not yet checked
CHECKING = auto() # in progress (wizard UI only — never stored on repo)
AVAILABLE = auto() # confirmed for target release
UNAVAILABLE = auto() # confirmed not available
SUITE_AGNOSTIC = auto() # "stable"-style URL, always passes
# Maps each AvailabilityStatus to (icon_name, css_class) for UI badges.
# UNKNOWN and CHECKING have no badge (handled separately by callers).
AVAILABILITY_ICONS: dict[AvailabilityStatus, tuple[str, str]] = {
AvailabilityStatus.AVAILABLE: ("pamac-tray-no-update", "success"),
AvailabilityStatus.UNAVAILABLE: ("dialog-warning-symbolic", "warning"),
AvailabilityStatus.SUITE_AGNOSTIC: ("locked-symbolic", ""),
}
[docs]
@dataclass
class Repository:
"""APT source entry parsed from a .sources or .list file."""
source_file: Path
file_format: FileFormat
types: list[str] # ["deb"] or ["deb", "deb-src"]
uris: list[str]
suites: list[str] # ["noble"] or ["stable"] for suite-agnostic
components: list[str]
enabled: bool
description: str | None # Description: field; None → show URI as fallback
signed_by: str | None
architectures: list[str] = field(default_factory=list) # Architectures: amd64 arm64 …
# Derived — not stored in file
is_ppa: bool = field(init=False)
ppa_owner: str | None = field(init=False)
ppa_name: str | None = field(init=False)
availability: AvailabilityStatus = AvailabilityStatus.UNKNOWN
def __post_init__(self) -> None:
uri = self.uris[0] if self.uris else ""
self.is_ppa = "ppa.launchpadcontent.net" in uri or "ppa.launchpad.net" in uri
if self.is_ppa:
# https://ppa.launchpadcontent.net/{owner}/{ppa}/ubuntu
parts = uri.rstrip("/").split("/")
self.ppa_owner = parts[-3] if len(parts) >= 3 else None
self.ppa_name = parts[-2] if len(parts) >= 2 else None
else:
self.ppa_owner = self.ppa_name = None
@property
def display_name(self) -> str:
"""Human-readable label: description if set, otherwise the primary URI.
:returns: Description string, or first URI, or the literal ``(unknown)``
if the repo has no URIs.
:rtype: str
"""
if self.description:
return self.description
return self.uris[0] if self.uris else "(unknown)"
[docs]
@dataclass
class WizardState:
"""Shared state threaded through all upgrade wizard pages."""
candidate_repos: list[Repository]
target_codename: str
selected: list[Repository] = field(default_factory=list)
on_complete: Callable[[], None] | None = None