diff --git a/specfile/changelog.py b/specfile/changelog.py index 4728ac6..990374d 100644 --- a/specfile/changelog.py +++ b/specfile/changelog.py @@ -1,7 +1,6 @@ # Copyright Contributors to the Packit project. # SPDX-License-Identifier: MIT -import collections import copy import datetime import getpass @@ -19,7 +18,7 @@ from specfile.macros import Macros from specfile.sections import Section from specfile.types import SupportsIndex -from specfile.utils import EVR +from specfile.utils import EVR, UserList WEEKDAYS = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") MONTHS = ( @@ -199,7 +198,7 @@ def assemble( return cls(header, content, [""] if append_newline else None) -class Changelog(collections.UserList): +class Changelog(UserList[ChangelogEntry]): """ Class that represents a changelog. @@ -326,7 +325,7 @@ def parse(cls, section: Section) -> "Changelog": Constructed instance of `Changelog` class. """ - def extract_following_lines(content): + def extract_following_lines(content: List[str]) -> List[str]: following_lines: List[str] = [] while content and not content[-1].strip(): following_lines.insert(0, content.pop()) diff --git a/specfile/conditions.py b/specfile/conditions.py index 3063c5f..2e42da3 100644 --- a/specfile/conditions.py +++ b/specfile/conditions.py @@ -79,9 +79,10 @@ def process_conditions( List of tuples in the form of (line, validity). """ excluded_lines = [] - for md in macro_definitions or []: - position = md.get_position(macro_definitions) - excluded_lines.append(range(position, position + len(md.body.splitlines()))) + if macro_definitions: + for md in macro_definitions: + position = md.get_position(macro_definitions) + excluded_lines.append(range(position, position + len(md.body.splitlines()))) condition_regex = re.compile( r""" ^ diff --git a/specfile/macro_definitions.py b/specfile/macro_definitions.py index 0d70dc5..df3db0f 100644 --- a/specfile/macro_definitions.py +++ b/specfile/macro_definitions.py @@ -1,7 +1,6 @@ # Copyright Contributors to the Packit project. # SPDX-License-Identifier: MIT -import collections import copy import re from enum import Enum, auto @@ -10,6 +9,7 @@ from specfile.conditions import process_conditions from specfile.formatter import formatted from specfile.types import SupportsIndex +from specfile.utils import UserList if TYPE_CHECKING: from specfile.specfile import Specfile @@ -114,7 +114,7 @@ def get_raw_data(self) -> List[str]: return result -class MacroDefinitions(collections.UserList): +class MacroDefinitions(UserList[MacroDefinition]): """ Class that represents all macro definitions. @@ -162,7 +162,7 @@ def __getattr__(self, name: str) -> MacroDefinition: except ValueError: raise AttributeError(name) - def __setattr__(self, name: str, value: Union[MacroDefinition, List[str]]) -> None: + def __setattr__(self, name: str, value: Union[MacroDefinition, str]) -> None: if name not in self: return super().__setattr__(name, value) try: diff --git a/specfile/prep.py b/specfile/prep.py index 0eb30f6..2299aee 100644 --- a/specfile/prep.py +++ b/specfile/prep.py @@ -11,7 +11,7 @@ from specfile.options import Options from specfile.sections import Section from specfile.types import SupportsIndex -from specfile.utils import split_conditional_macro_expansion +from specfile.utils import UserList, split_conditional_macro_expansion def valid_prep_macro(name: str) -> bool: @@ -149,7 +149,7 @@ class AutopatchMacro(PrepMacro): DEFAULTS: Dict[str, Union[bool, int, str]] = {} -class PrepMacros(collections.UserList): +class PrepMacros(UserList[PrepMacro]): """ Class that represents a list of %prep macros. @@ -185,7 +185,7 @@ def __contains__(self, item: object) -> bool: if isinstance(item, type): return any(isinstance(m, item) for m in self.data) return any( - m.name.startswith(item) if item == "%patch" else m.name == item + m.name.startswith(cast(str, item)) if item == "%patch" else m.name == item for m in self.data ) diff --git a/specfile/sections.py b/specfile/sections.py index d8020cc..ab40c21 100644 --- a/specfile/sections.py +++ b/specfile/sections.py @@ -18,6 +18,7 @@ from specfile.macros import Macros from specfile.options import Options from specfile.types import SupportsIndex +from specfile.utils import UserList if TYPE_CHECKING: from specfile.specfile import Specfile @@ -139,7 +140,7 @@ def get_raw_data(self) -> List[str]: return str(self).splitlines() -class Sections(collections.UserList): +class Sections(UserList[Section]): """ Class that represents all spec file sections, hence the entire spec file. diff --git a/specfile/sourcelist.py b/specfile/sourcelist.py index 97b8113..254dbbf 100644 --- a/specfile/sourcelist.py +++ b/specfile/sourcelist.py @@ -1,7 +1,6 @@ # Copyright Contributors to the Packit project. # SPDX-License-Identifier: MIT -import collections import copy from typing import TYPE_CHECKING, Any, Dict, List, Optional, overload @@ -12,6 +11,7 @@ from specfile.sections import Section from specfile.tags import Comments from specfile.types import SupportsIndex +from specfile.utils import UserList if TYPE_CHECKING: from specfile.specfile import Specfile @@ -81,7 +81,7 @@ def expanded_location(self) -> str: return Macros.expand(self.location) -class Sourcelist(collections.UserList): +class Sourcelist(UserList[SourcelistEntry]): """ Class that represents entries in a %sourcelist/%patchlist section. diff --git a/specfile/sources.py b/specfile/sources.py index 34b6675..11b5091 100644 --- a/specfile/sources.py +++ b/specfile/sources.py @@ -539,7 +539,7 @@ def insert(self, i: int, location: str) -> None: valid = self._get_tag_validity(cast(TagSource, source)) container.insert( index, - Tag( + Tag( # type: ignore[arg-type] name, location, separator, diff --git a/specfile/tags.py b/specfile/tags.py index 0b2ba27..5c2f54b 100644 --- a/specfile/tags.py +++ b/specfile/tags.py @@ -1,7 +1,6 @@ # Copyright Contributors to the Packit project. # SPDX-License-Identifier: MIT -import collections import copy import itertools import re @@ -24,7 +23,7 @@ from specfile.macros import Macros from specfile.sections import Section from specfile.types import SupportsIndex -from specfile.utils import split_conditional_macro_expansion +from specfile.utils import UserList, split_conditional_macro_expansion if TYPE_CHECKING: from specfile.specfile import Specfile @@ -66,7 +65,7 @@ def __repr__(self) -> str: return f"Comment({self.text!r}, {self.prefix!r})" -class Comments(collections.UserList): +class Comments(UserList[Comment]): """ Class that represents comments associated with a tag, that is consecutive comment lines located directly above a tag definition. @@ -312,7 +311,7 @@ def get_position(self, container: "Tags") -> int: ) + len(self.comments.get_raw_data()) -class Tags(collections.UserList): +class Tags(UserList[Tag]): """ Class that represents all tags in a certain %package section. diff --git a/specfile/utils.py b/specfile/utils.py index a42e95b..e6ae0db 100644 --- a/specfile/utils.py +++ b/specfile/utils.py @@ -3,7 +3,8 @@ import collections import re -from typing import Tuple +import sys +from typing import TYPE_CHECKING, Tuple from specfile.constants import ARCH_NAMES from specfile.exceptions import SpecfileException, UnterminatedMacroException @@ -160,3 +161,13 @@ def split_conditional_macro_expansion(value: str) -> Tuple[str, str, str]: if not isinstance(node, ConditionalMacroExpansion): return value, "", "" return "".join(str(n) for n in node.body), f"%{{{node.prefix}{node.name}:", "}" + + +# Python 3.6-3.8 do not allow creating a generic UserList at runtime. +# This hack allows type checkers to determine the UserList dunder method return +# types while still working at runtime. +if TYPE_CHECKING or sys.version_info >= (3, 9): + UserList = collections.UserList +else: + # UserList[...] always returns a UserList + UserList = collections.defaultdict(lambda: collections.UserList)