Skip to content

Commit

Permalink
Basic type-checking with mypy and pyright (#2102)
Browse files Browse the repository at this point in the history
  • Loading branch information
Avasam committed Mar 14, 2024
1 parent 2ef6883 commit e2906a8
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 17 deletions.
40 changes: 39 additions & 1 deletion .github/workflows/main.yml
Expand Up @@ -97,12 +97,50 @@ jobs:
# This job can be run locally with the `format_all.bat` script
checkers:
runs-on: ubuntu-latest
runs-on: windows-2019
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
# This job only needs to target the oldest supported version (black@stable supports Python >=3.8)
python-version: '3.8'
- run: pip install isort pycln
- run: pycln . --config=pycln.toml --check
- run: isort . --diff --check-only
- uses: psf/black@stable
with:
options: "--fast --check --diff --verbose"

mypy:
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
# 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: mypy . --python-version=${{ matrix.python-version }}

pyright:
runs-on: windows-2019
strategy:
fail-fast: false
matrix:
python-version: ['3.7', '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 }}
# pyright vendors typeshed, but let's make sure we have the most up to date stubs
- run: pip install types-regex types-setuptools
- uses: jakebailey/pyright-action@v2
with:
python-version: ${{ matrix.python-version }}
annotate: errors

2 changes: 1 addition & 1 deletion Pythonwin/pywin/framework/stdin.py
Expand Up @@ -168,4 +168,4 @@ def fake_input(prompt=None):
finally:
get_input_line = input
else:
sys.stdin = Stdin()
sys.stdin = Stdin() # type: ignore[assignment] # Not an actual TextIO
38 changes: 38 additions & 0 deletions mypy.ini
@@ -0,0 +1,38 @@
[mypy]
show_column_numbers = true
warn_unused_ignores = true
; Target the oldest supported version in editors
python_version = 3.7

strict = false
implicit_reexport = true

; 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
check_untyped_defs = false
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)

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

; verstamp is installed from win32verstamp.py called in setup.py
; Most of win32com re-exports win32comext
; Test is a local untyped module in win32comext.axdebug
; pywin32_system32 is an empty module created in setup.py to store dlls
[mypy-verstamp,win32com.*,Test,pywin32_system32]
ignore_missing_imports = True
60 changes: 60 additions & 0 deletions pyrightconfig.json
@@ -0,0 +1,60 @@
{
"typeCheckingMode": "basic",
// Target the oldest supported version in editors
"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
"Pythonwin/pywin/idle/",
// Ignoring non-public apis for now
"**/Test/",
"**/test/",
"**/Demos/",
"**/demo/",
],
// 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
"extraPaths": [
"com",
"win32/Lib",
"Pythonwin",
],
// TODO: For now this allows us to at least put pyright in place by massively reducing checked code
// it also reduces issues with the shipped types-pywin32 from typeshed
"reportGeneralTypeIssues": "none",
"reportArgumentType": "none",
"reportAttributeAccessIssue": "none",
// FIXE: These all need to be fixed first and turned back to error
// some of the fixes need to be done in types-pywin32 from typeshed
"reportAssignmentType": "warning",
"reportCallIssue": "warning",
"reportIndexIssue": "warning",
"reportOperatorIssue": "warning",
"reportOptionalCall": "warning",
"reportOptionalIterable": "warning",
"reportOptionalMemberAccess": "warning",
"reportOptionalSubscript": "warning",
// TODO: Leave Unbound/Undefined to its own PR(s)
"reportUnboundVariable": "warning",
"reportUndefinedVariable": "warning",
// Too many dynamically generated modules. This will require type stubs to properly fix.
"reportMissingImports": "warning",
// IDEM, but happens when pywin32 is not in site-packages but module is found from typeshed.
// TODO: Is intended to be fixed with an editable install
// Since we're a library, and not user code, we care less about forgetting to install a dependency,
// as long as we have its stubs. So just disabling for now is fine.
"reportMissingModuleSource": "none",
// External type stubs may not be completable, and this will require type stubs for dynamic modules.
"reportMissingTypeStubs": "information",
// Sometimes used for extra runtime safety
"reportUnnecessaryComparison": "warning",
// Use Flake8/Pycln/Ruff instead
"reportUnusedImport": "none",
}
23 changes: 9 additions & 14 deletions pywin32_postinstall.py
Expand Up @@ -2,19 +2,14 @@
#
# copies pywintypesXX.dll and pythoncomXX.dll into the system directory,
# and creates a pth file
import argparse
import glob
import os
import shutil
import sys
import sysconfig

try:
import winreg as winreg
except:
import winreg

# Send output somewhere so it can be found if necessary...
import tempfile
import tempfile # Send output somewhere so it can be found if necessary...
import winreg

tee_f = open(os.path.join(tempfile.gettempdir(), "pywin32_postinstall.log"), "w")

Expand Down Expand Up @@ -44,11 +39,11 @@ def flush(self):
# with sys.stdout as None but stderr is hooked up. This work-around allows
# bdist_wininst to see the output we write and display it at the end of
# the install.
if sys.stdout is None:
if sys.stdout is None: # pyright: ignore[reportUnnecessaryComparison]
sys.stdout = sys.stderr

sys.stderr = Tee(sys.stderr)
sys.stdout = Tee(sys.stdout)
sys.stderr = Tee(sys.stderr) # type: ignore[assignment] # Not an actual TextIO
sys.stdout = Tee(sys.stdout) # type: ignore[assignment] # Not an actual TextIO

com_modules = [
# module_name, class_names
Expand Down Expand Up @@ -193,7 +188,9 @@ 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(mod)
spec.loader.exec_module( # pyright: ignore[reportOptionalMemberAccess] # We provide the loader, we know it won't be None
mod
)


def SetPyKeyVal(key_name, value_name, value):
Expand Down Expand Up @@ -697,8 +694,6 @@ def verify_destination(location):


def main():
import argparse

parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""A post-install script for the pywin32 extensions.
Expand Down
2 changes: 1 addition & 1 deletion win32/Lib/sspi.py
Expand Up @@ -372,7 +372,7 @@ def authorize(self, sec_buffer_in):
sec_buffer = None
client_step = 0
server_step = 0
while not (sspiclient.authenticated) or len(sec_buffer[0].Buffer):
while not sspiclient.authenticated or (sec_buffer and len(sec_buffer[0].Buffer)):
client_step += 1
err, sec_buffer = sspiclient.authorize(sec_buffer)
print("Client step %s" % client_step)
Expand Down

0 comments on commit e2906a8

Please sign in to comment.