Skip to content

Commit 22e87b7

Browse files
Add type arguments to UserList subclasses (#299)
Add type arguments to UserList subclasses 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. RELEASE NOTES BEGIN Improve type annotations for UserList subclasses RELEASE NOTES END Reviewed-by: Nikola Forró
2 parents be69fc0 + 32216b8 commit 22e87b7

File tree

9 files changed

+33
-22
lines changed

9 files changed

+33
-22
lines changed

specfile/changelog.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Copyright Contributors to the Packit project.
22
# SPDX-License-Identifier: MIT
33

4-
import collections
54
import copy
65
import datetime
76
import getpass
@@ -19,7 +18,7 @@
1918
from specfile.macros import Macros
2019
from specfile.sections import Section
2120
from specfile.types import SupportsIndex
22-
from specfile.utils import EVR
21+
from specfile.utils import EVR, UserList
2322

2423
WEEKDAYS = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")
2524
MONTHS = (
@@ -199,7 +198,7 @@ def assemble(
199198
return cls(header, content, [""] if append_newline else None)
200199

201200

202-
class Changelog(collections.UserList):
201+
class Changelog(UserList[ChangelogEntry]):
203202
"""
204203
Class that represents a changelog.
205204
@@ -326,7 +325,7 @@ def parse(cls, section: Section) -> "Changelog":
326325
Constructed instance of `Changelog` class.
327326
"""
328327

329-
def extract_following_lines(content):
328+
def extract_following_lines(content: List[str]) -> List[str]:
330329
following_lines: List[str] = []
331330
while content and not content[-1].strip():
332331
following_lines.insert(0, content.pop())

specfile/conditions.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,10 @@ def process_conditions(
7979
List of tuples in the form of (line, validity).
8080
"""
8181
excluded_lines = []
82-
for md in macro_definitions or []:
83-
position = md.get_position(macro_definitions)
84-
excluded_lines.append(range(position, position + len(md.body.splitlines())))
82+
if macro_definitions:
83+
for md in macro_definitions:
84+
position = md.get_position(macro_definitions)
85+
excluded_lines.append(range(position, position + len(md.body.splitlines())))
8586
condition_regex = re.compile(
8687
r"""
8788
^

specfile/macro_definitions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Copyright Contributors to the Packit project.
22
# SPDX-License-Identifier: MIT
33

4-
import collections
54
import copy
65
import re
76
from enum import Enum, auto
@@ -10,6 +9,7 @@
109
from specfile.conditions import process_conditions
1110
from specfile.formatter import formatted
1211
from specfile.types import SupportsIndex
12+
from specfile.utils import UserList
1313

1414
if TYPE_CHECKING:
1515
from specfile.specfile import Specfile
@@ -114,7 +114,7 @@ def get_raw_data(self) -> List[str]:
114114
return result
115115

116116

117-
class MacroDefinitions(collections.UserList):
117+
class MacroDefinitions(UserList[MacroDefinition]):
118118
"""
119119
Class that represents all macro definitions.
120120
@@ -162,7 +162,7 @@ def __getattr__(self, name: str) -> MacroDefinition:
162162
except ValueError:
163163
raise AttributeError(name)
164164

165-
def __setattr__(self, name: str, value: Union[MacroDefinition, List[str]]) -> None:
165+
def __setattr__(self, name: str, value: Union[MacroDefinition, str]) -> None:
166166
if name not in self:
167167
return super().__setattr__(name, value)
168168
try:

specfile/prep.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from specfile.options import Options
1212
from specfile.sections import Section
1313
from specfile.types import SupportsIndex
14-
from specfile.utils import split_conditional_macro_expansion
14+
from specfile.utils import UserList, split_conditional_macro_expansion
1515

1616

1717
def valid_prep_macro(name: str) -> bool:
@@ -149,7 +149,7 @@ class AutopatchMacro(PrepMacro):
149149
DEFAULTS: Dict[str, Union[bool, int, str]] = {}
150150

151151

152-
class PrepMacros(collections.UserList):
152+
class PrepMacros(UserList[PrepMacro]):
153153
"""
154154
Class that represents a list of %prep macros.
155155
@@ -185,7 +185,7 @@ def __contains__(self, item: object) -> bool:
185185
if isinstance(item, type):
186186
return any(isinstance(m, item) for m in self.data)
187187
return any(
188-
m.name.startswith(item) if item == "%patch" else m.name == item
188+
m.name.startswith(cast(str, item)) if item == "%patch" else m.name == item
189189
for m in self.data
190190
)
191191

specfile/sections.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from specfile.macros import Macros
1919
from specfile.options import Options
2020
from specfile.types import SupportsIndex
21+
from specfile.utils import UserList
2122

2223
if TYPE_CHECKING:
2324
from specfile.specfile import Specfile
@@ -139,7 +140,7 @@ def get_raw_data(self) -> List[str]:
139140
return str(self).splitlines()
140141

141142

142-
class Sections(collections.UserList):
143+
class Sections(UserList[Section]):
143144
"""
144145
Class that represents all spec file sections, hence the entire spec file.
145146

specfile/sourcelist.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Copyright Contributors to the Packit project.
22
# SPDX-License-Identifier: MIT
33

4-
import collections
54
import copy
65
from typing import TYPE_CHECKING, Any, Dict, List, Optional, overload
76

@@ -12,6 +11,7 @@
1211
from specfile.sections import Section
1312
from specfile.tags import Comments
1413
from specfile.types import SupportsIndex
14+
from specfile.utils import UserList
1515

1616
if TYPE_CHECKING:
1717
from specfile.specfile import Specfile
@@ -81,7 +81,7 @@ def expanded_location(self) -> str:
8181
return Macros.expand(self.location)
8282

8383

84-
class Sourcelist(collections.UserList):
84+
class Sourcelist(UserList[SourcelistEntry]):
8585
"""
8686
Class that represents entries in a %sourcelist/%patchlist section.
8787

specfile/sources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ def insert(self, i: int, location: str) -> None:
539539
valid = self._get_tag_validity(cast(TagSource, source))
540540
container.insert(
541541
index,
542-
Tag(
542+
Tag( # type: ignore[arg-type]
543543
name,
544544
location,
545545
separator,

specfile/tags.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Copyright Contributors to the Packit project.
22
# SPDX-License-Identifier: MIT
33

4-
import collections
54
import copy
65
import itertools
76
import re
@@ -24,7 +23,7 @@
2423
from specfile.macros import Macros
2524
from specfile.sections import Section
2625
from specfile.types import SupportsIndex
27-
from specfile.utils import split_conditional_macro_expansion
26+
from specfile.utils import UserList, split_conditional_macro_expansion
2827

2928
if TYPE_CHECKING:
3029
from specfile.specfile import Specfile
@@ -66,7 +65,7 @@ def __repr__(self) -> str:
6665
return f"Comment({self.text!r}, {self.prefix!r})"
6766

6867

69-
class Comments(collections.UserList):
68+
class Comments(UserList[Comment]):
7069
"""
7170
Class that represents comments associated with a tag, that is consecutive comment lines
7271
located directly above a tag definition.
@@ -312,7 +311,7 @@ def get_position(self, container: "Tags") -> int:
312311
) + len(self.comments.get_raw_data())
313312

314313

315-
class Tags(collections.UserList):
314+
class Tags(UserList[Tag]):
316315
"""
317316
Class that represents all tags in a certain %package section.
318317

specfile/utils.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
import collections
55
import re
6-
from typing import Tuple
6+
import sys
7+
from typing import TYPE_CHECKING, Tuple
78

89
from specfile.constants import ARCH_NAMES
910
from specfile.exceptions import SpecfileException, UnterminatedMacroException
@@ -160,3 +161,13 @@ def split_conditional_macro_expansion(value: str) -> Tuple[str, str, str]:
160161
if not isinstance(node, ConditionalMacroExpansion):
161162
return value, "", ""
162163
return "".join(str(n) for n in node.body), f"%{{{node.prefix}{node.name}:", "}"
164+
165+
166+
# Python 3.6-3.8 do not allow creating a generic UserList at runtime.
167+
# This hack allows type checkers to determine the UserList dunder method return
168+
# types while still working at runtime.
169+
if TYPE_CHECKING or sys.version_info >= (3, 9):
170+
UserList = collections.UserList
171+
else:
172+
# UserList[...] always returns a UserList
173+
UserList = collections.defaultdict(lambda: collections.UserList)

0 commit comments

Comments
 (0)