Skip to content

Commit b33d4a0

Browse files
committed
Initial pyright config
1 parent 52d7324 commit b33d4a0

12 files changed

+81
-28
lines changed

pyproject.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ testing = [
5959
# for tools/finalize.py
6060
'jaraco.develop >= 7.21; python_version >= "3.9" and sys_platform != "cygwin"',
6161
"pytest-home >= 0.5",
62-
"mypy==1.10.0", # pin mypy version so a new version doesn't suddenly cause the CI to fail
62+
# pin type-checkers so a new version doesn't suddenly cause the CI to fail
63+
"pyright == 1.1.364",
64+
"mypy == 1.10.0",
6365
# No Python 3.11 dependencies require tomli, but needed for type-checking since we import it directly
6466
"tomli",
6567
# No Python 3.12 dependencies require importlib_metadata, but needed for type-checking since we import it directly

pyrightconfig.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/microsoft/pyright/main/packages/vscode-pyright/schemas/pyrightconfig.schema.json",
3+
"exclude": [
4+
"build",
5+
".tox",
6+
".eggs",
7+
"**/extern", // Vendored
8+
"**/_vendor", // Vendored
9+
"setuptools/_distutils", // Vendored
10+
"**/tests", // Disabled as long as analyzeUnannotatedFunctions=false to reduce log spam
11+
"**/_*", // Disabled as long as analyzeUnannotatedFunctions=false to reduce log spam
12+
],
13+
// Our testing setup doesn't allow passing CLI arguments, so local devs have to set this manually.
14+
// "pythonVersion": "3.8",
15+
// For now we don't mind if mypy's `type: ignore` comments accidentally suppresses pyright issues
16+
"enableTypeIgnoreComments": true,
17+
"typeCheckingMode": "basic",
18+
// Avoid raising issues when importing from "extern" modules, as those are added to path dynamically.
19+
// https://github.com/pypa/setuptools/pull/3979#discussion_r1367968993
20+
"reportMissingImports": "none",
21+
// Too many issues caused by vendoring and dynamic patching, still worth fixing when we can
22+
"reportAttributeAccessIssue": "warning",
23+
// FIXME: A handful of reportOperatorIssue spread throughout the codebase
24+
"reportOperatorIssue": "warning",
25+
// Deferred initialization (initialize_options/finalize_options) causes many "potentially None" issues
26+
// TODO: Fix with type-guards or by changing how it's initialized
27+
"reportCallIssue": "warning",
28+
"reportArgumentType": "warning",
29+
"reportOptionalIterable": "warning",
30+
"reportOptionalMemberAccess": "warning",
31+
"reportGeneralTypeIssues": "warning",
32+
"reportOptionalOperand": "warning",
33+
}

setuptools/command/build.py

+6
Original file line numberDiff line numberDiff line change
@@ -80,12 +80,15 @@ def finalize_options(self):
8080

8181
def initialize_options(self):
8282
"""(Required by the original :class:`setuptools.Command` interface)"""
83+
...
8384

8485
def finalize_options(self):
8586
"""(Required by the original :class:`setuptools.Command` interface)"""
87+
...
8688

8789
def run(self):
8890
"""(Required by the original :class:`setuptools.Command` interface)"""
91+
...
8992

9093
def get_source_files(self) -> List[str]:
9194
"""
@@ -97,6 +100,7 @@ def get_source_files(self) -> List[str]:
97100
with all the files necessary to build the distribution.
98101
All files should be strings relative to the project root directory.
99102
"""
103+
...
100104

101105
def get_outputs(self) -> List[str]:
102106
"""
@@ -110,6 +114,7 @@ def get_outputs(self) -> List[str]:
110114
in ``get_output_mapping()`` plus files that are generated during the build
111115
and don't correspond to any source file already present in the project.
112116
"""
117+
...
113118

114119
def get_output_mapping(self) -> Dict[str, str]:
115120
"""
@@ -120,3 +125,4 @@ def get_output_mapping(self) -> Dict[str, str]:
120125
Destination files should be strings in the form of
121126
``"{build_lib}/destination/file/path"``.
122127
"""
128+
...

setuptools/command/build_ext.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -151,20 +151,18 @@ def _get_output_mapping(self) -> Iterator[Tuple[str, str]]:
151151
yield (output_cache, inplace_cache)
152152

153153
def get_ext_filename(self, fullname):
154-
so_ext = os.getenv('SETUPTOOLS_EXT_SUFFIX')
154+
so_ext = os.getenv('SETUPTOOLS_EXT_SUFFIX', '')
155155
if so_ext:
156156
filename = os.path.join(*fullname.split('.')) + so_ext
157157
else:
158158
filename = _build_ext.get_ext_filename(self, fullname)
159-
so_ext = get_config_var('EXT_SUFFIX')
159+
so_ext = str(get_config_var('EXT_SUFFIX'))
160160

161161
if fullname in self.ext_map:
162162
ext = self.ext_map[fullname]
163-
use_abi3 = ext.py_limited_api and get_abi3_suffix()
164-
if use_abi3:
165-
filename = filename[: -len(so_ext)]
166-
so_ext = get_abi3_suffix()
167-
filename = filename + so_ext
163+
abi3_suffix = get_abi3_suffix()
164+
if ext.py_limited_api and abi3_suffix: # Use abi3
165+
filename = filename[: -len(so_ext)] + abi3_suffix
168166
if isinstance(ext, Library):
169167
fn, ext = os.path.splitext(filename)
170168
return self.shlib_compiler.library_filename(fn, libtype)

setuptools/command/easy_install.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1791,7 +1791,7 @@ def auto_chmod(func, arg, exc):
17911791
return func(arg)
17921792
et, ev, _ = sys.exc_info()
17931793
# TODO: This code doesn't make sense. What is it trying to do?
1794-
raise (ev[0], ev[1] + (" %s %s" % (func, arg)))
1794+
raise (ev[0], ev[1] + (" %s %s" % (func, arg))) # pyright: ignore[reportOptionalSubscript, reportIndexIssue]
17951795

17961796

17971797
def update_dist_caches(dist_path, fix_zipimporter_caches):
@@ -2017,7 +2017,7 @@ def is_python_script(script_text, filename):
20172017

20182018

20192019
try:
2020-
from os import chmod as _chmod
2020+
from os import chmod as _chmod # pyright: ignore[reportAssignmentType] # Loosing type-safety w/ pyright, but that's ok
20212021
except ImportError:
20222022
# Jython compatibility
20232023
def _chmod(*args: object, **kwargs: object) -> None: # type: ignore[misc] # Mypy re-uses the imported definition anyway

setuptools/command/editable_wheel.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
from .install_scripts import install_scripts as install_scripts_cls
6060

6161
if TYPE_CHECKING:
62+
from typing_extensions import Self
6263
from .._vendor.wheel.wheelfile import WheelFile
6364

6465
_P = TypeVar("_P", bound=StrPath)
@@ -400,7 +401,7 @@ def __call__(
400401
self, wheel: "WheelFile", files: List[str], mapping: Dict[str, str]
401402
): ...
402403

403-
def __enter__(self): ...
404+
def __enter__(self) -> "Self": ...
404405

405406
def __exit__(self, _exc_type, _exc_value, _traceback): ...
406407

setuptools/compat/py311.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ def shutil_rmtree(path, ignore_errors=False, onexc=None):
77
return shutil.rmtree(path, ignore_errors, onexc=onexc)
88

99
def _handler(fn, path, excinfo):
10-
return onexc(fn, path, excinfo[1])
10+
return onexc(fn, path, excinfo[1]) if onexc else None
1111

1212
return shutil.rmtree(path, ignore_errors, onerror=_handler)

setuptools/config/pyprojecttoml.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,10 @@ def _obtain(self, dist: "Distribution", field: str, package_dir: Mapping[str, st
301301
def _obtain_version(self, dist: "Distribution", package_dir: Mapping[str, str]):
302302
# Since plugins can set version, let's silently skip if it cannot be obtained
303303
if "version" in self.dynamic and "version" in self.dynamic_cfg:
304-
return _expand.version(self._obtain(dist, "version", package_dir))
304+
return _expand.version(
305+
# We already do an early check for the presence of "version"
306+
self._obtain(dist, "version", package_dir) # pyright: ignore[reportArgumentType]
307+
)
305308
return None
306309

307310
def _obtain_readme(self, dist: "Distribution") -> Optional[Dict[str, str]]:
@@ -311,9 +314,10 @@ def _obtain_readme(self, dist: "Distribution") -> Optional[Dict[str, str]]:
311314
dynamic_cfg = self.dynamic_cfg
312315
if "readme" in dynamic_cfg:
313316
return {
317+
# We already do an early check for the presence of "readme"
314318
"text": self._obtain(dist, "readme", {}),
315319
"content-type": dynamic_cfg["readme"].get("content-type", "text/x-rst"),
316-
}
320+
} # pyright: ignore[reportReturnType]
317321

318322
self._ensure_previously_set(dist, "readme")
319323
return None

setuptools/monkey.py

+19-12
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import sys
99
import types
1010
from importlib import import_module
11-
from typing import List, TypeVar
11+
from typing import List, Optional, Type, TypeVar, Union, cast, overload
1212

1313
import distutils.filelist
1414

1515

1616
_T = TypeVar("_T")
17+
_UnpatchT = TypeVar("_UnpatchT", type, types.FunctionType)
18+
1719

1820
__all__: List[str] = []
1921
"""
@@ -36,25 +38,30 @@ def _get_mro(cls):
3638
return inspect.getmro(cls)
3739

3840

39-
def get_unpatched(item: _T) -> _T:
40-
lookup = (
41-
get_unpatched_class
42-
if isinstance(item, type)
43-
else get_unpatched_function
44-
if isinstance(item, types.FunctionType)
45-
else lambda item: None
46-
)
47-
return lookup(item)
41+
@overload
42+
def get_unpatched(item: _UnpatchT) -> _UnpatchT: ... # type: ignore[overload-overlap]
43+
@overload
44+
def get_unpatched(item: object) -> None: ...
45+
def get_unpatched(
46+
item: Union[type, types.FunctionType, object],
47+
) -> Optional[Union[type, types.FunctionType]]:
48+
if isinstance(item, type):
49+
return get_unpatched_class(item)
50+
if isinstance(item, types.FunctionType):
51+
return get_unpatched_function(item)
52+
return None
4853

4954

50-
def get_unpatched_class(cls):
55+
def get_unpatched_class(cls: Type[_T]) -> Type[_T]:
5156
"""Protect against re-patching the distutils if reloaded
5257
5358
Also ensures that no other distutils extension monkeypatched the distutils
5459
first.
5560
"""
5661
external_bases = (
57-
cls for cls in _get_mro(cls) if not cls.__module__.startswith('setuptools')
62+
cast(Type[_T], cls)
63+
for cls in _get_mro(cls)
64+
if not cls.__module__.startswith('setuptools')
5865
)
5966
base = next(external_bases)
6067
if not base.__module__.startswith('distutils'):

setuptools/package_index.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,7 @@ def open_with_auth(url, opener=urllib.request.urlopen):
10711071
if scheme in ('http', 'https'):
10721072
auth, address = _splituser(netloc)
10731073
else:
1074-
auth = None
1074+
auth, address = (None, None)
10751075

10761076
if not auth:
10771077
cred = PyPIConfig().find_credential(url)

setuptools/sandbox.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,8 @@ def _violation(self, operation, *args, **kw):
446446
def _file(self, path, mode='r', *args, **kw):
447447
if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):
448448
self._violation("file", path, mode, *args, **kw)
449-
return _file(path, mode, *args, **kw)
449+
# Self-referential, can't be None
450+
return _file(path, mode, *args, **kw) # pyright: ignore[reportOptionalCall]
450451

451452
def _open(self, path, mode='r', *args, **kw):
452453
if mode not in ('r', 'rt', 'rb', 'rU', 'U') and not self._ok(path):

tox.ini

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ setenv =
66
PYTHONWARNDEFAULTENCODING = 1
77
SETUPTOOLS_ENFORCE_DEPRECATION = {env:SETUPTOOLS_ENFORCE_DEPRECATION:1}
88
commands =
9+
pyright .
910
pytest {posargs}
1011
usedevelop = True
1112
extras =

0 commit comments

Comments
 (0)