Skip to content

Commit

Permalink
Turn on strict typing in mypy (#2211)
Browse files Browse the repository at this point in the history
  • Loading branch information
Avasam authored Apr 11, 2024
1 parent de64f6e commit 2b3b1a0
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ max_line_length = 88 # Same as Black
[*.md]
trim_trailing_whitespace = false

[*.{yaml,yml,toml}]
[*.{yaml,yml,json,toml}]
indent_size = 2
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ jobs:
strategy:
fail-fast: false
matrix:
# mypy 1.5 dropped support for python 3.7
# mypy 1.5 dropped support for Python 3.7
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- run: pip install types-regex types-setuptools mypy>=1.5
- run: pip install types-regex types-setuptools mypy==1.9
- run: mypy . --python-version=${{ matrix.python-version }}

pyright:
Expand Down
2 changes: 1 addition & 1 deletion com/win32com/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import win32api

# flag if we are in a "frozen" build.
_frozen = getattr(sys, "frozen", 1 == 0)
_frozen = getattr(sys, "frozen", False)
# pythoncom dumbly defaults this to zero - we believe sys.frozen over it.
if _frozen and not getattr(pythoncom, "frozen", 0):
pythoncom.frozen = sys.frozen
Expand Down
61 changes: 43 additions & 18 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,33 +1,53 @@
[mypy]
show_column_numbers = true
warn_unused_ignores = true
; Target the oldest supported version in editors
python_version = 3.7
; Target the oldest supported version in editors and default CLI
; mypy 1.5 dropped support for Python 3.7
python_version = 3.8

strict = false
strict = true
implicit_reexport = true
; Necessary to avoid "same module name" issues
explicit_package_bases = true
; Must specify top-level packages and scripts folders for mypy to work with explicit_package_bases
mypy_path =
$MYPY_CONFIG_FILE_DIR/com,
$MYPY_CONFIG_FILE_DIR/win32/Lib,
$MYPY_CONFIG_FILE_DIR/Pythonwin,
$MYPY_CONFIG_FILE_DIR/AutoDuck,
$MYPY_CONFIG_FILE_DIR/win32/scripts/VersionStamp,

; Implicit return types !
; TODO: turn back check_untyped_defs to true. For now this allows us to
; at least put mypy in place by massively reducing checked code
; TODO: Gradually type classes and functions until we can turn back check_untyped_defs to true.
; For now this allows us to at least put mypy in place by massively reducing checked code
check_untyped_defs = false
; Implicit return types !
disallow_untyped_calls = false
disallow_untyped_defs = false
disallow_incomplete_defs = false

; attr-defined: Module has no attribute (modules are dynamic)
; method-assign: Cannot assign to a method (lots of monkey patching)
; name-defined: Name "..." is not defined (dynamic modules will be hard to type without stubs, ie: pythoncom.*, leave undefined/unbound to Flake8/Ruff/pyright)
disable_error_code = attr-defined, method-assign, name-defined
; TODO: adodbapi should be updated and fixed separatly
; Pythonwin/Scintilla is vendored
; Pythonwin/pywin/idle is vendored IDLE extensions predating Python 2.3. They now live in idlelib in https://github.com/python/cpython/tree/main/Lib/idlelib
; Ignoring non-public apis for now
; Duplicate module named "rasutil" and "setup", short-term fix is to ignore
exclude = .*((build|adodbapi|Pythonwin/Scintilla|Pythonwin/pywin/idle|[Tt]est|[Dd]emos?)/.*|rasutil.py|setup.py)
disable_error_code =
; Module has no attribute; (Dynamic modules will be hard to type without first-party stubs, ie: pythoncom.*)
attr-defined,
; Class cannot subclass "..." (has type "Any"); (IDEM)
; TODO: Use typeshed's types-pywin32 stubs after a few more fixes there
misc,
; Name "..." is not defined; (IDEM, leave undefined/unbound to Flake8/Ruff/pyright)
name-defined,
; Cannot assign to a method (we do lots of monkey patching)
method-assign,
exclude = (?x)(
^build/
; Vendored
| ^Pythonwin/Scintilla/
; Forked IDLE extensions predating Python 2.3. They now live in idlelib in https://github.com/python/cpython/tree/main/Lib/idlelib
| ^Pythonwin/pywin/idle/
; TODO: adodbapi should be updated and fixed separatly
| ^adodbapi/
; TODO: Ignoring non-public APIs until all public API is typed
| ([Tt]est|[Dd]emos?)/
)

; C-modules that will need type-stubs
[mypy-adsi.*,dde,exchange,exchdapi,perfmon,servicemanager,win32api,win32console,win32clipboard,win32event,win32evtlog,win32file,win32gui,win32help,win32pdh,win32process,win32ras,win32security,win32service,win32trace,win32ui,win32uiole,win32wnet,_win32sysloader,_winxptheme]
[mypy-adsi.*,dde,exchange,exchdapi,mapi,perfmon,servicemanager,win32api,win32console,win32clipboard,win32comext.adsi.adsi,win32event,win32evtlog,win32file,win32gui,win32help,win32pdh,win32process,win32ras,win32security,win32service,win32trace,win32ui,win32uiole,win32wnet,_win32sysloader,_winxptheme]
ignore_missing_imports = True

; verstamp is installed from win32verstamp.py called in setup.py
Expand All @@ -36,3 +56,8 @@ ignore_missing_imports = True
; pywin32_system32 is an empty module created in setup.py to store dlls
[mypy-verstamp,win32com.*,Test,pywin32_system32]
ignore_missing_imports = True

; Distutils being removed from stdlib currently causes some issues on Python 3.12
; https://github.com/mhammond/pywin32/issues/2119
[mypy-distutils.*]
ignore_missing_imports = True
8 changes: 4 additions & 4 deletions pyrightconfig.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
{
"typeCheckingMode": "basic",
// Target the oldest supported version in editors
// Target the oldest supported version in editors and default CLI
"pythonVersion": "3.7",
// Keep it simple for now by allowing both mypy and pyright to use `type: ignore`
"enableTypeIgnoreComments": true,
// Exclude from scanning when running pyright
"exclude": [
"build/",
// TODO: adodbapi should be updated and fixed separatly
"adodbapi/",
// Vendored
"Pythonwin/Scintilla/",
// Vendored IDLE extensions predating Python 2.3. They now live in idlelib in https://github.com/python/cpython/tree/main/Lib/idlelib
// Forked IDLE extensions predating Python 2.3. They now live in idlelib in https://github.com/python/cpython/tree/main/Lib/idlelib
"Pythonwin/pywin/idle/",
// Ignoring non-public apis for now
"**/Test/",
"**/test/",
"**/Demos/",
"**/demo/",
// TODO: adodbapi should be updated and fixed separately
"adodbapi/",
],
// Packages that will be accessible globally.
// Setting this makes pyright use the repo's code for those modules instead of typeshed or pywin32 in site-packages
Expand Down
4 changes: 1 addition & 3 deletions pywin32_postinstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,7 @@ def LoadSystemModule(lib_dir, modname):
loader = importlib.machinery.ExtensionFileLoader(modname, filename)
spec = importlib.machinery.ModuleSpec(name=modname, loader=loader, origin=filename)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module( # pyright: ignore[reportOptionalMemberAccess] # We provide the loader, we know it won't be None
mod
)
loader.exec_module(mod)


def SetPyKeyVal(key_name, value_name, value):
Expand Down
30 changes: 14 additions & 16 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

build_id = "306" # may optionally include a ".{patchno}" suffix.

__doc__ = """This is a distutils setup-script for the pywin32 extensions.
Expand Down Expand Up @@ -44,6 +46,8 @@
from setuptools.command.install import install
from setuptools.command.install_lib import install_lib

from distutils import ccompiler
from distutils._msvccompiler import MSVCCompiler
from distutils.command.install_data import install_data

if sys.version_info >= (3, 8):
Expand Down Expand Up @@ -936,22 +940,17 @@ def my_new_compiler(**kw):


# No way to cleanly wedge our compiler sub-class in.
from distutils import ccompiler
from distutils._msvccompiler import MSVCCompiler

orig_new_compiler = ccompiler.new_compiler
ccompiler.new_compiler = my_new_compiler

base_compiler = MSVCCompiler
ccompiler.new_compiler = my_new_compiler # type: ignore[assignment] # Assuming the caller will always use only kwargs


class my_compiler(base_compiler):
class my_compiler(MSVCCompiler):
# Just one GUIDS.CPP and it gives trouble on mainwin too. Maybe I
# should just rename the file, but a case-only rename is likely to be
# worse! This can probably go away once we kill the VS project files
# though, as we can just specify the lowercase name in the module def.
_cpp_extensions = base_compiler._cpp_extensions + [".CPP"]
src_extensions = base_compiler.src_extensions + [".CPP"]
_cpp_extensions = MSVCCompiler._cpp_extensions + [".CPP"]
src_extensions = MSVCCompiler.src_extensions + [".CPP"]

def link(
self,
Expand Down Expand Up @@ -1112,7 +1111,7 @@ def finalize_options(self):
pch_header="PyWinTypes.h",
)

win32_extensions = [pywintypes]
win32_extensions: list[WinExt] = [pywintypes]

win32_extensions.append(
WinExt_win32(
Expand Down Expand Up @@ -1254,11 +1253,10 @@ def finalize_options(self):
windows_h_ver = info[2]
if len(info) > 3:
sources = info[3].split()
extra_compile_args = []
ext = WinExt_win32(
name,
libraries=lib_names,
extra_compile_args=extra_compile_args,
extra_compile_args=[],
windows_h_version=windows_h_ver,
sources=sources,
)
Expand Down Expand Up @@ -1425,8 +1423,8 @@ def finalize_options(self):
base_address=dll_base_address,
)
dll_base_address += 0x80000 # pythoncom is large!
com_extensions = [pythoncom]
com_extensions += [
com_extensions = [
pythoncom,
WinExt_win32com(
"adsi",
libraries="ACTIVEDS ADSIID user32 advapi32",
Expand Down Expand Up @@ -2089,7 +2087,7 @@ def finalize_options(self):
swig_include_files = "mapilib adsilib".split()


def expand_modules(module_dir: Union[str, os.PathLike]):
def expand_modules(module_dir: Union[str, os.PathLike[str]]):
"""Helper to allow our script specifications to include wildcards."""
return [str(path.with_suffix("")) for path in Path(module_dir).rglob("*.py")]

Expand All @@ -2104,7 +2102,7 @@ def convert_data_files(files: Iterable[str]):
for file in files:
file = os.path.normpath(file)
if file.find("*") >= 0:
files_use = (
files_use = tuple(
str(path)
for path in Path(file).parent.rglob(os.path.basename(file))
# We never want CVS
Expand Down
10 changes: 6 additions & 4 deletions win32/Lib/win32timezone.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: UTF-8 -*-
from __future__ import annotations

"""
win32timezone:
Expand Down Expand Up @@ -231,7 +232,6 @@
datetime.datetime(2011, 11, 6, 1, 0, tzinfo=TimeZoneInfo('Pacific Standard Time'))
"""
from __future__ import annotations

import datetime
import logging
Expand All @@ -240,6 +240,7 @@
import struct
import winreg
from itertools import count
from typing import Dict

import win32api

Expand Down Expand Up @@ -792,8 +793,8 @@ def get_sorted_time_zones(key=None):
return zones


class _RegKeyDict(dict):
def __init__(self, key):
class _RegKeyDict(Dict[str, int]):
def __init__(self, key: winreg.HKEYType):
dict.__init__(self)
self.key = key
self.__load_values()
Expand Down Expand Up @@ -907,7 +908,8 @@ def resolveMUITimeZone(spec):


# from jaraco.util.dictlib 5.3.1
class RangeMap(dict):
# TODO: Update to implementation in jaraco.collections
class RangeMap(dict): # type: ignore[type-arg] # Source code is untyped :/ TODO: Add generics!
"""
A dictionary-like object that uses the keys as bounds for a range.
Inclusion of the value for that range is determined by the
Expand Down

0 comments on commit 2b3b1a0

Please sign in to comment.