From f7208a52e6776ca09eb451aef5504a9fad433eec Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 27 Feb 2024 16:43:48 -0500 Subject: [PATCH 1/3] Config updates for mypy & pyright type-checking --- .github/workflows/main.yml | 35 +++++++++++++++++++++- mypy.ini | 38 ++++++++++++++++++++++++ pyrightconfig.json | 60 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 mypy.ini create mode 100644 pyrightconfig.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 260d16047b..e5be54f541 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -95,13 +95,46 @@ jobs: path: | dist/*.whl + # 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 mypy types-regex types-setuptools + - 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: jakebailey/pyright-action@v1 + with: + python-version: ${{ matrix.python-version }} + diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000000..9055b84925 --- /dev/null +++ b/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 diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 0000000000..1b68253a1c --- /dev/null +++ b/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 + // spme 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", +} From c8925758f01f63f5d67fb0b4ac1f14e211c0057f Mon Sep 17 00:00:00 2001 From: Avasam Date: Tue, 27 Feb 2024 16:44:02 -0500 Subject: [PATCH 2/3] Fixes for basic mypy and pyright type-checking --- Pythonwin/pywin/framework/bitmap.py | 2 +- Pythonwin/pywin/framework/editor/color/coloreditor.py | 2 +- Pythonwin/pywin/framework/editor/editor.py | 2 +- Pythonwin/pywin/framework/mdi_pychecker.py | 2 +- Pythonwin/pywin/framework/sgrepmdi.py | 2 +- Pythonwin/pywin/framework/stdin.py | 2 +- Pythonwin/pywin/scintilla/bindings.py | 6 ++++-- Pythonwin/pywin/scintilla/find.py | 4 +++- Pythonwin/pywin/tools/browseProjects.py | 2 +- com/win32com/client/gencache.py | 11 ++++++++--- com/win32com/server/dispatcher.py | 4 +++- com/win32com/test/testvb.py | 1 + com/win32comext/adsi/demos/test.py | 1 + com/win32comext/axscript/client/framework.py | 4 +++- com/win32comext/axscript/client/scriptdispatch.py | 4 +++- com/win32comext/mapi/mapiutil.py | 8 +++++--- isapi/install.py | 8 +++++--- isapi/samples/advanced.py | 2 +- isapi/simple.py | 4 +++- pywin32_postinstall.py | 8 ++++---- win32/Lib/sspi.py | 2 +- win32/Lib/win32pdhutil.py | 4 +++- win32/Lib/win32rcparser.py | 5 +++-- win32/Lib/win32timezone.py | 3 ++- win32/scripts/ce/pysynch.py | 1 + 25 files changed, 61 insertions(+), 33 deletions(-) diff --git a/Pythonwin/pywin/framework/bitmap.py b/Pythonwin/pywin/framework/bitmap.py index 0099fbe590..7619006f1f 100644 --- a/Pythonwin/pywin/framework/bitmap.py +++ b/Pythonwin/pywin/framework/bitmap.py @@ -128,7 +128,7 @@ def MatchDocType(self, fileName, fileType): # For debugging purposes, when this module may be reloaded many times. try: - win32ui.GetApp().RemoveDocTemplate(bitmapTemplate) + win32ui.GetApp().RemoveDocTemplate(bitmapTemplate) # type: ignore[has-type, used-before-def] except NameError: pass diff --git a/Pythonwin/pywin/framework/editor/color/coloreditor.py b/Pythonwin/pywin/framework/editor/color/coloreditor.py index 6818d82502..6b42de93bf 100644 --- a/Pythonwin/pywin/framework/editor/color/coloreditor.py +++ b/Pythonwin/pywin/framework/editor/color/coloreditor.py @@ -638,7 +638,7 @@ def GetPythonPropertyPages(self): # For debugging purposes, when this module may be reloaded many times. try: - win32ui.GetApp().RemoveDocTemplate(editorTemplate) + win32ui.GetApp().RemoveDocTemplate(editorTemplate) # type: ignore[has-type, used-before-def] except NameError: pass diff --git a/Pythonwin/pywin/framework/editor/editor.py b/Pythonwin/pywin/framework/editor/editor.py index 6c332bd777..a1208cba6f 100644 --- a/Pythonwin/pywin/framework/editor/editor.py +++ b/Pythonwin/pywin/framework/editor/editor.py @@ -503,7 +503,7 @@ def Create(fileName=None, title=None, template=None): if __name__ == prefModule: # For debugging purposes, when this module may be reloaded many times. try: - win32ui.GetApp().RemoveDocTemplate(editorTemplate) + win32ui.GetApp().RemoveDocTemplate(editorTemplate) # type: ignore[has-type, used-before-def] except (NameError, win32ui.error): pass diff --git a/Pythonwin/pywin/framework/mdi_pychecker.py b/Pythonwin/pywin/framework/mdi_pychecker.py index bde43f775b..6bd34905f9 100644 --- a/Pythonwin/pywin/framework/mdi_pychecker.py +++ b/Pythonwin/pywin/framework/mdi_pychecker.py @@ -842,7 +842,7 @@ def getNew(self): try: - win32ui.GetApp().RemoveDocTemplate(greptemplate) + win32ui.GetApp().RemoveDocTemplate(greptemplate) # type: ignore[has-type, used-before-def] except NameError: pass diff --git a/Pythonwin/pywin/framework/sgrepmdi.py b/Pythonwin/pywin/framework/sgrepmdi.py index a4068b5cd2..6383c939d5 100644 --- a/Pythonwin/pywin/framework/sgrepmdi.py +++ b/Pythonwin/pywin/framework/sgrepmdi.py @@ -751,7 +751,7 @@ def getNew(self): try: - win32ui.GetApp().RemoveDocTemplate(greptemplate) + win32ui.GetApp().RemoveDocTemplate(greptemplate) # type: ignore[has-type, used-before-def] except NameError: pass diff --git a/Pythonwin/pywin/framework/stdin.py b/Pythonwin/pywin/framework/stdin.py index 93f5c02df2..20e1b094bd 100644 --- a/Pythonwin/pywin/framework/stdin.py +++ b/Pythonwin/pywin/framework/stdin.py @@ -168,4 +168,4 @@ def fake_input(prompt=None): finally: get_input_line = input else: - sys.stdin = Stdin() + sys.stdin = Stdin() # type: ignore[assignment] diff --git a/Pythonwin/pywin/scintilla/bindings.py b/Pythonwin/pywin/scintilla/bindings.py index 44644bdfc8..16ed461a87 100644 --- a/Pythonwin/pywin/scintilla/bindings.py +++ b/Pythonwin/pywin/scintilla/bindings.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import traceback import win32api @@ -13,8 +15,8 @@ next_id = 5000 -event_to_commands = {} # dict of integer IDs to event names. -command_to_events = {} # dict of event names to int IDs +event_to_commands: dict[str, int] = {} # dict of event names to IDs +command_to_events: dict[int, str] = {} # dict of IDs to event names def assign_command_id(event, id=0): diff --git a/Pythonwin/pywin/scintilla/find.py b/Pythonwin/pywin/scintilla/find.py index e1d21a5bce..75fcaa16c7 100644 --- a/Pythonwin/pywin/scintilla/find.py +++ b/Pythonwin/pywin/scintilla/find.py @@ -1,4 +1,6 @@ # find.py - Find and Replace +from __future__ import annotations + import afxres import win32api import win32con @@ -35,7 +37,7 @@ def __setattr__(self, attr, val): curDialog = None lastSearch = defaultSearch = SearchParams() -searchHistory = [] +searchHistory: list[str] = [] def ShowFindDialog(): diff --git a/Pythonwin/pywin/tools/browseProjects.py b/Pythonwin/pywin/tools/browseProjects.py index 1a420b5f18..fe308c4f52 100644 --- a/Pythonwin/pywin/tools/browseProjects.py +++ b/Pythonwin/pywin/tools/browseProjects.py @@ -24,7 +24,7 @@ def GetText(self): class HLICLBRItem(hierlist.HierListItem): - def __init__(self, name, file, lineno, suffix=""): + def __init__(self, name: str, file, lineno, suffix=""): # If the 'name' object itself has a .name, use it. Not sure # how this happens, but seems pyclbr related. # See PyWin32 bug 817035 diff --git a/com/win32com/client/gencache.py b/com/win32com/client/gencache.py index cc23afdf7d..296e594e84 100644 --- a/com/win32com/client/gencache.py +++ b/com/win32com/client/gencache.py @@ -21,10 +21,14 @@ Maybe an OLE2 compound file, or a bsddb file? """ +from __future__ import annotations + import glob import os import sys from importlib import reload +from types import ModuleType +from typing import Any import pythoncom import pywintypes @@ -36,11 +40,11 @@ bForDemandDefault = 0 # Default value of bForDemand - toggle this to change the world - see also makepy.py # The global dictionary -clsidToTypelib = {} +clsidToTypelib: dict[str, tuple[str, int, int, int]] = {} # If we have a different version of the typelib generated, this # maps the "requested version" to the "generated version". -versionRedirectMap = {} +versionRedirectMap: dict[tuple[str, int, int, int], ModuleType | None] = {} # There is no reason we *must* be readonly in a .zip, but we are now, # Rather than check for ".zip" or other tricks, PEP302 defines @@ -53,7 +57,8 @@ # A dictionary of ITypeLibrary objects for demand generation explicitly handed to us # Keyed by usual clsid, lcid, major, minor -demandGeneratedTypeLibraries = {} +# Typing as Any because PyITypeLib is not exposed +demandGeneratedTypeLibraries: dict[tuple[str, int, int, int], Any] = {} import pickle as pickle diff --git a/com/win32com/server/dispatcher.py b/com/win32com/server/dispatcher.py index 88c1fa0218..1b2fb4cea8 100644 --- a/com/win32com/server/dispatcher.py +++ b/com/win32com/server/dispatcher.py @@ -3,6 +3,8 @@ Please see policy.py for a discussion on dispatchers and policies """ +from __future__ import annotations + import traceback from sys import exc_info @@ -284,6 +286,6 @@ def _HandleException_(self): try: import win32trace - DefaultDebugDispatcher = DispatcherWin32trace + DefaultDebugDispatcher: type[DispatcherTrace] = DispatcherWin32trace except ImportError: # no win32trace module - just use a print based one. DefaultDebugDispatcher = DispatcherTrace diff --git a/com/win32com/test/testvb.py b/com/win32com/test/testvb.py index bd188ca0c2..02e33b614f 100644 --- a/com/win32com/test/testvb.py +++ b/com/win32com/test/testvb.py @@ -4,6 +4,7 @@ # import traceback +from collections.abc import Callable import pythoncom import win32com.client diff --git a/com/win32comext/adsi/demos/test.py b/com/win32comext/adsi/demos/test.py index 64061ab68d..3d6b47d565 100644 --- a/com/win32comext/adsi/demos/test.py +++ b/com/win32comext/adsi/demos/test.py @@ -1,4 +1,5 @@ import sys +from collections.abc import Callable import pythoncom import win32api diff --git a/com/win32comext/axscript/client/framework.py b/com/win32comext/axscript/client/framework.py index 9dbdfd5533..0b250a71e1 100644 --- a/com/win32comext/axscript/client/framework.py +++ b/com/win32comext/axscript/client/framework.py @@ -7,6 +7,8 @@ There are classes defined for the engine itself, and for ScriptItems """ +from __future__ import annotations + import re import sys @@ -175,7 +177,7 @@ def Build(self, typeinfo, funcdesc): class EventSink: """A set of events against an item. Note this is a COM client for connection points.""" - _public_methods_ = [] + _public_methods_: list[str] = [] def __init__(self, myItem, coDispatch): self.events = {} diff --git a/com/win32comext/axscript/client/scriptdispatch.py b/com/win32comext/axscript/client/scriptdispatch.py index c1989dc286..2c15d74367 100644 --- a/com/win32comext/axscript/client/scriptdispatch.py +++ b/com/win32comext/axscript/client/scriptdispatch.py @@ -5,6 +5,8 @@ this yet, so it is not well tested! """ +from __future__ import annotations + import types import pythoncom @@ -26,7 +28,7 @@ def _is_callable(obj): class ScriptDispatch: - _public_methods_ = [] + _public_methods_: list[str] = [] def __init__(self, engine, scriptNamespace): self.engine = engine diff --git a/com/win32comext/mapi/mapiutil.py b/com/win32comext/mapi/mapiutil.py index bf13788076..8716a650a9 100644 --- a/com/win32comext/mapi/mapiutil.py +++ b/com/win32comext/mapi/mapiutil.py @@ -1,10 +1,12 @@ # General utilities for MAPI and MAPI objects. +from __future__ import annotations + import pythoncom from pywintypes import TimeType from . import mapi, mapitags -prTable = {} +prTable: dict[int, str] = {} def GetPropTagName(pt): @@ -57,7 +59,7 @@ def GetPropTagName(pt): return ret -mapiErrorTable = {} +mapiErrorTable: dict[int, str] = {} def GetScodeString(hr): @@ -68,7 +70,7 @@ def GetScodeString(hr): return mapiErrorTable.get(hr, pythoncom.GetScodeString(hr)) -ptTable = {} +ptTable: dict[int, str] = {} def GetMapiTypeName(propType, rawType=True): diff --git a/isapi/install.py b/isapi/install.py index f28cf2ea0c..b89c521dfa 100644 --- a/isapi/install.py +++ b/isapi/install.py @@ -2,6 +2,8 @@ # this code adapted from "Tomcat JK2 ISAPI redirector", part of Apache # Created July 2004, Mark Hammond. +from __future__ import annotations + import importlib.machinery import os import shutil @@ -73,7 +75,7 @@ class VirtualDirParameters: EnableDirBrowsing = _DEFAULT_ENABLE_DIR_BROWSING EnableDefaultDoc = _DEFAULT_ENABLE_DEFAULT_DOC DefaultDoc = None # Only set in IIS if not None - ScriptMaps = [] + ScriptMaps: list[ScriptMapParams] = [] ScriptMapUpdate = "end" # can be 'start', 'end', 'replace' Server = None @@ -117,8 +119,8 @@ def __str__(self): class ISAPIParameters: ServerName = _DEFAULT_SERVER_NAME # Description = None - Filters = [] - VirtualDirs = [] + Filters: list[FilterParameters] = [] + VirtualDirs: list[VirtualDirParameters] = [] def __init__(self, **kw): self.__dict__.update(kw) diff --git a/isapi/samples/advanced.py b/isapi/samples/advanced.py index 913de024a6..3cce5d885b 100644 --- a/isapi/samples/advanced.py +++ b/isapi/samples/advanced.py @@ -67,7 +67,7 @@ from isapi import InternalReloadException try: - reload_counter += 1 + reload_counter += 1 # type: ignore[used-before-def] except NameError: reload_counter = 0 diff --git a/isapi/simple.py b/isapi/simple.py index b453bbae01..d5fcb04d7c 100644 --- a/isapi/simple.py +++ b/isapi/simple.py @@ -8,6 +8,8 @@ must ensure each of the required methods are implemented. """ +from __future__ import annotations + class SimpleExtension: "Base class for a simple ISAPI extension" @@ -38,7 +40,7 @@ def TerminateExtension(self, status): class SimpleFilter: "Base class for a a simple ISAPI filter" - filter_flags = None + filter_flags: int | None = None def __init__(self): pass diff --git a/pywin32_postinstall.py b/pywin32_postinstall.py index 19b971ba44..f81c00e231 100644 --- a/pywin32_postinstall.py +++ b/pywin32_postinstall.py @@ -47,8 +47,8 @@ def flush(self): if sys.stdout is None: sys.stdout = sys.stderr -sys.stderr = Tee(sys.stderr) -sys.stdout = Tee(sys.stdout) +sys.stderr = Tee(sys.stderr) # type: ignore[assignment] +sys.stdout = Tee(sys.stdout) # type: ignore[assignment] com_modules = [ # module_name, class_names @@ -72,7 +72,7 @@ def flush(self): # functions which write lines to PythonXX\pywin32-install.log. This is # a list of actions for the uninstaller, the format is inspired by what # the Wise installer also creates. - file_created + file_created # type: ignore[used-before-def] # 3.10 stopped supporting bdist_wininst, but we can still build them with 3.9. # This can be kept until Python 3.9 or exe installers support is dropped. is_bdist_wininst = True @@ -98,7 +98,7 @@ def get_root_hkey(): try: - create_shortcut + create_shortcut # type: ignore[used-before-def] except NameError: # Create a function with the same signature as create_shortcut provided # by bdist_wininst diff --git a/win32/Lib/sspi.py b/win32/Lib/sspi.py index d1e76e7f8b..79afa58393 100644 --- a/win32/Lib/sspi.py +++ b/win32/Lib/sspi.py @@ -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) diff --git a/win32/Lib/win32pdhutil.py b/win32/Lib/win32pdhutil.py index 7795dd904e..850e0ab89d 100644 --- a/win32/Lib/win32pdhutil.py +++ b/win32/Lib/win32pdhutil.py @@ -18,6 +18,8 @@ the easiest way is often to simply use PerfMon to find out the names. """ +from __future__ import annotations + import time import win32pdh @@ -27,7 +29,7 @@ # Handle some localization issues. # see http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/Q287/1/59.asp&NoWebContent=1 # Build a map of english_counter_name: counter_id -counter_english_map = {} +counter_english_map: dict[str, int] = {} def find_pdh_counter_localized_name(english_name, machine_name=None): diff --git a/win32/Lib/win32rcparser.py b/win32/Lib/win32rcparser.py index e76acfc9aa..8f065bb8eb 100644 --- a/win32/Lib/win32rcparser.py +++ b/win32/Lib/win32rcparser.py @@ -7,6 +7,7 @@ This is a parser for Windows .rc files, which are text files which define dialogs and other Windows UI resources. """ +from __future__ import annotations import os import pprint @@ -172,8 +173,8 @@ def __repr__(self): class RCParser: next_id = 1001 - dialogs = {} - _dialogs = {} + dialogs: dict[str, list[list[str | int | None | tuple[str | int, ...]]]] = {} + _dialogs: dict[str, DialogDef] = {} debugEnabled = False token = "" diff --git a/win32/Lib/win32timezone.py b/win32/Lib/win32timezone.py index d27816ed65..dc41514e03 100644 --- a/win32/Lib/win32timezone.py +++ b/win32/Lib/win32timezone.py @@ -231,6 +231,7 @@ datetime.datetime(2011, 11, 6, 1, 0, tzinfo=TimeZoneInfo('Pacific Standard Time')) """ +from __future__ import annotations import datetime import logging @@ -250,7 +251,7 @@ # A couple of objects for working with objects as if they were native C-type # structures. class _SimpleStruct: - _fields_ = [] # must be overridden by subclasses + _fields_: list[tuple[str, type]] = [] # must be overridden by subclasses def __init__(self, *args, **kw): for i, (name, typ) in enumerate(self._fields_): diff --git a/win32/scripts/ce/pysynch.py b/win32/scripts/ce/pysynch.py index 3244ae45dc..f01e7e4b89 100644 --- a/win32/scripts/ce/pysynch.py +++ b/win32/scripts/ce/pysynch.py @@ -4,6 +4,7 @@ import getopt import os import sys +from collections.abc import Callable import win32api import win32con From d318fe1049ae22adc0350e8396619a0dc1611584 Mon Sep 17 00:00:00 2001 From: Avasam Date: Wed, 28 Feb 2024 11:30:58 -0500 Subject: [PATCH 3/3] Improve error reporting in GitHub and always fetch latest stubs --- .github/workflows/main.yml | 10 ++++++++-- Pythonwin/pywin/framework/stdin.py | 2 +- pyrightconfig.json | 2 +- pywin32_postinstall.py | 23 +++++++++-------------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e5be54f541..a0583c3a9a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,7 +123,7 @@ jobs: - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - run: pip install mypy types-regex types-setuptools + - run: pip install types-regex types-setuptools mypy>=1.5 - run: mypy . --python-version=${{ matrix.python-version }} pyright: @@ -134,7 +134,13 @@ jobs: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v3 - - uses: jakebailey/pyright-action@v1 + - 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 diff --git a/Pythonwin/pywin/framework/stdin.py b/Pythonwin/pywin/framework/stdin.py index 20e1b094bd..91fe7ef3e1 100644 --- a/Pythonwin/pywin/framework/stdin.py +++ b/Pythonwin/pywin/framework/stdin.py @@ -168,4 +168,4 @@ def fake_input(prompt=None): finally: get_input_line = input else: - sys.stdin = Stdin() # type: ignore[assignment] + sys.stdin = Stdin() # type: ignore[assignment] # Not an actual TextIO diff --git a/pyrightconfig.json b/pyrightconfig.json index 1b68253a1c..1dd371af23 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -32,7 +32,7 @@ "reportArgumentType": "none", "reportAttributeAccessIssue": "none", // FIXE: These all need to be fixed first and turned back to error - // spme of the fixes need to be done in types-pywin32 from typeshed + // some of the fixes need to be done in types-pywin32 from typeshed "reportAssignmentType": "warning", "reportCallIssue": "warning", "reportIndexIssue": "warning", diff --git a/pywin32_postinstall.py b/pywin32_postinstall.py index f81c00e231..5a4981edc6 100644 --- a/pywin32_postinstall.py +++ b/pywin32_postinstall.py @@ -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") @@ -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) # type: ignore[assignment] -sys.stdout = Tee(sys.stdout) # type: ignore[assignment] +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 @@ -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): @@ -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.