diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/__init__.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/__init__.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/__init__.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/__init__.py 2023-12-13 13:35:36.000000000 +0000 @@ -18,8 +18,6 @@ # USA # import the core of apt_pkg """High-Level Interface for working with apt.""" -from __future__ import print_function - import apt_pkg # import some fancy classes diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/auth.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/auth.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/auth.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/auth.py 2023-12-13 13:35:36.000000000 +0000 @@ -24,8 +24,6 @@ # USA """Handle GnuPG keys used to trust signed repositories.""" -from __future__ import print_function - import errno import os import os.path diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/cache.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/cache.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/cache.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/cache.py 2023-12-13 13:35:36.000000000 +0000 @@ -19,8 +19,6 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA -from __future__ import print_function - import fnmatch import os import warnings diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/cdrom.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/cdrom.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/cdrom.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/cdrom.py 2023-12-13 13:35:36.000000000 +0000 @@ -20,8 +20,6 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA """Classes related to cdrom handling.""" -from __future__ import print_function - from typing import Optional import glob diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/debfile.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/debfile.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/debfile.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/debfile.py 2023-12-13 13:35:36.000000000 +0000 @@ -17,15 +17,14 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA """Classes for working with locally available Debian packages.""" -from __future__ import print_function import apt import apt_inst import apt_pkg + import gzip import os import sys - from typing import Dict, Iterable, List, Optional, Set, Tuple, Union, cast from apt_pkg import gettext as _ diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/package.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/package.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/package.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/package.py 2023-12-13 13:35:36.000000000 +0000 @@ -19,8 +19,6 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA """Functionality related to packages.""" -from __future__ import print_function - import logging import os import sys @@ -878,7 +876,7 @@ % ( self.package.name, self.version, - getattr(index, "describe", ""), + getattr(index, "describe", ""), ) ) if not self.uri: @@ -1298,7 +1296,7 @@ path = "/var/lib/dpkg/info/%s.list" % name try: with open(path, "rb") as file_list: - return file_list.read().decode("utf-8").split("\n") + return file_list.read().decode("utf-8").strip().split("\n") except EnvironmentError: continue diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/progress/__init__.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/progress/__init__.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/progress/__init__.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/progress/__init__.py 2023-12-13 13:35:36.000000000 +0000 @@ -23,8 +23,6 @@ for terminals, etc. """ -from __future__ import print_function - from typing import Sequence diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/progress/base.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/progress/base.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/progress/base.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/progress/base.py 2023-12-13 13:35:36.000000000 +0000 @@ -22,8 +22,6 @@ Custom progress classes should inherit from these classes. They can also be used as dummy progress classes which simply do nothing. """ -from __future__ import print_function - import errno import fcntl import io diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/progress/text.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/progress/text.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/progress/text.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/progress/text.py 2023-12-13 13:35:36.000000000 +0000 @@ -15,8 +15,6 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA """Progress reporting for text interfaces.""" -from __future__ import print_function - import io import os import signal diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/utils.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/utils.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/apt/utils.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/apt/utils.py 2023-12-13 13:35:36.000000000 +0000 @@ -16,8 +16,6 @@ # 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., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -from __future__ import print_function - import datetime import os diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/__init__.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/__init__.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/__init__.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/__init__.py 2023-12-13 13:35:36.000000000 +0000 @@ -1,5 +1,3 @@ -from __future__ import print_function - import apt_pkg diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/_deb822.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/_deb822.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/_deb822.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/_deb822.py 2023-12-13 13:35:36.000000000 +0000 @@ -23,7 +23,17 @@ This represents a single deb822 section. """ - def __init__(self, section: str): + tags: collections.OrderedDict[str, str] + header: str + footer: str + + def __init__(self, section: typing.Union[str, "Section"]): + if isinstance(section, Section): + self.tags = collections.OrderedDict(section.tags) + self.header = section.header + self.footer = section.footer + return + comments = ["", ""] in_section = False trimmed_section = "" @@ -95,14 +105,26 @@ """ def __init__(self, fobj: io.TextIOBase): - sections = fobj.read().split("\n\n") - self.sections = [Section(s) for s in sections] + self.sections = [] + section = "" + for line in fobj: + if not line.isspace(): + # A line is part of the section if it has non-whitespace characters + section += line + elif section: + # Our line is just whitespace and we have gathered section content, so let's write out the section + self.sections.append(Section(section)) + section = "" + + # The final section may not be terminated by an empty line + if section: + self.sections.append(Section(section)) def __iter__(self) -> typing.Iterator[Section]: return iter(self.sections) def __str__(self) -> str: - return "\n\n".join(str(s) for s in self.sections) + return "\n".join(str(s) for s in self.sections) if __name__ == "__main__": diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/distinfo.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/distinfo.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/distinfo.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/distinfo.py 2023-12-13 13:35:36.000000000 +0000 @@ -21,8 +21,6 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA -from __future__ import print_function - import csv import errno import logging diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/distro.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/distro.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/distro.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/distro.py 2023-12-13 13:35:36.000000000 +0000 @@ -97,7 +97,7 @@ cdrom_comps = [] enabled_comps = [] # source_code = [] - for source in self.sourceslist.list: + for source in self.sourceslist.exploded_list(): if ( not source.invalid and self.is_codename(source.dist) @@ -289,7 +289,16 @@ comps = list(self.enabled_comps) if type is None: type = self.binary_type - new_source = self.sourceslist.add(type, uri, dist, comps, comment) + + parent = None + file = None + for parent in reversed(self.child_sources) or reversed(self.main_sources): + file = parent.file + break + + new_source = self.sourceslist.add( + type, uri, dist, comps, comment, parent=parent, file=file + ) # if source code is enabled add a deb-src line after the new # source if self.get_source_code and type == self.binary_type: @@ -310,12 +319,13 @@ comp: the component that should be enabled """ - comps = set([comp]) + comps = list([comp]) # look for parent components that we may have to add for source in self.main_sources: for c in source.template.components: if c.name == comp and c.parent_component: - comps.add(c.parent_component) + if c.parent_component not in comps: + comps.append(c.parent_component) for c in comps: self._enable_component(c) @@ -335,7 +345,7 @@ if comp in comps_per_dist[source.dist]: return # add it - source.comps += [comp] + source.comps = source.comps + [comp] comps_per_dist[source.dist].add(comp) sources = [] @@ -394,7 +404,9 @@ sources.extend(self.main_sources) for source in sources: if comp in source.comps: - source.comps.remove(comp) + comps = source.comps + comps.remove(comp) + source.comps = comps if len(source.comps) < 1: self.sourceslist.remove(source) @@ -512,15 +524,10 @@ def _system_image_channel(): """Get the current channel from system-image-cli -i if possible.""" - from subprocess import Popen, PIPE + from subprocess import Popen, PIPE, DEVNULL import errno try: - from subprocess import DEVNULL - except ImportError: - # no DEVNULL in 2.7 - DEVNULL = os.open(os.devnull, os.O_RDWR) - try: out = Popen( ["system-image-cli", "-i"], stdout=PIPE, diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/sourceslist.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/sourceslist.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/aptsources/sourceslist.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/aptsources/sourceslist.py 2023-12-13 13:35:36.000000000 +0000 @@ -23,8 +23,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA -from __future__ import absolute_import, print_function - +import builtins import glob import io import logging @@ -36,6 +35,7 @@ Any, Dict, Callable, + Generic, Iterable, Iterator, List, @@ -44,6 +44,7 @@ TypeVar, Union, ) +import weakref import apt_pkg from .distinfo import DistInfo, Template @@ -110,8 +111,10 @@ self.__doc__ = doc def __get__( - self, obj: "Deb822SourceEntry", objtype: Optional[type] = None + self, obj: Optional["Deb822SourceEntry"], objtype: Optional[type] = None ) -> Optional[str]: + if obj is None: + return self # type: ignore return obj.section.get(self.key, None) def __set__(self, obj: "Deb822SourceEntry", value: Optional[str]) -> None: @@ -127,20 +130,48 @@ self.__doc__ = doc def __get__( - self, obj: "Deb822SourceEntry", objtype: Optional[type] = None + self, obj: Optional["Deb822SourceEntry"], objtype: Optional[type] = None ) -> List[str]: + if obj is None: + return self # type: ignore return SourceEntry.mysplit(obj.section.get(self.key, "")) def __set__(self, obj: "Deb822SourceEntry", values: List[str]) -> None: obj.section[self.key] = " ".join(values) +class ExplodedEntryProperty(property, Generic[T]): + def __init__(self, parent: T): + self.parent = parent + + def __get__( + self, obj: Optional["ExplodedDeb822SourceEntry"], objtype: Optional[type] = None + ) -> T: + if obj is None: + return self # type: ignore + return self.parent.__get__(obj.parent) # type: ignore + + def __set__(self, obj: "ExplodedDeb822SourceEntry", value: T) -> None: + obj.split_out() + self.parent.__set__(obj.parent, value) # type: ignore + + def DeprecatedProperty(prop: T) -> T: return prop +def _null_weakref() -> None: + """Behaves like an expired weakref.ref, returning None""" + return None + + class Deb822SourceEntry: - def __init__(self, section: Optional[Union[_deb822.Section, str]], file: str): + def __init__( + self, + section: Optional[Union[_deb822.Section, str]], + file: str, + list: Optional["SourcesList"] = None, + ): if section is None: self.section = _deb822.Section("") elif isinstance(section, str): @@ -151,6 +182,13 @@ self._line = str(self.section) self.file = file self.template: Optional[Template] = None # type DistInfo.Suite + self.may_merge = False + self._children = weakref.WeakSet["ExplodedDeb822SourceEntry"]() + + if list: + self._list: Callable[[], Optional[SourcesList]] = weakref.ref(list) + else: + self._list = _null_weakref def __eq__(self, other: Any) -> Any: # FIXME: Implement plurals more correctly @@ -185,6 +223,7 @@ @property def trusted(self) -> Optional[bool]: + """Return the value of the Trusted field""" try: return apt_pkg.string_to_bool(self.section["Trusted"]) except KeyError: @@ -236,6 +275,229 @@ """Deprecated (for deb822) accessor for .disabled""" self.disabled = not enabled + def merge(self, other: "AnySourceEntry") -> bool: + """Merge the two entries if they are compatible.""" + if ( + not self.may_merge + and self.template is None + and not all(child.template for child in self._children) + ): + return False + if self.file != other.file: + return False + if not isinstance(other, Deb822SourceEntry): + return False + if self.comment != other.comment and not any( + "Added by software-properties" in c for c in (self.comment, other.comment) + ): + return False + + for tag in list(self.section.tags) + list(other.section.tags): + if tag.lower() in ( + "types", + "uris", + "suites", + "components", + "architectures", + "signed-by", + ): + continue + in_self = self.section.get(tag, None) + in_other = other.section.get(tag, None) + if in_self != in_other: + return False + + if ( + sum( + [ + set(self.types) != set(other.types), + set(self.uris) != set(other.uris), + set(self.suites) != set(other.suites), + set(self.comps) != set(other.comps), + set(self.architectures) != set(other.architectures), + ] + ) + > 1 + ): + return False + + for typ in other.types: + if typ not in self.types: + self.types += [typ] + + for uri in other.uris: + if uri not in self.uris: + self.uris += [uri] + + for suite in other.suites: + if suite not in self.suites: + self.suites += [suite] + + for component in other.comps: + if component not in self.comps: + self.comps += [component] + + for arch in other.architectures: + if arch not in self.architectures: + self.architectures += [arch] + + return True + + def _reparent_children(self, to: "Deb822SourceEntry") -> None: + """If we end up being split, check if any of our children need to be reparented to the new parent.""" + for child in self._children: + for typ in to.types: + for uri in to.uris: + for suite in to.suites: + if (child._type, child._uri, child._suite) == (typ, uri, suite): + assert child.parent == self + child._parent = weakref.ref(to) + + +class ExplodedDeb822SourceEntry: + """This represents a bit of a deb822 paragraph corresponding to a legacy sources.list entry""" + + # Mostly we use slots to prevent accidentally assigning unproxied attributes + __slots__ = ["_parent", "_type", "_uri", "_suite", "template", "__weakref__"] + + def __init__(self, parent: Deb822SourceEntry, typ: str, uri: str, suite: str): + self._parent = weakref.ref(parent) + self._type = typ + self._uri = uri + self._suite = suite + self.template = parent.template + parent._children.add(self) + + @property + def parent(self) -> Deb822SourceEntry: + if self._parent is not None: + if (parent := self._parent()) is not None: + return parent + raise ValueError("The parent entry is no longer valid") + + @property + def uri(self) -> str: + self.__check_valid() + return self._uri + + @uri.setter + def uri(self, uri: str) -> None: + self.split_out() + self.parent.uris = [u if u != self._uri else uri for u in self.parent.uris] + self._uri = uri + + @property + def types(self) -> List[str]: + return [self.type] + + @property + def suites(self) -> List[str]: + return [self.dist] + + @property + def uris(self) -> List[str]: + return [self.uri] + + @property + def type(self) -> str: + self.__check_valid() + return self._type + + @type.setter + def type(self, typ: str) -> None: + self.split_out() + self.parent.types = [typ] + self._type = typ + self.__check_valid() + assert self._type == typ + assert self.parent.types == [self._type] + + @property + def dist(self) -> str: + self.__check_valid() + return self._suite + + @dist.setter + def dist(self, suite: str) -> None: + self.split_out() + self.parent.suites = [suite] + self._suite = suite + self.__check_valid() + assert self._suite == suite + assert self.parent.suites == [self._suite] + + def __check_valid(self) -> None: + if self.parent._list() is None: + raise ValueError("The parent entry is dead") + for type in self.parent.types: + for uri in self.parent.uris: + for suite in self.parent.suites: + if (type, uri, suite) == (self._type, self._uri, self._suite): + return + raise ValueError(f"Could not find parent of {self}") + + def split_out(self) -> None: + parent = self.parent + if (parent.types, parent.uris, parent.suites) == ( + [self._type], + [self._uri], + [self._suite], + ): + return + sources_list = parent._list() + if sources_list is None: + raise ValueError("The parent entry is dead") + + try: + index = sources_list.list.index(parent) + except ValueError as e: + raise ValueError( + f"Parent entry for partial deb822 {self} no longer valid" + ) from e + + sources_list.remove(parent) + + reparented = False + for type in reversed(parent.types): + for uri in reversed(parent.uris): + for suite in reversed(parent.suites): + new = Deb822SourceEntry( + section=_deb822.Section(parent.section), + file=parent.file, + list=sources_list, + ) + new.types = [type] + new.uris = [uri] + new.suites = [suite] + new.may_merge = True + + parent._reparent_children(new) + sources_list.list.insert(index, new) + if (type, uri, suite) == (self._type, self._uri, self._suite): + self._parent = weakref.ref(new) + reparented = True + if not reparented: + raise ValueError(f"Could not find parent of {self}") + + def __repr__(self) -> str: + return f" None: + """Set the source to enabled.""" + self.disabled = not enabled + + @property + def file(self) -> str: + """Return the file.""" + return self.parent.file + class SourceEntry: """single sources.list entry""" @@ -415,28 +677,31 @@ return line @property - def types(self) -> List[str]: + def types(self) -> List[builtins.str]: """deb822 compatible accessor for the type""" return [self.type] @property - def uris(self) -> List[str]: + def uris(self) -> List[builtins.str]: """deb822 compatible accessor for the uri""" return [self.uri] @property - def suites(self) -> List[str]: + def suites(self) -> List[builtins.str]: """deb822 compatible accessor for the suite""" return [self.dist] AnySourceEntry = Union[SourceEntry, Deb822SourceEntry] +AnyExplodedSourceEntry = Union[ + SourceEntry, Deb822SourceEntry, ExplodedDeb822SourceEntry +] class NullMatcher(object): """a Matcher that does nothing""" - def match(self, s: AnySourceEntry) -> bool: + def match(self, s: AnyExplodedSourceEntry) -> bool: return True @@ -464,11 +729,11 @@ self.list = [] # read sources.list file = apt_pkg.config.find_file("Dir::Etc::sourcelist") - if os.path.exists(file): + if file != "/dev/null" and os.path.exists(file): self.load(file) # read sources.list.d partsdir = apt_pkg.config.find_dir("Dir::Etc::sourceparts") - if os.path.exists(partsdir): + if partsdir != "/dev/null" and os.path.exists(partsdir): for file in os.listdir(partsdir): if (self.deb822 and file.endswith(".sources")) or file.endswith( ".list" @@ -485,12 +750,11 @@ for entry in self.list: yield entry - # typing: ignore def __find( - self, *predicates: Callable[[AnySourceEntry], bool], **attrs: Any - ) -> Iterator[AnySourceEntry]: + self, *predicates: Callable[[AnyExplodedSourceEntry], bool], **attrs: Any + ) -> Iterator[AnyExplodedSourceEntry]: uri = attrs.pop("uri", None) - for source in self.list: + for source in self.exploded_list(): if uri and source.uri and uri.rstrip("/") != source.uri.rstrip("/"): continue if all(getattr(source, key) == attrs[key] for key in attrs) and all( @@ -508,7 +772,8 @@ pos: int = -1, file: Optional[str] = None, architectures: Iterable[str] = [], - ) -> AnySourceEntry: + parent: Optional[AnyExplodedSourceEntry] = None, + ) -> AnyExplodedSourceEntry: """ Add a new source to the sources.list. The method will search for existing matching repos and will try to @@ -563,7 +828,12 @@ new_entry: AnySourceEntry if file is not None and file.endswith(".sources"): - new_entry = Deb822SourceEntry(None, file=file) + new_entry = Deb822SourceEntry(None, file=file, list=self) + if parent: + parent = getattr(parent, "parent", parent) + assert isinstance(parent, Deb822SourceEntry) + for k in parent.section.tags: + new_entry.section.tags[k] = parent.section.tags[k] new_entry.types = [type] new_entry.uris = [uri] new_entry.suites = [dist] @@ -597,8 +867,11 @@ self.list.insert(pos, new_entry) return new_entry - def remove(self, source_entry: SourceEntry) -> None: + def remove(self, source_entry: AnyExplodedSourceEntry) -> None: """remove the specified entry from the sources.list""" + if isinstance(source_entry, ExplodedDeb822SourceEntry): + source_entry.split_out() + source_entry = source_entry.parent self.list.remove(source_entry) def restore_backup(self, backup_ext: str) -> None: @@ -629,7 +902,7 @@ with open(file, "r") as f: if file.endswith(".sources"): for section in _deb822.File(f): - self.list.append(Deb822SourceEntry(section, file)) + self.list.append(Deb822SourceEntry(section, file, list=self)) else: for line in f: source = SourceEntry(line, file) @@ -637,6 +910,29 @@ except Exception as exc: logging.warning("could not open file '%s': %s\n" % (file, exc)) + def index(self, entry: AnyExplodedSourceEntry) -> int: + if isinstance(entry, ExplodedDeb822SourceEntry): + return self.list.index(entry.parent) + return self.list.index(entry) + + def merge(self) -> None: + """Merge consecutive entries that have been split back together.""" + merged = True + while merged: + i = 0 + merged = False + while i + 1 < len(self.list): + entry = self.list[i] + if isinstance(entry, Deb822SourceEntry): + j = i + 1 + while j < len(self.list): + if entry.merge(self.list[j]): + del self.list[j] + merged = True + else: + j += 1 + i += 1 + def save(self) -> None: """save the current sources""" files: Dict[str, io.TextIOWrapper] = {} @@ -653,11 +949,12 @@ f.write(header) return + self.merge() try: for source in self.list: if source.file not in files: files[source.file] = open(source.file, "w") - elif source.file.endswith(".sources"): + elif isinstance(source, Deb822SourceEntry): files[source.file].write("\n") files[source.file].write(source.str()) finally: @@ -690,6 +987,32 @@ # print self.parents return (parents, used_child_templates) + def exploded_list(self) -> List[AnyExplodedSourceEntry]: + """Present an exploded view of the list where each entry corresponds exactly to a Release file. + + A release file is uniquely identified by the triplet (type, uri, suite). Old style entries + always referred to a single release file, but deb822 entries allow multiple values for each + of those fields. + """ + res: List[AnyExplodedSourceEntry] = [] + for entry in self.list: + if isinstance(entry, SourceEntry): + res.append(entry) + elif ( + len(entry.types) == 1 + and len(entry.uris) == 1 + and len(entry.suites) == 1 + ): + res.append(entry) + else: + for typ in entry.types: + for uri in entry.uris: + for sui in entry.suites: + res.append(ExplodedDeb822SourceEntry(entry, typ, uri, sui)) + self.matcher.match(res[-1]) + + return res + class SourceEntryMatcher(object): """matcher class to make a source entry look nice @@ -710,7 +1033,7 @@ self.templates.append(template) return - def match(self, source: AnySourceEntry) -> bool: + def match(self, source: AnyExplodedSourceEntry) -> bool: """Add a matching template to the source""" found = False for template in self.templates: diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/debian/changelog python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/debian/changelog --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/debian/changelog 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/debian/changelog 2023-12-13 13:35:36.000000000 +0000 @@ -1,8 +1,8 @@ -python-apt (2.7.0+git20231122.1269~ubuntu22.04.1) jammy; urgency=low +python-apt (2.7.0+git20231213.1276~ubuntu22.04.1) jammy; urgency=low * Auto build. - -- Peter J. Mello Wed, 22 Nov 2023 19:32:25 +0000 + -- Peter J. Mello Wed, 13 Dec 2023 13:35:36 +0000 python-apt (2.7.0) unstable; urgency=medium diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/debian/git-build-recipe.manifest python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/debian/git-build-recipe.manifest --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/debian/git-build-recipe.manifest 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/debian/git-build-recipe.manifest 2023-12-13 13:35:36.000000000 +0000 @@ -1,2 +1,2 @@ -# git-build-recipe format 0.4 deb-version {debupstream}+git20231122.1269 -lp:python-apt git-commit:29d3e42d7d304acd9349ea8fe774ecc874517da2 +# git-build-recipe format 0.4 deb-version {debupstream}+git20231213.1276 +lp:python-apt git-commit:bcdef3d10eb7528f1cb45dd2b2adeae5dce5eb49 diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/doc/source/examples/dpkg-contents.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/doc/source/examples/dpkg-contents.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/doc/source/examples/dpkg-contents.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/doc/source/examples/dpkg-contents.py 2023-12-13 13:35:36.000000000 +0000 @@ -1,8 +1,6 @@ #!/usr/bin/python3 """Emulate dpkg --contents""" -from __future__ import print_function - import grp import pwd import stat diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/doc/source/examples/dpkg-extract.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/doc/source/examples/dpkg-extract.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/doc/source/examples/dpkg-extract.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/doc/source/examples/dpkg-extract.py 2023-12-13 13:35:36.000000000 +0000 @@ -1,8 +1,6 @@ #!/usr/bin/python3 """Emulate dpkg --extract package.deb outdir""" -from __future__ import print_function - import os import sys diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/doc/source/examples/dpkg-info.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/doc/source/examples/dpkg-info.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/doc/source/examples/dpkg-info.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/doc/source/examples/dpkg-info.py 2023-12-13 13:35:36.000000000 +0000 @@ -1,8 +1,6 @@ #!/usr/bin/python3 """Emulate dpkg --info package.deb control-file""" -from __future__ import print_function - import sys from apt_inst import DebFile diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/doc/source/library/apt_pkg.rst python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/doc/source/library/apt_pkg.rst --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/doc/source/library/apt_pkg.rst 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/doc/source/library/apt_pkg.rst 2023-12-13 13:35:36.000000000 +0000 @@ -1099,7 +1099,7 @@ Example: Find all missing dependencies ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -With the help of Dependency.AllTargets(), you can easily find all packages with +With the help of Dependency.all_targets(), you can easily find all packages with broken dependencies: .. literalinclude:: ../examples/missing-deps.py diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/apt_pkgmodule.cc python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/apt_pkgmodule.cc --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/apt_pkgmodule.cc 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/apt_pkgmodule.cc 2023-12-13 13:35:36.000000000 +0000 @@ -43,6 +43,13 @@ PyObject *PyAptError; +static char PyAptWarning_Doc[] = + "Exception class for most python-apt warnings.\n" + "\n" + ".. versionadded:: 2.7"; + +PyObject *PyAptWarning; + static char PyAptCacheMismatchError_Doc[] = "Raised when passing an object from a different cache to\n" ":class:`apt_pkg.DepCache` methods\n\n" @@ -886,6 +893,9 @@ PyAptError = PyErr_NewExceptionWithDoc("apt_pkg.Error", PyAptError_Doc, PyExc_SystemError, NULL); if (PyAptError == NULL) INIT_ERROR; + PyAptWarning = PyErr_NewExceptionWithDoc("apt_pkg.Warning", PyAptWarning_Doc, PyExc_Warning, NULL); + if (PyAptWarning == NULL) + INIT_ERROR; PyAptCacheMismatchError = PyErr_NewExceptionWithDoc("apt_pkg.CacheMismatchError", PyAptCacheMismatchError_Doc, PyExc_ValueError, NULL); if (PyAptCacheMismatchError == NULL) diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/depcache.cc python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/depcache.cc --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/depcache.cc 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/depcache.cc 2023-12-13 13:35:36.000000000 +0000 @@ -107,8 +107,6 @@ return HandleErrors(); } - //std::cout << "PM created" << std::endl; - PyInstallProgress iprogress; iprogress.setCallbackInst(pyInstallProgressInst); @@ -125,24 +123,17 @@ for (pkgAcquire::ItemIterator I = Fetcher.ItemsBegin(); I != Fetcher.ItemsEnd(); I++) { - //std::cout << "looking at: " << (*I)->DestFile - // << " status: " << (*I)->Status << std::endl; - if ((*I)->Status == pkgAcquire::Item::StatDone && (*I)->Complete == true) continue; if ((*I)->Status == pkgAcquire::Item::StatIdle) { - //std::cout << "transient failure" << std::endl; - Transient = true; //Failed = true; continue; } - //std::cout << "something is wrong!" << std::endl; - _error->Warning(_("Failed to fetch %s %s\n"),(*I)->DescURI().c_str(), (*I)->ErrorText.c_str()); Failed = true; @@ -158,7 +149,6 @@ // Try to deal with missing package files if (Failed == true && PM->FixMissing() == false) { - //std::cerr << "Unable to correct missing packages." << std::endl; _error->Error("Aborting install."); Py_INCREF(Py_None); return HandleErrors(Py_None); @@ -171,18 +161,14 @@ _system->UnLockInner(true); pkgPackageManager::OrderResult Res = iprogress.Run(PM); - //std::cout << "iprogress.Run() returned: " << (int)Res << std::endl; if (Res == pkgPackageManager::Failed || _error->PendingError() == true) { return HandleErrors(PyBool_FromLong(false)); } if (Res == pkgPackageManager::Completed) { - //std::cout << "iprogress.Run() returned Completed " << std::endl; Py_RETURN_TRUE; } - //std::cout << "looping again, install unfinished" << std::endl; - // Reload the fetcher object and loop again for media swapping Fetcher.Shutdown(); if (PM->GetArchives(&Fetcher,&List,&Recs) == false) { diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/generic.cc python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/generic.cc --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/generic.cc 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/generic.cc 2023-12-13 13:35:36.000000000 +0000 @@ -20,33 +20,40 @@ /* We throw away all warnings and only propogate the first error. */ PyObject *HandleErrors(PyObject *Res) { - if (_error->PendingError() == false) - { - // Throw away warnings - _error->Discard(); - return Res; - } - if (Res != 0) { - Py_DECREF(Res); } string Err; int errcnt = 0; + int wrncnt = 0; while (_error->empty() == false) { string Msg; bool Type = _error->PopMessage(Msg); - if (errcnt > 0) + if (errcnt > 0 || wrncnt > 0) Err.append(", "); Err.append((Type == true ? "E:" : "W:")); Err.append(Msg); - ++errcnt; + if (Type) + ++errcnt; + else + ++wrncnt; + } + if (errcnt > 0) + { + PyErr_SetString(PyAptError,Err.c_str()); + goto err; + } + else if (wrncnt > 0) + { + if (PyErr_WarnEx(PyAptWarning, Err.c_str(), 1) == -1) + goto err; } - if (errcnt == 0) - Err = "Internal Error"; - PyErr_SetString(PyAptError,Err.c_str()); - return 0; + + return Res; +err: + Py_DECREF(Res); + return nullptr; } /*}}}*/ diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/generic.h python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/generic.h --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/generic.h 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/generic.h 2023-12-13 13:35:36.000000000 +0000 @@ -37,6 +37,7 @@ * Exception class for almost all Python errors */ extern PyObject *PyAptError; +extern PyObject *PyAptWarning; /** * Exception class for invalidated cache objects. */ diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/progress.cc python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/progress.cc --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/python/progress.cc 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/python/progress.cc 2023-12-13 13:35:36.000000000 +0000 @@ -55,7 +55,6 @@ PyObject *method = PyObject_GetAttrString(callbackInst,(char*) method_name); if(method == NULL) { - //std::cerr << "Can't find '" << method_name << "' method" << std::endl; Py_XDECREF(arglist); if (res) { Py_INCREF(Py_None); @@ -125,7 +124,6 @@ bool PyFetchProgress::MediaChange(std::string Media, std::string Drive) { PyCbObj_END_ALLOW_THREADS - //std::cout << "MediaChange" << std::endl; PyObject *arglist = Py_BuildValue("(ss)", Media.c_str(), Drive.c_str()); PyObject *result = NULL; @@ -147,7 +145,6 @@ void PyFetchProgress::UpdateStatus(pkgAcquire::ItemDesc &Itm, int status) { - //std::cout << "UpdateStatus: " << Itm.URI << " " << status << std::endl; // Added object file size and object partial size to // parameters that are passed to updateStatus. // -- Stephan @@ -231,7 +228,6 @@ void PyFetchProgress::Start() { - //std::cout << "Start" << std::endl; pkgAcquireStatus::Start(); @@ -252,7 +248,6 @@ */ PyCbObj_END_ALLOW_THREADS - //std::cout << "Stop" << std::endl; pkgAcquireStatus::Stop(); RunSimpleCallback("stop"); } @@ -262,7 +257,6 @@ PyCbObj_END_ALLOW_THREADS pkgAcquireStatus::Pulse(Owner); - //std::cout << "Pulse" << std::endl; if(callbackInst == 0) { PyCbObj_BEGIN_ALLOW_THREADS return false; @@ -341,7 +335,6 @@ // support custom fork methods if(PyObject_HasAttrString(callbackInst, "fork")) { PyObject *method = PyObject_GetAttrString(callbackInst, "fork"); - std::cerr << "custom fork found" << std::endl; PyObject *arglist = Py_BuildValue("()"); PyObject *result = PyObject_CallObject(method, arglist); Py_DECREF(arglist); @@ -354,9 +347,7 @@ std::cerr << "custom fork() result could not be parsed?"<< std::endl; return pkgPackageManager::Failed; } - std::cerr << "got pid: " << child_id << std::endl; } else { - //std::cerr << "using build-in fork()" << std::endl; child_id = fork(); } @@ -374,7 +365,6 @@ PyObject *v = PyObject_GetAttrString(callbackInst, "writefd"); if(v) { int fd = PyObject_AsFileDescriptor(v); - std::cout << "got fd: " << fd << std::endl; APT::Progress::PackageManagerProgressFd progress(fd); res = pm->DoInstall(&progress); @@ -382,7 +372,6 @@ APT::Progress::PackageManagerProgressFd progress(-1); res = pm->DoInstall(&progress); } - //std::cout << "res: " << res << std::endl; _exit(res); } @@ -397,7 +386,6 @@ method = PyObject_GetAttrString(callbackInst, "waitChild"); else method = PyObject_GetAttrString(callbackInst, "wait_child"); - //std::cerr << "custom waitChild found" << std::endl; PyObject *result = PyObject_CallObject(method, NULL); if (result == NULL) { std::cerr << "waitChild method invalid" << std::endl; @@ -411,9 +399,7 @@ return pkgPackageManager::Failed; } PyCbObj_BEGIN_ALLOW_THREADS - //std::cerr << "got child_res: " << res << std::endl; } else { - //std::cerr << "using build-in waitpid()" << std::endl; PyCbObj_BEGIN_ALLOW_THREADS while (waitpid(child_id, &ret, WNOHANG) == 0) { PyCbObj_END_ALLOW_THREADS @@ -422,7 +408,6 @@ } res = (pkgPackageManager::OrderResult) WEXITSTATUS(ret); - //std::cerr << "build-in waitpid() got: " << res << std::endl; } FinishUpdate(); diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_apt_cache.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_apt_cache.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_apt_cache.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_apt_cache.py 2023-12-13 13:35:36.000000000 +0000 @@ -8,8 +8,6 @@ # notice and this notice are preserved. """Unit tests for verifying the correctness of check_dep, etc in apt_pkg.""" -from __future__ import print_function - import glob import logging import os diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_aptsources.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_aptsources.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_aptsources.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_aptsources.py 2023-12-13 13:35:36.000000000 +0000 @@ -648,6 +648,263 @@ self.assertTrue("multiverse" in comps) self.assertTrue("universe" in comps) + def test_enable_component_deb822_multi(self): + apt_pkg.config.set("Dir::Etc::sourcelist", "/dev/null") + + target = ( + apt_pkg.config.find_dir("dir::etc::sourceparts") + "enable_comps.sources" + ) + line = "Types: deb\nURIs: http://archive.ubuntu.com/ubuntu\nSuites: lucid lucid-updates\nComponents: main\n" + with open(target, "w") as target_file: + target_file.write(line) + sources = aptsources.sourceslist.SourcesList(True, self.templates, deb822=True) + distro = aptsources.distro.get_distro(id="Ubuntu") + # make sure we are using the right distro + distro.codename = "lucid" + distro.id = "Ubuntu" + distro.release = "10.04" + # and get the sources + distro.get_sources(sources) + self.assertEqual(len(distro.main_sources), 1) + self.assertEqual(len(sources.list), 1) + # test enable_component + comp = "multiverse" + distro.enable_component(comp) + self.assertEqual(len(sources.list), 2) # split into two + + self.assertEqual( + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu\n" + "Suites: lucid\n" + "Components: main multiverse universe", + str(sources.list[0]), + ) + self.assertEqual( + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu\n" + "Suites: lucid-updates\n" + "Components: main multiverse universe", + str(sources.list[1]), + ) + + comps = set() + for entry in sources: + comps = comps.union(set(entry.comps)) + self.assertTrue("multiverse" in comps) + self.assertTrue("universe" in comps) + + sources.save() + self.assertEqual( + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu\n" + "Suites: lucid lucid-updates\n" + "Components: main multiverse universe", + str(sources.list[0]), + ) + + def test_enable_component_deb822_multi_mixed_origin(self): + apt_pkg.config.set("Dir::Etc::sourcelist", "/dev/null") + + target = ( + apt_pkg.config.find_dir("dir::etc::sourceparts") + "enable_comps.sources" + ) + line = "Types: deb\nURIs: http://archive.ubuntu.com/ubuntu http://example.com/\nSuites: lucid\nComponents: main\n" + with open(target, "w") as target_file: + target_file.write(line) + sources = aptsources.sourceslist.SourcesList(True, self.templates, deb822=True) + distro = aptsources.distro.get_distro(id="Ubuntu") + # make sure we are using the right distro + distro.codename = "lucid" + distro.id = "Ubuntu" + distro.release = "10.04" + # and get the sources + distro.get_sources(sources) + self.assertEqual(len(distro.main_sources), 2) + self.assertEqual(len(sources.list), 1) + # test enable_component + comp = "multiverse" + distro.enable_component(comp) + self.assertEqual(len(sources.list), 2) # split into two + + self.assertEqual( + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu\n" + "Suites: lucid\n" + "Components: main multiverse universe", + str(sources.list[0]), + ) + self.assertEqual( + "Types: deb\n" + "URIs: http://example.com/\n" + "Suites: lucid\n" + "Components: main", + str(sources.list[1]), + ) + + sources.save() + self.assertEqual( + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu\n" + "Suites: lucid\n" + "Components: main multiverse universe", + str(sources.list[0]), + ) + self.assertEqual( + "Types: deb\n" + "URIs: http://example.com/\n" + "Suites: lucid\n" + "Components: main", + str(sources.list[1]), + ) + + def test_enable_component_deb822_multi_mixed_ultimate(self): + apt_pkg.config.set("Dir::Etc::sourcelist", "/dev/null") + + target = ( + apt_pkg.config.find_dir("dir::etc::sourceparts") + "enable_comps.sources" + ) + line = "Types: deb deb-src\nURIs: http://archive.ubuntu.com/ubuntu http://example.com/\nSuites: lucid lucid-updates notalucid\nComponents: main\n" + with open(target, "w") as target_file: + target_file.write(line) + sources = aptsources.sourceslist.SourcesList(True, self.templates, deb822=True) + distro = aptsources.distro.get_distro(id="Ubuntu") + # make sure we are using the right distro + distro.codename = "lucid" + distro.id = "Ubuntu" + distro.release = "10.04" + # and get the sources + distro.get_sources(sources) + self.assertEqual(len(distro.main_sources), 1) + self.assertEqual(len(sources.list), 1) + self.assertEqual(len(sources.exploded_list()), 12) + # test enable_component + comp = "multiverse" + distro.enable_component(comp) + self.assertEqual(len(sources.list), 12) # split into two + + expected = [] + for typ in "deb", "deb-src": + for uri in "http://archive.ubuntu.com/ubuntu", "http://example.com/": + for suite in "lucid", "lucid-updates", "notalucid": + comps = "main multiverse universe" + # unofficial source ends up without enablement + if uri == "http://example.com/" or suite == "notalucid": + comps = "main" + expected.append( + f"Types: {typ}\n" + f"URIs: {uri}\n" + f"Suites: {suite}\n" + f"Components: {comps}" + ) + + self.maxDiff = None + self.assertEqual(expected, list(map(str, sources.list))) + sources.save() + + expected = [ + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu\n" + "Suites: lucid lucid-updates\n" + "Components: main multiverse universe", + # unofficial suite + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu http://example.com/\n" + "Suites: notalucid\n" + "Components: main", + # unofficial mirror, FIXME: We'd rather merge the notalucid into here + "Types: deb deb-src\n" + "URIs: http://example.com/\n" + "Suites: lucid lucid-updates\n" + "Components: main", + ] + self.maxDiff = None + self.assertEqual(expected, list(map(str, sources.list))) + + def test_deb822_explode(self): + apt_pkg.config.set("Dir::Etc::sourcelist", "/dev/null") + target = ( + apt_pkg.config.find_dir("dir::etc::sourceparts") + "enable_comps.sources" + ) + line = "Types: deb\nURIs: http://archive.ubuntu.com/ubuntu\nSuites: lucid\nComponents: main\n" + with open(target, "w") as target_file: + target_file.write(line) + sources = aptsources.sourceslist.SourcesList(True, self.templates, deb822=True) + + self.assertEqual(len(sources.list), 1) + self.assertEqual(len(sources.exploded_list()), 1) + self.assertIsInstance( + sources.exploded_list()[0], aptsources.sourceslist.Deb822SourceEntry + ) + + sources.list[0].suites += ["fakesuite"] + self.assertEqual(len(sources.list), 1) + self.assertEqual(len(sources.exploded_list()), 2) + self.assertIsInstance( + sources.exploded_list()[0], aptsources.sourceslist.ExplodedDeb822SourceEntry + ) + self.assertIsInstance( + sources.exploded_list()[1], aptsources.sourceslist.ExplodedDeb822SourceEntry + ) + self.assertEqual(sources.list[0].suites, ["lucid", "fakesuite"]) + sources.remove(sources.exploded_list()[1]) + self.assertEqual(len(sources.list), 1) + self.assertEqual(len(sources.exploded_list()), 1) + self.assertEqual(sources.list[0].suites, ["lucid"]) + + sources.list[0].types += ["deb-src"] + self.assertEqual(len(sources.list), 1) + self.assertEqual(len(sources.exploded_list()), 2) + self.assertIsInstance( + sources.exploded_list()[0], aptsources.sourceslist.ExplodedDeb822SourceEntry + ) + self.assertIsInstance( + sources.exploded_list()[1], aptsources.sourceslist.ExplodedDeb822SourceEntry + ) + self.assertEqual(sources.list[0].types, ["deb", "deb-src"]) + sources.remove(sources.exploded_list()[1]) + self.assertEqual(len(sources.list), 1) + self.assertEqual(len(sources.exploded_list()), 1) + self.assertEqual(sources.list[0].types, ["deb"]) + + sources.list[0].uris += ["http://example.com"] + self.assertEqual(len(sources.list), 1) + self.assertEqual(len(sources.exploded_list()), 2) + self.assertIsInstance( + sources.exploded_list()[0], aptsources.sourceslist.ExplodedDeb822SourceEntry + ) + self.assertIsInstance( + sources.exploded_list()[1], aptsources.sourceslist.ExplodedDeb822SourceEntry + ) + self.assertEqual( + sources.list[0].uris, + ["http://archive.ubuntu.com/ubuntu", "http://example.com"], + ) + sources.remove(sources.exploded_list()[1]) + self.assertEqual(len(sources.list), 1) + self.assertEqual(len(sources.exploded_list()), 1) + self.assertEqual(sources.list[0].uris, ["http://archive.ubuntu.com/ubuntu"]) + + # test setting attributes + sources.list[0].uris += ["http://example.com"] + with self.assertRaises(AttributeError): + sources.exploded_list()[0].types = ["does not work"] + with self.assertRaises(AttributeError): + sources.exploded_list()[0].uris = ["does not work"] + with self.assertRaises(AttributeError): + sources.exploded_list()[0].suites = ["does not work"] + with self.assertRaises(AttributeError): + sources.exploded_list()[0].doesnotexist = ["does not work"] + + # test overriding + sources.exploded_list()[0].type = "faketype" + self.assertEqual(sources.list[0].type, "faketype") + self.assertEqual(sources.list[1].type, "deb") + sources.exploded_list()[0].type = "deb" + self.assertEqual(sources.list[0].type, "deb") + self.assertEqual(sources.list[1].type, "deb") + sources.save() + self.assertEqual(len(sources.list), 1) + def testDistribution_short(self): """aptsources: Test distribution detection.""" apt_pkg.config.set( @@ -803,6 +1060,489 @@ self.assertEqual(count, len(sources.list)) self.assertEqual(sourceslist_wslash, sourceslist_woslash) + def test_deb822_distro_enable_disable_component(self): + """Test enabling and disabling a component in the distro sources. + + This ensures reasonable behavior when enabling and then disabling a component""" + with tempfile.NamedTemporaryFile("w", suffix=".sources") as file: + file.write( + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + ) + file.flush() + + apt_pkg.config.set("Dir::Etc::sourcelist", file.name) + sources = aptsources.sourceslist.SourcesList( + True, self.templates, deb822=True + ) + distro = aptsources.distro.get_distro( + id="Ubuntu", + codename="noble", + ) + + self.assertEqual(len(sources.list), 2) + distro.get_sources(sources) + distro.enable_component("multiverse") + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble noble-updates\n" + "Components: main universe multiverse\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe multiverse\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + # Disable it again + # FIXME: The child entries will no longer be valid at this point, so we have to call + # get_sources(), but it's not clear whether this should be considered a bug - + # there may have been other changes rendering entries no longer valid that distro + # is holding on to. + distro.get_sources(sources) + distro.disable_component("multiverse") + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + # Disable universe as well + distro.get_sources(sources) + distro.disable_component("universe") + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble noble-updates\n" + "Components: main\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + def test_deb822_distro_enable_disable_component_mixed_origin(self): + """Test enabling and disabling a component in the distro sources, with mixed origin + + Here we ensure that we still get idempotent behavior of disable after enable even if the + entry we were modifying also had a non-official repository in it.""" + with tempfile.NamedTemporaryFile("w", suffix=".sources") as file: + file.write( + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/ http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + ) + file.flush() + + apt_pkg.config.set("Dir::Etc::sourcelist", file.name) + sources = aptsources.sourceslist.SourcesList( + True, self.templates, deb822=True + ) + distro = aptsources.distro.get_distro( + id="Ubuntu", + codename="noble", + ) + + self.assertEqual(len(sources.list), 2) + distro.get_sources(sources) + distro.enable_component("multiverse") + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble noble-updates\n" + "Components: main universe multiverse\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe multiverse\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + # Disable it again + # FIXME: The child entries will no longer be valid at this point, so we have to call + # get_sources(), but it's not clear whether this should be considered a bug - + # there may have been other changes rendering entries no longer valid that distro + # is holding on to. + distro.get_sources(sources) + distro.disable_component("multiverse") + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/ http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + # Disable universe too. The behaviour here is interesting: Distro only disables + # universe for the official source, so we end up with the non-official source split out. + distro.get_sources(sources) + distro.disable_component("universe") + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble noble-updates\n" + "Components: main\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# main archive\n" # note it keeps the comment on the split out child + "Types: deb deb-src\n" + "URIs: http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + def test_deb822_distro_enable_disable_child_source_mixed_origins(self): + """Test enabling and disabling a child source (proposed) in the distro sources, with mixed origin + + Here we ensure that we still get idempotent behavior of disable after enable even if the + entry we were modifying also had a non-official repository in it.""" + with tempfile.NamedTemporaryFile("w", suffix=".sources") as file: + file.write( + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/ http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + ) + file.flush() + + apt_pkg.config.set("Dir::Etc::sourcelist", file.name) + sources = aptsources.sourceslist.SourcesList( + True, self.templates, deb822=True + ) + distro = aptsources.distro.get_distro( + id="Ubuntu", + codename="noble", + ) + + self.assertEqual(len(sources.list), 2) + distro.get_sources(sources) + distro.get_source_code = True + distro.add_source(dist="noble-proposed") + + # FIXME: Component ordering is not stable right now + for entry in sources.list: + entry.comps = sorted(entry.comps) + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + + # FIXME: In an optimal world it would look like this + self.assertNotEqual( + readonly.read(), + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble noble-updates noble-proposed\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + with open(file.name, "r") as readonly: + self.maxDiff = None + # Sadly our merge algorithm does not always produce optimal merges, because it merges the unofficial entry first + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/ http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble-proposed\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + # Disable it again + # FIXME: The child entries will no longer be valid at this point, so we have to call + # get_sources(), but it's not clear whether this should be considered a bug - + # there may have been other changes rendering entries no longer valid that distro + # is holding on to. + distro.get_sources(sources) + for child in distro.child_sources + distro.source_code_sources: + if child.dist.endswith("proposed"): + sources.remove(child) + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb deb-src\n" + "URIs: http://archive.ubuntu.com/ubuntu/ http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb deb-src\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + def test_deb822_distro_enable_disable_child_source_mixed_origins_no_source_code( + self, + ): + """Test enabling and disabling a child source (proposed) in the distro sources, with mixed origin + + Here we ensure that we still get idempotent behavior of disable after enable even if the + entry we were modifying also had a non-official repository in it.""" + with tempfile.NamedTemporaryFile("w", suffix=".sources") as file: + file.write( + "# main archive\n" + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu/ http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + ) + file.flush() + + apt_pkg.config.set("Dir::Etc::sourcelist", file.name) + sources = aptsources.sourceslist.SourcesList( + True, self.templates, deb822=True + ) + distro = aptsources.distro.get_distro( + id="Ubuntu", + codename="noble", + ) + + self.assertEqual(len(sources.list), 2) + distro.get_sources(sources) + distro.add_source(dist="noble-proposed") + + # FIXME: Component ordering is not stable right now + for entry in sources.list: + entry.comps = sorted(entry.comps) + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + + # FIXME: In an optimal world it would look like this + self.assertNotEqual( + readonly.read(), + "# main archive\n" + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble noble-updates noble-proposed\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# main archive\n" + "Types: deb\n" + "URIs: http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + with open(file.name, "r") as readonly: + self.maxDiff = None + # Sadly our merge algorithm does not always produce optimal merges, because it merges the unofficial entry first + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu/ http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu/\n" + "Suites: noble-proposed\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + + # Disable it again + # FIXME: The child entries will no longer be valid at this point, so we have to call + # get_sources(), but it's not clear whether this should be considered a bug - + # there may have been other changes rendering entries no longer valid that distro + # is holding on to. + distro.get_sources(sources) + for child in distro.child_sources + distro.source_code_sources: + if child.dist.endswith("proposed"): + sources.remove(child) + sources.save() + + with open(file.name, "r") as readonly: + self.maxDiff = None + self.assertEqual( + readonly.read(), + "# main archive\n" + "Types: deb\n" + "URIs: http://archive.ubuntu.com/ubuntu/ http://unofficial.example.com/\n" + "Suites: noble noble-updates\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n" + "\n" + "# security\n" + "Types: deb\n" + "URIs: http://security.ubuntu.com/ubuntu/\n" + "Suites: noble-security\n" + "Components: main universe\n" + "Signed-By: /usr/share/keyrings/ubuntu-archive-keyrings.gpg\n", + ) + if __name__ == "__main__": os.chdir(os.path.dirname(__file__)) diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_aptsources_deb822.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_aptsources_deb822.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_aptsources_deb822.py 1970-01-01 00:00:00.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_aptsources_deb822.py 2023-12-13 13:35:36.000000000 +0000 @@ -0,0 +1,64 @@ +#!/usr/bin/python3 + +import io +import unittest +import os +import copy +import tempfile + +import apt_pkg +import aptsources.sourceslist +import aptsources.distro + +import testcommon + + +class TestAptSources(testcommon.TestCase): + def setUp(self): + testcommon.TestCase.setUp(self) + self.tempfile = tempfile.NamedTemporaryFile(suffix=".sources") + apt_pkg.config.set("Dir::Etc::sourcelist", self.tempfile.name) + apt_pkg.config.set("Dir::Etc::sourceparts", "/dev/null") + + def tearDown(self): + self.tempfile.close() + + def testEmptyDeb822(self): + """aptsources: Test sources.list parsing.""" + sources = aptsources.sourceslist.SourcesList(True) + self.assertListEqual(sources.list, []) + + def testDeb822SectionRecognizedWithoutEndLine(self): + """aptsources: Test sources.list parsing.""" + section = aptsources._deb822.Section("key: value\notherkey: othervalue") + + # Writing it back out gives us an extra newline at the end + self.assertEqual(section["key"], "value") + self.assertEqual(section["otherkey"], "othervalue") + self.assertEqual(str(section), "key: value\notherkey: othervalue\n") + + file = aptsources._deb822.File(io.StringIO("key: value\notherkey: othervalue")) + self.assertEqual(len(file.sections), 1) + + section = next(iter(file)) + self.assertEqual(section["key"], "value") + self.assertEqual(section["otherkey"], "othervalue") + self.assertEqual(str(section), "key: value\notherkey: othervalue\n") + + def testDeb822MultipleLinesSeparator(self): + """aptsources: Test sources.list parsing.""" + for separator in "\n\n\n\n", "\n\n\n", "\n\n": + with self.subTest(f"{len(separator)} separators"): + file = aptsources._deb822.File( + io.StringIO("key: value" + separator + "otherkey: othervalue\n") + ) + self.assertEqual(len(file.sections), 2) + + self.assertEqual(file.sections[0]["key"], "value") + self.assertEqual(file.sections[1]["otherkey"], "othervalue") + self.assertEqual(str(file), "key: value\n\notherkey: othervalue\n") + + +if __name__ == "__main__": + os.chdir(os.path.dirname(__file__)) + unittest.main() diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_auth.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_auth.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_auth.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_auth.py 2023-12-13 13:35:36.000000000 +0000 @@ -1,7 +1,5 @@ #!/usr/bin/python3 -from __future__ import print_function - import contextlib import errno import itertools diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_tagfile.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_tagfile.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/tests/test_tagfile.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/tests/test_tagfile.py 2023-12-13 13:35:36.000000000 +0000 @@ -10,8 +10,6 @@ # notice and this notice are preserved. """Unit tests for verifying the correctness of apt_pkg.TagFile""" -from __future__ import print_function, unicode_literals - import io import glob import os @@ -93,19 +91,10 @@ with io.open(packages, "w", encoding="UTF-8") as packages_file: print("Maintainer: %s" % value, file=packages_file) print("", file=packages_file) - if sys.version < "3": - # In Python 2, test the traditional file interface. - with open(packages) as packages_file: - tagfile = apt_pkg.TagFile(packages_file) - tagfile.step() - self.assertEqual(value.encode("UTF-8"), tagfile.section["Maintainer"]) with io.open(packages, encoding="UTF-8") as packages_file: tagfile = apt_pkg.TagFile(packages_file) tagfile.step() - if sys.version < "3": - self.assertEqual(value.encode("UTF-8"), tagfile.section["Maintainer"]) - else: - self.assertEqual(value, tagfile.section["Maintainer"]) + self.assertEqual(value, tagfile.section["Maintainer"]) def test_latin1(self): value = "Tést Persön " @@ -113,25 +102,14 @@ with io.open(packages, "w", encoding="ISO-8859-1") as packages_file: print("Maintainer: %s" % value, file=packages_file) print("", file=packages_file) - if sys.version < "3": - # In Python 2, test the traditional file interface. - with open(packages) as packages_file: - tagfile = apt_pkg.TagFile(packages_file) - tagfile.step() - self.assertEqual( - value.encode("ISO-8859-1"), tagfile.section["Maintainer"] - ) with io.open(packages) as packages_file: tagfile = apt_pkg.TagFile(packages_file, bytes=True) tagfile.step() self.assertEqual(value.encode("ISO-8859-1"), tagfile.section["Maintainer"]) - if sys.version >= "3": - # In Python 3, TagFile can pick up the encoding of the file - # object. - with io.open(packages, encoding="ISO-8859-1") as packages_file: - tagfile = apt_pkg.TagFile(packages_file) - tagfile.step() - self.assertEqual(value, tagfile.section["Maintainer"]) + with io.open(packages, encoding="ISO-8859-1") as packages_file: + tagfile = apt_pkg.TagFile(packages_file) + tagfile.step() + self.assertEqual(value, tagfile.section["Maintainer"]) def test_mixed(self): value = "Tést Persön " @@ -142,16 +120,6 @@ with io.open(packages, "a", encoding="ISO-8859-1") as packages_file: print("Maintainer: %s" % value, file=packages_file) print("", file=packages_file) - if sys.version < "3": - # In Python 2, test the traditional file interface. - with open(packages) as packages_file: - tagfile = apt_pkg.TagFile(packages_file) - tagfile.step() - self.assertEqual(value.encode("UTF-8"), tagfile.section["Maintainer"]) - tagfile.step() - self.assertEqual( - value.encode("ISO-8859-1"), tagfile.section["Maintainer"] - ) with io.open(packages) as packages_file: tagfile = apt_pkg.TagFile(packages_file, bytes=True) tagfile.step() diff -Nru python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/utils/get_debian_mirrors.py python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/utils/get_debian_mirrors.py --- python-apt-2.7.0+git20231122.1269~ubuntu22.04.1/utils/get_debian_mirrors.py 2023-11-22 19:32:25.000000000 +0000 +++ python-apt-2.7.0+git20231213.1276~ubuntu22.04.1/utils/get_debian_mirrors.py 2023-12-13 13:35:36.000000000 +0000 @@ -17,7 +17,6 @@ # 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 print_function import collections import sys import urllib.request