Skip to content

Commit 0ae16c1

Browse files
Initial Type Checking Work
1 parent 924c73a commit 0ae16c1

24 files changed

+215
-100
lines changed

.pre-commit-config.yaml

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ repos:
2323

2424
# Run the Ruff linter.
2525
- repo: https://github.com/astral-sh/ruff-pre-commit
26-
rev: v0.11.2
26+
rev: v0.11.11
2727
hooks:
2828
# Linter
2929
- id: ruff
@@ -33,10 +33,43 @@ repos:
3333
- id: ruff-format
3434
exclude: ^tests/artifacts/
3535

36-
- repo: https://github.com/pre-commit/mirrors-mypy
37-
rev: v1.15.0
36+
# Use a Local Workflow to ensure that `mypy` picks up all the local up-to-date dependencies
37+
- repo: local
3838
hooks:
39-
- id: mypy
39+
- args:
40+
[
41+
"run",
42+
"--",
43+
"--cache-fine-grained",
44+
"--config-file",
45+
"pyproject.toml",
46+
]
47+
description: Static type checker for Python
48+
# https://github.com/pre-commit/mirrors-mypy/blob/main/.pre-commit-hooks.yaml
49+
entry: dmypy
50+
id: mypy
51+
language: system
52+
name: mypy
53+
require_serial: true
54+
types_or: [python, pyi]
55+
56+
# - repo: https://github.com/pre-commit/mirrors-mypy
57+
# rev: v1.15.0
58+
# hooks:
59+
# - id: mypy
60+
# args: [
61+
# "--config-file",
62+
# "pyproject.toml",
63+
# ]
64+
# additional_dependencies:
65+
# - "deepdiff"
66+
# - "hypothesis"
67+
# - "packaging"
68+
# - "platformdirs"
69+
# - "pytest"
70+
# - "tomlkit"
71+
# - "tzlocal"
72+
# - "urwid"
4073

4174
- repo: https://github.com/PyCQA/autoflake
4275
rev: v2.3.1 # check for the latest version

pyproject.toml

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,6 @@ get-supported-configs = "variantlib.commands.plugins.get_supported_configs:get_s
8181
testpaths = ["tests/"]
8282
addopts = "-vvv --cov=variantlib --cov-report=term-missing --no-cov-on-fail"
8383

84-
[tool.mypy]
85-
disallow_untyped_defs = true
86-
no_implicit_optional = true
87-
8884
[[tool.mypy.overrides]]
8985
module = "tests.*"
9086
disallow_untyped_defs = false
@@ -100,3 +96,33 @@ extras = ["test"]
10096
commands = [
10197
["pytest", "-nauto"],
10298
]
99+
100+
# ==== mypy ====
101+
[tool.mypy]
102+
exclude = [
103+
".github",
104+
".ipython",
105+
".hypothesis",
106+
".mypy_cache",
107+
".pytest_cache",
108+
".ruff_cache",
109+
".uv",
110+
".venv",
111+
".workdir",
112+
"~/.cache",
113+
"**/__pycache__/**",
114+
"assets",
115+
"dist",
116+
"tests",
117+
]
118+
check_untyped_defs = true
119+
disable_error_code = "method-assign"
120+
disallow_untyped_defs = true
121+
follow_untyped_imports = true
122+
no_implicit_optional = true
123+
python_version = "3.13"
124+
scripts_are_modules = true
125+
strict = true
126+
warn_unused_ignores = true
127+
warn_redundant_casts = true
128+
warn_unused_configs = true

variantlib/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343

4444
def get_variant_hashes_by_priority(
4545
*,
46-
variants_json: dict | VariantsJson,
46+
variants_json: dict[str, VariantDescription] | VariantsJson,
4747
use_auto_install: bool = True,
4848
isolated: bool = True,
4949
venv_path: str | pathlib.Path | None = None,

variantlib/cache.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@
99
from collections.abc import Callable
1010

1111
T = TypeVar("T")
12+
RT = TypeVar("RT")
1213

1314

14-
class VariantCache(Generic[T]):
15+
class VariantCache(Generic[RT]):
1516
"""This class is not necessary today - can be used for finer cache control later."""
1617

17-
def __init__(self) -> None:
18-
self.cache: T | None = None
18+
cache: RT | None = None
1919

20-
def __call__(self, func: Callable) -> Callable:
21-
def wrapper(*args: Any, **kwargs: dict[str, Any]) -> T:
20+
def __call__(self, func: Callable[[T], RT]) -> Callable[[T], RT]:
21+
def wrapper(*args: Any, **kwargs: dict[str, Any]) -> RT:
2222
if self.cache is None:
2323
self.cache = func(*args, **kwargs)
2424
return self.cache

variantlib/commands/analyze_wheel.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,14 @@ def analyze_wheel(args: list[str]) -> None:
6767
)
6868

6969
with zipfile.ZipFile(input_file, "r") as zip_file:
70-
# original_xml_data = xmltodict.parse(zip_file.open("Data.xml").read())
7170
for name in zip_file.namelist():
7271
if name.endswith(".dist-info/METADATA"):
7372
metadata_str = zip_file.open(name).read().decode("utf-8")
7473
break
74+
else:
75+
raise FileNotFoundError(
76+
f"Impossible to find the `METADATA` file inside `{input_file}`."
77+
)
7578

7679
# Extract the hash value
7780
hash_match = re.search(rf"{METADATA_VARIANT_HASH_HEADER}: (\w+)", metadata_str)

variantlib/commands/config/list_paths.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99
from variantlib.configuration import get_configuration_files
1010

1111
try:
12-
from tzlocal import get_localzone
12+
from tzlocal import get_localzone # pyright: ignore[reportAssignmentType]
1313
except ImportError:
1414

15-
def get_localzone() -> None:
15+
def get_localzone() -> None: # type: ignore[misc]
1616
return None
1717

1818

variantlib/commands/config/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ def main(args: list[str]) -> None:
3333
parser.parse_args(args=args, namespace=namespace)
3434

3535
main_fn = registered_commands[namespace.command].load()
36-
return main_fn(namespace.args)
36+
main_fn(namespace.args)

variantlib/commands/config/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def setup(args: list[str]) -> None:
214214
"property_priorities",
215215
):
216216
# Always use multiline output for readability.
217-
toml_data[key].multiline(multiline=True)
217+
toml_data[key].multiline(multiline=True) # type: ignore[union-attr]
218218

219219
ui.clear()
220220
sys.stderr.write(

variantlib/commands/config/setup_interfaces/urwid_ui.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# URWID doesn't explicitly export symbols at the top level
2+
# Disabling errors to prevent noise.
3+
4+
# mypy: disable-error-code="attr-defined,name-defined"
5+
16
from __future__ import annotations
27

38
import sys
@@ -92,10 +97,11 @@ def input_handler(key: str) -> None:
9297
)
9398
listbox = urwid.ListBox(urwid.SimpleListWalker([text_widget]))
9499
scrollable = urwid.ScrollBar(
95-
listbox, trough_char=urwid.ScrollBar.Symbols.LITE_SHADE
100+
listbox, # pyright: ignore[reportArgumentType]
101+
trough_char=urwid.ScrollBar.Symbols.LITE_SHADE,
96102
)
97-
frame = urwid.Frame(scrollable, footer=footer)
98-
loop = urwid.MainLoop(frame, self.palette, unhandled_input=input_handler)
103+
frame = urwid.Frame(scrollable, footer=footer) # type: ignore[var-annotated]
104+
loop = urwid.MainLoop(frame, self.palette, unhandled_input=input_handler) # type: ignore[arg-type]
99105
try:
100106
loop.run()
101107
except KeyboardInterrupt:
@@ -156,11 +162,11 @@ def input_handler(cls, key: str) -> None:
156162
urwid.Padding(
157163
urwid.AttrMap(urwid.LineBox(listbox), "dialog"),
158164
align=urwid.CENTER,
159-
width=(urwid.RELATIVE, 50),
165+
width=(urwid.RELATIVE, 50), # type: ignore[arg-type]
160166
),
161167
height=height,
162168
)
163-
loop = urwid.MainLoop(frame, self.palette, unhandled_input=State.input_handler)
169+
loop = urwid.MainLoop(frame, self.palette, unhandled_input=State.input_handler) # type: ignore[arg-type]
164170
loop.run()
165171
return State.retval
166172

@@ -178,7 +184,7 @@ def update_key(
178184
]
179185
required_values_set = set(known_values) if known_values_required else set()
180186

181-
class MovableCheckBox(urwid.CheckBox):
187+
class MovableCheckBox(urwid.CheckBox): # type: ignore[misc]
182188
def __init__(self, *args: Any, required: bool, **kwargs: Any):
183189
super().__init__(*args, **kwargs)
184190
self.mcb_required = required
@@ -191,27 +197,21 @@ def keypress(self, size: tuple[int], key: str) -> str | None:
191197
if key in ("f7", "tab"):
192198
old_pos = value_box.focus_position
193199
if old_pos != 0:
194-
item = value_box.body.pop(old_pos)
195-
value_box.body.insert(old_pos - 1, item)
196-
if old_pos != len(value_box.body) - 1:
200+
item = value_box.body.pop(old_pos) # pyright: ignore[reportAttributeAccessIssue]
201+
value_box.body.insert(old_pos - 1, item) # pyright: ignore[reportAttributeAccessIssue]
202+
if old_pos != len(value_box.body) - 1: # type: ignore[arg-type]
197203
value_box.focus_position -= 1
198204
return None
199205

200206
if key in ("f8",):
201207
old_pos = value_box.focus_position
202-
if old_pos != len(value_box.body) - 1:
203-
item = value_box.body.pop(old_pos)
204-
value_box.body.insert(old_pos + 1, item)
208+
if old_pos != len(value_box.body) - 1: # type: ignore[arg-type]
209+
item = value_box.body.pop(old_pos) # pyright: ignore[reportAttributeAccessIssue]
210+
value_box.body.insert(old_pos + 1, item) # pyright: ignore[reportAttributeAccessIssue]
205211
value_box.focus_position += 1
206212
return None
207213

208-
return super().keypress(size, key)
209-
210-
def input_handler(key: str) -> None:
211-
if key in ("s", "S"):
212-
value_box.focus_position = len(value_box) - 2
213-
if key in ("esc", "a", "A"):
214-
value_box.focus_position = len(value_box) - 1
214+
return super().keypress(size, key) # type: ignore[no-any-return]
215215

216216
def save_button(_: Any) -> None:
217217
raise urwid.ExitMainLoop
@@ -242,32 +242,39 @@ def abort_button(_: Any) -> None:
242242
]
243243
)
244244
)
245-
listbox = urwid.Frame(
245+
listbox = urwid.Frame( # type: ignore[var-annotated]
246246
urwid.ScrollBar(
247-
urwid.AttrMap(value_box, "list"),
247+
urwid.AttrMap(value_box, "list"), # pyright: ignore[reportArgumentType]
248248
trough_char=urwid.ScrollBar.Symbols.LITE_SHADE,
249249
),
250-
header=urwid.Text(INSTRUCTIONS[key]),
250+
header=urwid.Text(INSTRUCTIONS[key]), # type: ignore[arg-type]
251251
)
252252
frame = urwid.Filler(
253253
urwid.Padding(
254254
urwid.AttrMap(urwid.LineBox(listbox, title=key), "dialog"),
255255
align=urwid.CENTER,
256-
width=(urwid.RELATIVE, 100),
256+
width=(urwid.RELATIVE, 100), # type: ignore[arg-type]
257257
left=2,
258258
right=2,
259259
),
260260
valign=urwid.MIDDLE,
261-
height=(urwid.RELATIVE, 100),
261+
height=(urwid.RELATIVE, 100), # type: ignore[arg-type]
262262
top=2,
263263
bottom=2,
264264
)
265-
loop = urwid.MainLoop(frame, self.palette, unhandled_input=input_handler)
265+
266+
def input_handler(key: str) -> None:
267+
if key in ("s", "S"):
268+
value_box.focus_position = len(value_box) - 2 # pyright: ignore[reportArgumentType]
269+
if key in ("esc", "a", "A"):
270+
value_box.focus_position = len(value_box) - 1 # pyright: ignore[reportArgumentType]
271+
272+
loop = urwid.MainLoop(frame, self.palette, unhandled_input=input_handler) # type: ignore[arg-type]
266273
loop.run()
267274

268275
new_values = [
269276
item.base_widget.label
270-
for item in value_box.body
277+
for item in value_box.body # pyright: ignore[reportGeneralTypeIssues]
271278
if isinstance(item, urwid.AttrMap)
272279
and isinstance(item.base_widget, MovableCheckBox)
273280
and item.base_widget.get_state()

variantlib/commands/index_json_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def append_variant_info_to_json_file(
6363
if path.exists():
6464
data = json.loads(path.read_text())
6565

66-
for key, default_val in [ # type: ignore[var-annotated]
66+
for key, default_val in [
6767
(VARIANTS_JSON_SCHEMA_KEY, VARIANTS_JSON_SCHEMA_URL),
6868
(VARIANTS_JSON_VARIANT_DATA_KEY, {}),
6969
(

variantlib/commands/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ def main() -> None:
4646
parser.parse_args(namespace=namespace)
4747

4848
main_fn = registered_commands[namespace.command].load()
49-
return main_fn(namespace.args)
49+
main_fn(namespace.args)

variantlib/commands/plugins/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,4 @@ def main(args: list[str]) -> None:
3535

3636
with EntryPointPluginLoader() as loader:
3737
main_fn = registered_commands[namespace.command].load()
38-
return main_fn(namespace.args, plugin_loader=loader)
38+
main_fn(namespace.args, plugin_loader=loader)

variantlib/configuration.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from pathlib import Path
99
from typing import TYPE_CHECKING
1010
from typing import Any
11-
from typing import Callable
1211
from typing import TypeVar
1312

1413
import platformdirs
@@ -20,6 +19,8 @@
2019
from variantlib.utils import classproperty
2120

2221
if TYPE_CHECKING:
22+
from collections.abc import Callable
23+
2324
from variantlib.models.variant import VariantFeature
2425
from variantlib.models.variant import VariantProperty
2526

variantlib/dist_metadata.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,8 @@
3030
from variantlib.validators.base import validate_matches_re
3131

3232
if TYPE_CHECKING:
33-
import sys
3433
from email.message import Message
3534

36-
if sys.version_info >= (3, 11):
37-
pass
38-
else:
39-
pass
40-
4135

4236
@dataclass(init=False)
4337
class DistMetadata(VariantMetadata):

variantlib/models/base.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
from dataclasses import dataclass
2-
from typing import TYPE_CHECKING
1+
from __future__ import annotations
32

4-
if TYPE_CHECKING:
5-
from dataclasses import Field
3+
from dataclasses import dataclass
64

75

86
@dataclass(frozen=True)
97
class BaseModel:
108
def __post_init__(self) -> None:
119
# Execute the validator
12-
field_def: Field
1310
for field_name, field_def in self.__dataclass_fields__.items():
1411
value = getattr(self, field_name)
1512
if (validator := field_def.metadata.get("validator", None)) is not None:

0 commit comments

Comments
 (0)