Skip to content

Commit 7e54c5c

Browse files
authored
Fix all type hints, add missing type hints (#33)
* add mypy.ini and launch.json * cleanup the modify_items hook registration * stub the OrderedDict type which is not available as type hint in Python 3.6
1 parent 88685d8 commit 7e54c5c

File tree

7 files changed

+171
-101
lines changed

7 files changed

+171
-101
lines changed

.vscode/launch.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
// Use IntelliSense to learn about possible attributes.
3+
// Hover to view descriptions of existing attributes.
4+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5+
"version": "0.2.0",
6+
"configurations": [
7+
{
8+
"name": "Test plugin",
9+
"type": "python",
10+
"request": "launch",
11+
"module": "pytest",
12+
"args": ["-v"]
13+
}
14+
]
15+
}

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ Adam Talsma <[email protected]>
55
Brian Maissy <[email protected]>
66
Jonas Zinn <[email protected]>
77
Andrew Gilbert <[email protected]>
8+
Bartosz Peszek <[email protected]>

docs/source/conf.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# -*- coding: utf-8 -*-
2-
#
2+
from typing import List, Dict
3+
34
# pytest-order documentation build configuration file, created by
45
# sphinx-quickstart on Mon Mar 17 18:20:44 2014.
56
#
@@ -75,7 +76,7 @@
7576

7677
# List of patterns, relative to source directory, that match files and
7778
# directories to ignore when looking for source files.
78-
exclude_patterns = []
79+
exclude_patterns: List[str] = []
7980

8081
# The reST default role (used for this markup: `text`) to use for all
8182
# documents.
@@ -135,7 +136,7 @@
135136
# Add any paths that contain custom static files (such as style sheets) here,
136137
# relative to this directory. They are copied after the builtin static files,
137138
# so a file named "default.css" will overwrite the builtin "default.css".
138-
html_static_path = []
139+
html_static_path: List[str] = []
139140

140141
# Add any extra paths that contain custom files (such as robots.txt or
141142
# .htaccess) here, relative to this directory. These files are copied
@@ -188,7 +189,7 @@
188189

189190
# -- Options for LaTeX output ---------------------------------------------
190191

191-
latex_elements = {
192+
latex_elements: Dict[str, str] = {
192193
# The paper size ("letterpaper" or "a4paper").
193194
# "papersize": "letterpaper",
194195

mypy.ini

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[mypy]
2+
; Ensures correct typing information inside non-typed functions
3+
check_untyped_defs = True
4+
; Strict checking for None
5+
no_implicit_optional = True
6+
; Disallow none and partial generics
7+
disallow_any_generics = True
8+
; Point out pointless things
9+
warn_redundant_casts = True
10+
warn_unused_ignores = True
11+
; Misc
12+
show_error_codes = True
13+
14+
; These are needed for VSCode
15+
; Silences errors about missing imports
16+
; (type-checks correctly when they're not missing tho)
17+
ignore_missing_imports = True
18+
; Needed to position the underline correctly
19+
show_column_numbers = True

pytest_order/item.py

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import sys
2-
from typing import List, Optional, Union, Dict, Tuple
2+
from typing import Optional, List, Dict, Tuple, Generic, TypeVar
33

44
from _pytest.python import Function
55

66
from .settings import Scope, Settings
77

88

9+
_ItemType = TypeVar("_ItemType", "Item", "ItemGroup")
10+
11+
912
class Item:
1013
"""Represents a single test item."""
1114

@@ -46,8 +49,8 @@ def __init__(
4649
items: List[Item],
4750
settings: Settings,
4851
scope: Scope,
49-
rel_marks: List["RelativeMark"],
50-
dep_marks: List["RelativeMark"],
52+
rel_marks: List["RelativeMark[Item]"],
53+
dep_marks: List["RelativeMark[Item]"],
5154
) -> None:
5255
self.items = items
5356
self.settings = settings
@@ -63,16 +66,16 @@ def __init__(
6366
self.dep_marks = filter_marks(dep_marks, items)
6467

6568
def collect_markers(self, item: Item) -> None:
66-
if item.order is not None:
67-
self.handle_order_mark(item)
69+
self.handle_order_mark(item)
6870
if item.nr_rel_items or item.order is None:
6971
self.unordered_items.append(item)
7072

7173
def handle_order_mark(self, item: Item) -> None:
72-
if item.order < 0:
73-
self._end_items.setdefault(item.order, []).append(item)
74-
else:
75-
self._start_items.setdefault(item.order, []).append(item)
74+
if item.order is not None:
75+
if item.order < 0:
76+
self._end_items.setdefault(item.order, []).append(item)
77+
else:
78+
self._start_items.setdefault(item.order, []).append(item)
7679

7780
def sort_numbered_items(self) -> List[Item]:
7881
self.start_items = sorted(self._start_items.items())
@@ -114,22 +117,26 @@ def print_unhandled_items(self) -> None:
114117
def number_of_rel_groups(self) -> int:
115118
return len(self.rel_marks) + len(self.dep_marks)
116119

117-
def handle_rel_marks(self, sorted_list: List[Item]) -> None:
120+
def handle_rel_marks(
121+
self, sorted_list: List[Item]
122+
) -> None:
118123
self.handle_relative_marks(
119124
self.rel_marks, sorted_list, self.all_rel_marks
120125
)
121126

122-
def handle_dep_marks(self, sorted_list: List[Item]) -> None:
127+
def handle_dep_marks(
128+
self, sorted_list: List[Item]
129+
) -> None:
123130
self.handle_relative_marks(
124131
self.dep_marks, sorted_list, self.all_dep_marks
125132
)
126133

127134
@staticmethod
128135
def handle_relative_marks(
129-
marks: List["RelativeMark"],
136+
marks: List["RelativeMark[Item]"],
130137
sorted_list: List[Item],
131-
all_marks: List["RelativeMark"],
132-
):
138+
all_marks: List["RelativeMark[Item]"],
139+
) -> None:
133140
for mark in reversed(marks):
134141
if move_item(mark, sorted_list):
135142
marks.remove(mark)
@@ -138,19 +145,21 @@ def handle_relative_marks(
138145
def group_order(self) -> Optional[int]:
139146
if self.start_items:
140147
return self.start_items[0][0]
141-
if self.end_items:
148+
elif self.end_items:
142149
return self.end_items[-1][0]
150+
return None
143151

144152

145153
class ItemGroup:
146-
"""Holds a group of sorted items with the same group order scope.
154+
"""
155+
Holds a group of sorted items with the same group order scope.
147156
Used for sorting groups similar to Item for sorting items.
148157
"""
149158

150159
def __init__(
151160
self, items: Optional[List[Item]] = None, order: Optional[int] = None
152161
) -> None:
153-
self.items = items or []
162+
self.items: List[Item] = items or []
154163
self.order = order
155164
self.nr_rel_items = 0
156165

@@ -168,25 +177,26 @@ def extend(self, groups: List["ItemGroup"], order: Optional[int]) -> None:
168177
self.order = order
169178

170179

171-
class RelativeMark:
172-
"""Represents a marker for an item or an item group.
180+
class RelativeMark(Generic[_ItemType]):
181+
"""
182+
Represents a marker for an item or an item group.
173183
Holds two related items or groups and their relationship.
174184
"""
175185

176186
def __init__(
177187
self,
178-
item: Union[Item, ItemGroup],
179-
item_to_move: Union[Item, ItemGroup],
188+
item: _ItemType,
189+
item_to_move: _ItemType,
180190
move_after: bool,
181191
) -> None:
182-
self.item: Item = item
183-
self.item_to_move: Item = item_to_move
192+
self.item: _ItemType = item
193+
self.item_to_move: _ItemType = item_to_move
184194
self.move_after: bool = move_after
185195

186196

187197
def filter_marks(
188-
marks: List[RelativeMark], all_items: List[Item]
189-
) -> List[RelativeMark]:
198+
marks: List[RelativeMark[_ItemType]], all_items: List[Item]
199+
) -> List[RelativeMark[_ItemType]]:
190200
result = []
191201
for mark in marks:
192202
if mark.item in all_items and mark.item_to_move in all_items:
@@ -197,7 +207,7 @@ def filter_marks(
197207

198208

199209
def move_item(
200-
mark: RelativeMark, sorted_items: List[Union[Item, ItemGroup]]
210+
mark: RelativeMark[_ItemType], sorted_items: List[_ItemType]
201211
) -> bool:
202212
if (
203213
mark.item not in sorted_items

pytest_order/plugin.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,23 +26,17 @@ def pytest_configure(config: Config) -> None:
2626
"in relation to one another. " + provided_by_pytest_order
2727
)
2828
config.addinivalue_line("markers", config_line)
29-
29+
# We need to dynamically add this `tryfirst` decorator to the plugin:
30+
# only when the CLI option is present should the decorator be added.
31+
# Thus, we manually run the decorator on the class function and
32+
# manually replace it.
3033
if config.getoption("indulgent_ordering"):
31-
# We need to dynamically add this `tryfirst` decorator to the plugin:
32-
# only when the CLI option is present should the decorator be added.
33-
# Thus, we manually run the decorator on the class function and
34-
# manually replace it.
35-
# Python 2.7 didn't allow arbitrary attributes on methods, so we have
36-
# to keep the function as a function and then add it to the class as a
37-
# pseudo method. Since the class is purely for structuring and `self`
38-
# is never referenced, this seems reasonable.
39-
OrderingPlugin.pytest_collection_modifyitems = pytest.hookimpl(
40-
function=modify_items, tryfirst=True
41-
)
34+
wrapper = pytest.hookimpl(tryfirst=True)
4235
else:
43-
OrderingPlugin.pytest_collection_modifyitems = pytest.hookimpl(
44-
function=modify_items, trylast=True
45-
)
36+
wrapper = pytest.hookimpl(trylast=True)
37+
setattr(
38+
OrderingPlugin, "pytest_collection_modifyitems", wrapper(modify_items)
39+
)
4640
config.pluginmanager.register(OrderingPlugin(), "orderingplugin")
4741

4842

@@ -112,7 +106,7 @@ def pytest_addoption(parser: Parser) -> None:
112106

113107
class OrderingPlugin:
114108
"""
115-
Plugin implementation
109+
Plugin implementation.
116110
117111
By putting this in a class, we are able to dynamically register it after
118112
the CLI is parsed.

0 commit comments

Comments
 (0)