Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic type-checking with mypy and pyright #2102

Merged
merged 4 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"typeCheckingMode": "basic",
// Target the oldest supported version in editors
"pythonVersion": "3.7",
Avasam marked this conversation as resolved.
Show resolved Hide resolved
// 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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading