Skip to content

Commit

Permalink
Add type arguments to UserList subclasses
Browse files Browse the repository at this point in the history
By having these classes inherit from the generics in the typing module,
type checkers will be able to determine the return types of the dunder
methods inherited from UserList.
  • Loading branch information
gotmax23 authored and nforro committed Nov 5, 2023
1 parent be69fc0 commit 32216b8
Show file tree
Hide file tree
Showing 9 changed files with 33 additions and 22 deletions.
7 changes: 3 additions & 4 deletions specfile/changelog.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

import collections
import copy
import datetime
import getpass
Expand All @@ -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 = (
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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())
Expand Down
7 changes: 4 additions & 3 deletions specfile/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
^
Expand Down
6 changes: 3 additions & 3 deletions specfile/macro_definitions.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions specfile/prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
)

Expand Down
3 changes: 2 additions & 1 deletion specfile/sections.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions specfile/sourcelist.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion specfile/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 3 additions & 4 deletions specfile/tags.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

import collections
import copy
import itertools
import re
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
13 changes: 12 additions & 1 deletion specfile/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

0 comments on commit 32216b8

Please sign in to comment.