From f7b2321575a932c19b416b59ea3207ccfd775713 Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 18 Oct 2024 16:50:41 -0400 Subject: [PATCH 1/6] Search by version numbers + run autofixes --- .github/workflows/main.yml | 11 +++--- Pythonwin/pywin/framework/app.py | 5 +-- Pythonwin/pywin/test/test_pywin.py | 15 +++++--- Pythonwin/win32cmdui.cpp | 13 ------- Pythonwin/win32uimodule.cpp | 6 +-- adodbapi/apibase.py | 3 +- adodbapi/test/adodbapitest.py | 45 +---------------------- adodbapi/test/dbapi20.py | 11 ++---- build_all.bat | 4 -- build_env.md | 2 +- make_all.bat | 5 --- mypy.ini | 2 +- pyrightconfig.json | 5 ++- ruff.toml | 17 +++++---- setup.py | 35 +++++++++--------- win32/Lib/_win32verstamp_pywin32ctypes.py | 3 +- win32/Lib/win32timezone.py | 3 +- win32/test/test_win32crypt.py | 3 +- 18 files changed, 59 insertions(+), 129 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index db40b4254..ba28701bd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] architecture: ["x64", "x86"] steps: @@ -69,7 +69,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13-dev"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 @@ -111,7 +111,7 @@ jobs: - uses: actions/setup-python@v5 with: # This job only needs to target the oldest supported version - python-version: "3.8" + python-version: "3.9" cache: pip cache-dependency-path: .github/workflows/main.yml - run: pip install clang-format pycln @@ -133,8 +133,7 @@ jobs: strategy: fail-fast: false matrix: - # mypy won't understand "3.13-dev", keeping the CI simple by just omitting it - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -152,7 +151,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/Pythonwin/pywin/framework/app.py b/Pythonwin/pywin/framework/app.py index 9899e62fa..24a4aa1cf 100644 --- a/Pythonwin/pywin/framework/app.py +++ b/Pythonwin/pywin/framework/app.py @@ -9,7 +9,7 @@ import os import sys import traceback -from typing import TYPE_CHECKING +from typing import Literal import regutil import win32api @@ -20,9 +20,6 @@ from . import scriptutils -if TYPE_CHECKING: - from typing_extensions import Literal - # Helper for writing a Window position by name, and later loading it. def SaveWindowSize(section, rect, state=""): diff --git a/Pythonwin/pywin/test/test_pywin.py b/Pythonwin/pywin/test/test_pywin.py index 49acc9d5e..b2802795f 100644 --- a/Pythonwin/pywin/test/test_pywin.py +++ b/Pythonwin/pywin/test/test_pywin.py @@ -139,9 +139,13 @@ def t_Browse(*args): if __name__ != "__main__": # make T findable by browser in __main__ namespace setattr(__main__, __class__.__qualname__, __class__) - with mock.patch( - "pywin.mfc.dialog.GetSimpleInput", (lambda *args: __class__.__qualname__) - ), mock.patch("pywin.tools.browser.Browse", t_Browse): + with ( + mock.patch( + "pywin.mfc.dialog.GetSimpleInput", + (lambda *args: __class__.__qualname__), + ), + mock.patch("pywin.tools.browser.Browse", t_Browse), + ): self.app.OnViewBrowse(0, 0) hl = o.dlg.hier_list self.assertGreater(len(hl.itemHandleMap), 10) @@ -443,8 +447,9 @@ def t_brk(self): GUIAboutToBreak() dmod = types.ModuleType("__main__", "debugger test main") - with mock.patch("pywin.framework.scriptutils.__main__", dmod), mock.patch( - "pywin.debugger.debugger.Debugger.GUIAboutToBreak", t_brk + with ( + mock.patch("pywin.framework.scriptutils.__main__", dmod), + mock.patch("pywin.debugger.debugger.Debugger.GUIAboutToBreak", t_brk), ): mf.SendMessage(wc.WM_COMMAND, cmGo) # debh.OnGo(0, 0) self.assertFalse(cmds_brk_next, "break commands remaining") diff --git a/Pythonwin/win32cmdui.cpp b/Pythonwin/win32cmdui.cpp index 617c8e5f6..247314652 100644 --- a/Pythonwin/win32cmdui.cpp +++ b/Pythonwin/win32cmdui.cpp @@ -27,19 +27,6 @@ inline void *GetPythonOleProcAddress(const char *procName) #endif hMod = GetModuleHandle(buf); - // XXX It is unclear why the code previously tried to identify a loaded PythonCOM DLL of - // any Python version 1.5 .. 3.9. If some InprocServer would load the DLL of a different - // Python version that would likely cause a crash. Thus deactivated. - // - // for (int i = 0; hMod == NULL && i < 40; i++) { - // #ifdef _DEBUG - // wsprintf(buf, _T("PythonCOM3%d_d.dll"), i); - // #else - // wsprintf(buf, _T("PythonCOM3%d.dll"), i); - // #endif - // hMod = GetModuleHandle(buf); - // } - if (hMod) { void *rc = GetProcAddress(hMod, procName); if (rc == NULL) diff --git a/Pythonwin/win32uimodule.cpp b/Pythonwin/win32uimodule.cpp index e5e03878f..c9a0a214d 100644 --- a/Pythonwin/win32uimodule.cpp +++ b/Pythonwin/win32uimodule.cpp @@ -2559,9 +2559,9 @@ extern "C" PYW_EXPORT BOOL Win32uiApplicationInit(Win32uiHostGlue *pGlue, const PyObject *argv = PySys_GetObject("argv"); PyInit_win32ui(); // Decide if we render sys.argv from command line. - // PY3.6- Py_Initialize sets sys.argv=NULL . - // PY3.7 Py_Initialize or intentional script triggers set sys.argv=[] . - // PY3.8+ Py_Initialize sets sys.argv=[''] - cannot be distinguished + // Python 3.6- Py_Initialize sets sys.argv=NULL . + // Python 3.7 Py_Initialize or intentional script triggers set sys.argv=[] . + // Python 3.8+ Py_Initialize sets sys.argv=[''] - cannot be distinguished // from a pre-existing command line setup anymore. So we need to check // another flag regarding the intended type of invokation, e.g. `cmd` // (or untangle all that crossover startup + module + app init here) diff --git a/adodbapi/apibase.py b/adodbapi/apibase.py index 749c378d3..0c09ef98e 100644 --- a/adodbapi/apibase.py +++ b/adodbapi/apibase.py @@ -13,7 +13,6 @@ import sys import time from collections.abc import Callable, Iterable, Mapping -from typing import Dict # noinspection PyUnresolvedReferences from . import ado_consts as adc @@ -465,7 +464,7 @@ def convert_to_python(variant, func): # convert DB value into Python value return func(variant) # call the appropriate conversion function -class MultiMap(Dict[int, Callable[[object], object]]): +class MultiMap(dict[int, Callable[[object], object]]): # builds a dictionary from {(iterable,of,keys) : function} """A dictionary of ado.type : function -- but you can set multiple items by passing an iterable of keys""" diff --git a/adodbapi/test/adodbapitest.py b/adodbapi/test/adodbapitest.py index dd8e9196d..654369b7b 100644 --- a/adodbapi/test/adodbapitest.py +++ b/adodbapi/test/adodbapitest.py @@ -188,49 +188,6 @@ def testUserDefinedConversions(self): pass self.helpRollbackTblTemp() - def testUserDefinedConversionForExactNumericTypes(self): - # variantConversions is a dictionary of conversion functions - # held internally in adodbapi.apibase - # - # !!! this test intentionally alters the value of what should be constant in the module - # !!! no new code should use this example, to is only a test to see that the - # !!! deprecated way of doing this still works. (use connection.variantConversions) - # - if sys.version_info < (3, 0): ### Py3 need different test - oldconverter = adodbapi.variantConversions[ - ado_consts.adNumeric - ] # keep old function to restore later - # By default decimal and "numbers" are returned as decimals. - # Instead, make numbers return as floats - try: - adodbapi.variantConversions[ado_consts.adNumeric] = adodbapi.cvtFloat - self.helpTestDataType( - "decimal(18,2)", "NUMBER", 3.45, compareAlmostEqual=1 - ) - self.helpTestDataType( - "numeric(18,2)", "NUMBER", 3.45, compareAlmostEqual=1 - ) - # now return strings - adodbapi.variantConversions[ado_consts.adNumeric] = adodbapi.cvtString - self.helpTestDataType("numeric(18,2)", "NUMBER", "3.45") - # now a completly weird user defined convertion - adodbapi.variantConversions[ado_consts.adNumeric] = ( - lambda x: "!!This function returns a funny unicode string %s!!" % x - ) - self.helpTestDataType( - "numeric(18,2)", - "NUMBER", - "3.45", - allowedReturnValues=[ - "!!This function returns a funny unicode string 3.45!!" - ], - ) - finally: - # now reset the converter to its original function - adodbapi.variantConversions[ado_consts.adNumeric] = ( - oldconverter # Restore the original convertion function - ) - def helpTestDataType( self, sqlDataTypeString, @@ -432,7 +389,7 @@ def testDataTypeInt(self): "bigint", "NUMBER", 3000000000, - allowedReturnValues=[3000000000, int(3000000000)], + allowedReturnValues=[3000000000, 3000000000], ) self.helpTestDataType("int", "NUMBER", 2147483647) diff --git a/adodbapi/test/dbapi20.py b/adodbapi/test/dbapi20.py index 373b984d6..441f0bd3c 100644 --- a/adodbapi/test/dbapi20.py +++ b/adodbapi/test/dbapi20.py @@ -197,14 +197,9 @@ def test_paramstyle(self): self.fail("Driver doesn't define paramstyle") def test_Exceptions(self): - # Make sure required exceptions exist, and are in the - # defined heirarchy. - if sys.version[0] == "3": # under Python 3 StardardError no longer exists - self.assertTrue(issubclass(self.driver.Warning, Exception)) - self.assertTrue(issubclass(self.driver.Error, Exception)) - else: - self.failUnless(issubclass(self.driver.Warning, Exception)) - self.failUnless(issubclass(self.driver.Error, Exception)) + # Make sure required exceptions exist, and are in the defined hierarchy. + self.assertTrue(issubclass(self.driver.Warning, Exception)) + self.assertTrue(issubclass(self.driver.Error, Exception)) self.assertTrue(issubclass(self.driver.InterfaceError, self.driver.Error)) self.assertTrue(issubclass(self.driver.DatabaseError, self.driver.Error)) diff --git a/build_all.bat b/build_all.bat index ec8cbbaa2..2260a6682 100644 --- a/build_all.bat +++ b/build_all.bat @@ -1,7 +1,3 @@ -py -3.8-32 setup.py -q build -@if errorlevel 1 goto failed -py -3.8 setup.py -q build -@if errorlevel 1 goto failed py -3.9-32 setup.py -q build @if errorlevel 1 goto failed py -3.9 setup.py -q build diff --git a/build_env.md b/build_env.md index 4f616bf99..b1010b962 100644 --- a/build_env.md +++ b/build_env.md @@ -5,7 +5,7 @@ This describes how to setup the build environment for pywin32. Double check the compiler version you need in the [Python wiki](https://wiki.python.org/moin/WindowsCompilers) but note that Python 3.5 -> 3.13 all use version 14.X of the compiler, which, confusingly, report themselves as V.19XX (eg, note in Python's banner, -3.5's "MSC v.1900", even 3.9b4's "MSC v.1924") +3.5's "MSC v.1900", even 3.13's "MSC v.1941") This compiler first shipped with Visual Studio 2015, although Visual Studio 2017, 2019 and 2022 all have this compiler available, just not installed diff --git a/make_all.bat b/make_all.bat index 05855ddfb..433a79545 100644 --- a/make_all.bat +++ b/make_all.bat @@ -13,11 +13,6 @@ rem Now the binaries. rem Check /build_env.md#build-environment to make sure you have all the required components installed rem (bdist_wininst needs --target-version to name the installers correctly!) -py -3.8-32 setup.py -q bdist_wininst --skip-build --target-version=3.8 -py -3.8-32 setup.py -q bdist_wheel --skip-build -py -3.8 setup.py -q bdist_wininst --skip-build --target-version=3.8 -py -3.8 setup.py -q bdist_wheel --skip-build - py -3.9-32 setup.py -q bdist_wininst --skip-build --target-version=3.9 py -3.9-32 setup.py -q bdist_wheel --skip-build py -3.9 setup.py -q bdist_wininst --skip-build --target-version=3.9 diff --git a/mypy.ini b/mypy.ini index e4676e0c7..4a70beb4e 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,7 +1,7 @@ [mypy] show_column_numbers = true ; Target the oldest supported version in editors and default CLI -python_version = 3.8 +python_version = 3.9 strict = true implicit_reexport = true diff --git a/pyrightconfig.json b/pyrightconfig.json index d96b3fc9d..b9505007e 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -1,11 +1,12 @@ { "typeCheckingMode": "basic", // Target the oldest supported version in editors and default CLI - "pythonVersion": "3.8", + "pythonVersion": "3.9", // Keep it simple for now by allowing both mypy and pyright to use `type: ignore` "enableTypeIgnoreComments": true, // Exclude from scanning when running pyright "exclude": [ + ".git/", // Avoids scanning git branch names ending in ".py" "build/", // Vendored "Pythonwin/Scintilla/", @@ -37,7 +38,7 @@ "reportOptionalIterable": "warning", "reportOptionalMemberAccess": "warning", "reportOptionalSubscript": "warning", - // Needs fixes in types-pywin32 and requires Python 3.8 to annotate ambiguous global variables + // Needs fixes in types-pywin32 to annotate ambiguous global variables "reportUnnecessaryComparison": "warning", // TODO: Leave Unbound its own PR(s) "reportUnboundVariable": "warning", diff --git a/ruff.toml b/ruff.toml index 6d75257db..3d8a72585 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,4 +1,4 @@ -target-version = "py38" # Target the oldest supported version in editors and default CLI +target-version = "py39" # Target the oldest supported version in editors and default CLI # This file is not UTF-8 extend-exclude = ["Pythonwin/pywin/test/_dbgscript.py"] @@ -16,12 +16,7 @@ select = [ # String formatting, concatenating, interpolation, ... "FLY", # static-join-to-f-string "G", # flake8-logging-format - # Note, we still want to allow multiline ISC - "UP025", # Remove unicode literals from strings - "UP030", # Use implicit references for positional format fields - # TODO: Still lots of manual fixes needed - # "UP031", # Use format specifiers instead of percent format - # "UP032", # Use f-string instead of format call + "UP", # pyupgrade # Ensure modern type annotation syntax and best practices # Not including those covered by type-checkers @@ -31,21 +26,27 @@ select = [ "UP006", # non-pep585-annotation "UP007", # non-pep604-annotation "UP010", # unnecessary-future-import + "UP035", # deprecated-import "UP037", # quoted-annotation # Helps prevent circular imports and other unneeded imports "TCH", # flake8-type-checking ] extend-ignore = [ + # We prefer explicit open mode parameter + "UP015", # redundant-open-modes # No such concerns for stdlib "TCH003", # typing-only-standard-library-import + # TODO: Still lots of manual fixes needed + "UP031", # Use format specifiers instead of percent format + "UP032", # Use f-string instead of format call ] [lint.per-file-ignores] # Explicit re-exports is fine in __init__.py, still a code smell elsewhere. "__init__.py" = ["PLC0414"] # TODO: Make adodbapi changes in their own PRs -"adodbapi/*" = ["C4", "YTT301", "UP031", "UP032", "ISC002"] +"adodbapi/*" = ["C4", "YTT301", "UP031", "UP032"] [lint.isort] combine-as-imports = true diff --git a/setup.py b/setup.py index e72edf0ad..cf1c6f062 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ import subprocess import sys import winreg +from collections.abc import Iterable, MutableSequence from pathlib import Path from setuptools import Extension, setup from setuptools.command.build import build @@ -42,7 +43,6 @@ from setuptools.command.install_lib import install_lib from setuptools.modified import newer_group from tempfile import gettempdir -from typing import Iterable from distutils import ccompiler from distutils._msvccompiler import MSVCCompiler @@ -896,8 +896,8 @@ def install(self): # This is crazy - in setuptools 61.1.0 (and probably some earlier versions), the # install_lib and build comments don't agree on where the .py files to install can # be found, so we end up with a warning logged: - # `warning: my_install_lib: 'build\lib.win-amd64-3.8' does not exist -- no Python modules to install` - # (because they are actually in `build\lib.win-amd64-cpython-38`!) + # `warning: my_install_lib: 'build\lib.win-amd64-3.XX' does not exist -- no Python modules to install` + # (because they are actually in `build\lib.win-amd64-cpython-3XX`!) # It's not an error though, so we end up with .exe installers lacking our lib files! builder = self.get_finalized_command("build") if os.path.isdir(builder.build_platlib) and not os.path.isdir(self.build_dir): @@ -941,7 +941,7 @@ def link( debug=0, *args, **kw, - ): + ) -> None: super().link( target_desc, objects, @@ -959,18 +959,18 @@ def link( # target. Do this externally to avoid suddenly dragging in the # modules needed by this process, and which we will soon try and # update. - args = [ - sys.executable, - # NOTE: On Python 3.7, all args must be str - str(Path(__file__).parent / "win32" / "Lib" / "win32verstamp.py"), - f"--version={pywin32_version}", - "--comments=https://github.com/mhammond/pywin32", - f"--original-filename={os.path.basename(output_filename)}", - "--product=PyWin32", - "--quiet" if "-v" not in sys.argv else "", - output_filename, - ] - self.spawn(args) + self.spawn( + [ + sys.executable, + str(Path(__file__).parent / "win32" / "Lib" / "win32verstamp.py"), + f"--version={pywin32_version}", + "--comments=https://github.com/mhammond/pywin32", + f"--original-filename={os.path.basename(output_filename)}", + "--product=PyWin32", + "--quiet" if "-v" not in sys.argv else "", + output_filename, + ] + ) # Work around bpo-36302/bpo-42009 - it sorts sources but this breaks # support for building .mc files etc :( @@ -984,7 +984,7 @@ def key_reverse_mc(a): sources = sorted(sources, key=key_reverse_mc) return MSVCCompiler.compile(self, sources, **kwargs) - def spawn(self, cmd): + def spawn(self, cmd: MutableSequence[str]): is_link = cmd[0].endswith("link.exe") or cmd[0].endswith('"link.exe"') is_mt = cmd[0].endswith("mt.exe") or cmd[0].endswith('"mt.exe"') if is_mt: @@ -2108,7 +2108,6 @@ def convert_optional_data_files(files): "Intended Audience :: Developers", "License :: OSI Approved :: Python Software Foundation License", "Operating System :: Microsoft :: Windows", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/win32/Lib/_win32verstamp_pywin32ctypes.py b/win32/Lib/_win32verstamp_pywin32ctypes.py index b878809b7..33333422f 100644 --- a/win32/Lib/_win32verstamp_pywin32ctypes.py +++ b/win32/Lib/_win32verstamp_pywin32ctypes.py @@ -30,13 +30,12 @@ LPVOID, WORD, ) -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Literal, SupportsBytes, SupportsIndex if TYPE_CHECKING: from ctypes import _NamedFuncPointer from _typeshed import ReadableBuffer - from typing_extensions import Literal, SupportsBytes, SupportsIndex kernel32 = WinDLL("kernel32", use_last_error=True) diff --git a/win32/Lib/win32timezone.py b/win32/Lib/win32timezone.py index fdd71c855..4e29f605d 100644 --- a/win32/Lib/win32timezone.py +++ b/win32/Lib/win32timezone.py @@ -246,7 +246,6 @@ import struct import winreg from itertools import count -from typing import Dict import win32api @@ -855,7 +854,7 @@ def get_sorted_time_zones(key=None): return zones -class _RegKeyDict(Dict[str, int]): +class _RegKeyDict(dict[str, int]): def __init__(self, key: winreg.HKEYType): dict.__init__(self) self.key = key diff --git a/win32/test/test_win32crypt.py b/win32/test/test_win32crypt.py index 9360cfbe2..f6f656caa 100644 --- a/win32/test/test_win32crypt.py +++ b/win32/test/test_win32crypt.py @@ -2,7 +2,8 @@ import contextlib import unittest -from typing import Any, Iterator +from collections.abc import Iterator +from typing import Any import win32crypt from pywin32_testutil import find_test_fixture, testmain From 44e60d22e89d1494120f4f00959c108d71ce1081 Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 18 Oct 2024 18:39:05 -0400 Subject: [PATCH 2/6] PEP 584, union operators added to dict --- Pythonwin/pywin/idle/CallTips.py | 4 +--- Pythonwin/pywin/scintilla/view.py | 5 ++--- adodbapi/test/adodbapitest.py | 6 +++--- isapi/install.py | 3 +-- win32/Demos/win32netdemo.py | 3 +-- win32/test/test_win32cred.py | 10 ++++------ win32/test/test_win32file.py | 3 +-- 7 files changed, 13 insertions(+), 21 deletions(-) diff --git a/Pythonwin/pywin/idle/CallTips.py b/Pythonwin/pywin/idle/CallTips.py index c15847955..21b16de53 100644 --- a/Pythonwin/pywin/idle/CallTips.py +++ b/Pythonwin/pywin/idle/CallTips.py @@ -94,10 +94,8 @@ def get_object_at_cursor( # How is this for a hack! import __main__ - namespace = sys.modules.copy() - namespace.update(__main__.__dict__) try: - return eval(word, namespace) + return eval(word, sys.modules | __main__.__dict__) except: pass return None # Can't find an object. diff --git a/Pythonwin/pywin/scintilla/view.py b/Pythonwin/pywin/scintilla/view.py index 99680f18e..5144a3a28 100644 --- a/Pythonwin/pywin/scintilla/view.py +++ b/Pythonwin/pywin/scintilla/view.py @@ -480,7 +480,7 @@ def list2dict(l): # extra attributes of win32ui objects if hasattr(ob, "_obj_"): try: - items_dict.update(list2dict(dir(ob._obj_))) + items_dict = list2dict(dir(ob._obj_)) except AttributeError: pass # object has no __dict__ @@ -650,8 +650,7 @@ def _GetObjectAtPos(self, pos=-1, bAllowCalls=0): left, right = self._GetWordSplit(pos, bAllowCalls) if left: # It is an attribute lookup # How is this for a hack! - namespace = sys.modules.copy() - namespace.update(__main__.__dict__) + namespace = sys.modules | __main__.__dict__ # Get the debugger's context. try: from pywin.framework import interact diff --git a/adodbapi/test/adodbapitest.py b/adodbapi/test/adodbapitest.py index 654369b7b..12b9ab5d6 100644 --- a/adodbapi/test/adodbapitest.py +++ b/adodbapi/test/adodbapitest.py @@ -1093,7 +1093,7 @@ def getConnection(self): return self.conn def getAnotherConnection(self, addkeys=None): - keys = dict(config.connStrSQLServer[1]) + keys = config.connStrSQLServer[1].copy() if addkeys: keys.update(addkeys) return config.dbSqlServerconnect(*config.connStrSQLServer[0], **keys) @@ -1273,7 +1273,7 @@ def getConnection(self): return self.conn def getAnotherConnection(self, addkeys=None): - keys = dict(config.connStrMySql[1]) + keys = config.connStrMySql[1].copy() if addkeys: keys.update(addkeys) return config.dbMySqlconnect(*config.connStrMySql[0], **keys) @@ -1339,7 +1339,7 @@ def getConnection(self): return self.conn def getAnotherConnection(self, addkeys=None): - keys = dict(config.connStrPostgres[1]) + keys = config.connStrPostgres[1].copy() if addkeys: keys.update(addkeys) return config.dbPostgresConnect(*config.connStrPostgres[0], **keys) diff --git a/isapi/install.py b/isapi/install.py index aa861bbfc..c9386a99a 100644 --- a/isapi/install.py +++ b/isapi/install.py @@ -760,8 +760,7 @@ def HandleCommandLine( # build a usage string if we don't have one. if not parser.get_usage(): - all_handlers = standard_arguments.copy() - all_handlers.update(custom_arg_handlers) + all_handlers = standard_arguments | custom_arg_handlers parser.set_usage(build_usage(all_handlers)) # allow the user to use uninstall as a synonym for remove if it wasn't diff --git a/win32/Demos/win32netdemo.py b/win32/Demos/win32netdemo.py index 6c0a55776..6b8750ced 100644 --- a/win32/Demos/win32netdemo.py +++ b/win32/Demos/win32netdemo.py @@ -194,8 +194,7 @@ def SetInfo(userName=None): userName = win32api.GetUserName() oldData = win32net.NetUserGetInfo(server, userName, 3) try: - d = oldData.copy() - d["usr_comment"] = "Test comment" + d = oldData | {"usr_comment": "Test comment"} win32net.NetUserSetInfo(server, userName, 3, d) new = win32net.NetUserGetInfo(server, userName, 3)["usr_comment"] if str(new) != "Test comment": diff --git a/win32/test/test_win32cred.py b/win32/test/test_win32cred.py index 4b9fe08d7..840263bad 100644 --- a/win32/test/test_win32cred.py +++ b/win32/test/test_win32cred.py @@ -14,12 +14,10 @@ def setUp(self): } def create_dummy_cred(self): - cred = copy.deepcopy(self.dummy_cred) - cred.update( - { - "Persist": win32cred.CRED_PERSIST_SESSION, - } - ) + cred = copy.deepcopy(self.dummy_cred) | { + "Persist": win32cred.CRED_PERSIST_SESSION, + } + try: win32cred.CredWrite(cred, self.flags) except Exception as e: diff --git a/win32/test/test_win32file.py b/win32/test/test_win32file.py index 12ba8c31d..b0eee2e3b 100644 --- a/win32/test/test_win32file.py +++ b/win32/test/test_win32file.py @@ -556,8 +556,7 @@ class TestFindFiles(unittest.TestCase): def testIter(self): dir = os.path.join(os.getcwd(), "*") files = win32file.FindFilesW(dir) - set1 = set() - set1.update(files) + set1 = set(files) set2 = set() for file in win32file.FindFilesIterator(dir): set2.add(file) From bb2db9772d6d8535bb623d24379f8eb2c4bc5d82 Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 18 Oct 2024 19:30:01 -0400 Subject: [PATCH 3/6] PEP 616, string methods to remove prefixes and suffixes. --- Pythonwin/pywin/framework/intpyapp.py | 5 +++-- com/win32com/client/gencache.py | 1 + isapi/install.py | 5 ++--- setup.py | 4 +--- win32/scripts/VersionStamp/bulkstamp.py | 9 +++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Pythonwin/pywin/framework/intpyapp.py b/Pythonwin/pywin/framework/intpyapp.py index acbd34cc1..644b61888 100644 --- a/Pythonwin/pywin/framework/intpyapp.py +++ b/Pythonwin/pywin/framework/intpyapp.py @@ -3,6 +3,7 @@ import os import sys import traceback +from collections.abc import Iterable, Sequence import __main__ import commctrl @@ -268,7 +269,7 @@ def Activate(self): if frame.GetWindowPlacement()[1] == win32con.SW_SHOWMINIMIZED: frame.ShowWindow(win32con.SW_RESTORE) - def ProcessArgs(self, args, dde=None): + def ProcessArgs(self, args: Sequence[str], dde=None): # If we are going to talk to a remote app via DDE, then # activate it! if ( @@ -290,7 +291,7 @@ def ProcessArgs(self, args, dde=None): ).lower() i -= 1 # arg is /edit's parameter par = i < len(args) and args[i] or "MISSING" - if argType in ("/nodde", "/new", "-nodde", "-new"): + if argType in ("/nodde", "/new"): # Already handled pass elif argType.startswith("/goto:"): diff --git a/com/win32com/client/gencache.py b/com/win32com/client/gencache.py index 717f2a9c7..c445c38c6 100644 --- a/com/win32com/client/gencache.py +++ b/com/win32com/client/gencache.py @@ -97,6 +97,7 @@ def _LoadDicts(): arc_path = loader.archive dicts_path = os.path.join(win32com.__gen_path__, "dicts.dat") if dicts_path.startswith(arc_path): + # Remove the leading slash as well dicts_path = dicts_path[len(arc_path) + 1 :] else: # Hm. See below. diff --git a/isapi/install.py b/isapi/install.py index c9386a99a..c4c61fb76 100644 --- a/isapi/install.py +++ b/isapi/install.py @@ -626,7 +626,7 @@ def _PatchParamsModule(params, dll_name, file_must_exist=True): sm.Module = dll_name -def GetLoaderModuleName(mod_name, check_module=None): +def GetLoaderModuleName(mod_name: str, check_module=None): # find the name of the DLL hosting us. # By default, this is "_{module_base_name}.dll" if hasattr(sys, "frozen"): @@ -635,8 +635,7 @@ def GetLoaderModuleName(mod_name, check_module=None): base, ext = os.path.splitext(mod_name) path, base = os.path.split(base) # handle the common case of 'foo.exe'/'foow.exe' - if base.endswith("w"): - base = base[:-1] + base.removesuffix("w") # For py2exe, we have '_foo.dll' as the standard pyisapi loader - but # 'foo.dll' is what we use (it just delegates). # So no leading '_' on the installed name. diff --git a/setup.py b/setup.py index cf1c6f062..f8662a809 100644 --- a/setup.py +++ b/setup.py @@ -2024,9 +2024,7 @@ def convert_data_files(files: Iterable[str]): raise RuntimeError("No file '%s'" % file) files_use = (file,) for fname in files_use: - path_use = os.path.dirname(fname) - if path_use.startswith("com\\"): - path_use = path_use[4:] + path_use = os.path.dirname(fname).removeprefix("com\\") ret.append((path_use, (fname,))) return ret diff --git a/win32/scripts/VersionStamp/bulkstamp.py b/win32/scripts/VersionStamp/bulkstamp.py index 89be6347d..0500745bf 100644 --- a/win32/scripts/VersionStamp/bulkstamp.py +++ b/win32/scripts/VersionStamp/bulkstamp.py @@ -33,7 +33,7 @@ import fnmatch import os import sys -from collections.abc import Mapping +from collections.abc import Iterable, Mapping from optparse import Values try: @@ -51,7 +51,9 @@ ] -def walk(vars: Mapping[str, str], debug, descriptions, dirname, names) -> int: +def walk( + vars: Mapping[str, str], debug, descriptions, dirname, names: Iterable[str] +) -> int: """Returns the number of stamped files.""" numStamped = 0 for name in names: @@ -60,8 +62,7 @@ def walk(vars: Mapping[str, str], debug, descriptions, dirname, names) -> int: # Handle the "_d" thing. pathname = os.path.join(dirname, name) base, ext = os.path.splitext(name) - if base.endswith("_d"): - name = base[:-2] + ext + name = base.removesuffix("_d") + ext is_dll = ext.lower() != ".exe" if os.path.normcase(name) in descriptions: description = descriptions[os.path.normcase(name)] From 6a858dcbe93592a21ce1d71d8103869b244de67a Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 18 Oct 2024 19:40:01 -0400 Subject: [PATCH 4/6] Test in dev mode --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ba28701bd..e19b31ca1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,7 +46,7 @@ jobs: - name: Run tests # Run the tests directly from the source dir so support files (eg, .wav files etc) # can be found - they aren't installed into the Python tree. - run: python pywin32_testall.py -v -skip-adodbapi + run: python -X dev pywin32_testall.py -v -skip-adodbapi - name: Build wheels run: | From 3a9e6cb8ce72a23a6493568cbfb742e61267767a Mon Sep 17 00:00:00 2001 From: Avasam Date: Fri, 18 Oct 2024 20:34:24 -0400 Subject: [PATCH 5/6] Run pycln --- Pythonwin/pywin/framework/intpyapp.py | 2 +- adodbapi/test/adodbapitest.py | 9 --------- adodbapi/test/dbapi20.py | 1 - 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/Pythonwin/pywin/framework/intpyapp.py b/Pythonwin/pywin/framework/intpyapp.py index 644b61888..60f7eeda9 100644 --- a/Pythonwin/pywin/framework/intpyapp.py +++ b/Pythonwin/pywin/framework/intpyapp.py @@ -3,7 +3,7 @@ import os import sys import traceback -from collections.abc import Iterable, Sequence +from collections.abc import Sequence import __main__ import commctrl diff --git a/adodbapi/test/adodbapitest.py b/adodbapi/test/adodbapitest.py index 12b9ab5d6..6cd9abf48 100644 --- a/adodbapi/test/adodbapitest.py +++ b/adodbapi/test/adodbapitest.py @@ -27,7 +27,6 @@ import decimal import random import string -import sys import time import unittest @@ -37,14 +36,6 @@ import adodbapi import adodbapi.apibase as api -try: - import adodbapi.ado_consts as ado_consts -except ImportError: # we are doing a shortcut import as a module -- so - try: - import ado_consts - except ImportError: - from adodbapi import ado_consts - def randomstring(length): return "".join([random.choice(string.ascii_letters) for n in range(32)]) diff --git a/adodbapi/test/dbapi20.py b/adodbapi/test/dbapi20.py index 441f0bd3c..eccfb5c86 100644 --- a/adodbapi/test/dbapi20.py +++ b/adodbapi/test/dbapi20.py @@ -14,7 +14,6 @@ __version__ = "$Revision: 1.15.0 $"[11:-2] __author__ = "Stuart Bishop " -import sys import time import unittest From e19c1ceee36286fe43f7169275894e0ad52a2af6 Mon Sep 17 00:00:00 2001 From: Avasam Date: Sun, 20 Oct 2024 17:07:03 -0400 Subject: [PATCH 6/6] Update more sys.version_info --- setup.py | 2 +- win32/test/test_win32gui.py | 18 +++--------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/setup.py b/setup.py index f8662a809..180ceb2cb 100644 --- a/setup.py +++ b/setup.py @@ -381,7 +381,7 @@ def _why_cant_build_extension(self, ext): # axdebug fails to build on 3.11 due to Python "frame" objects changing. # This could be fixed, but is almost certainly not in use any more, so # just skip it. - if ext.name == "axdebug" and sys.version_info > (3, 10): + if ext.name == "axdebug" and sys.version_info >= (3, 11): return "AXDebug no longer builds on 3.11 and up" include_dirs = self.compiler.include_dirs + os.environ.get("INCLUDE", "").split( diff --git a/win32/test/test_win32gui.py b/win32/test/test_win32gui.py index bc91a7add..b71ee8ad5 100644 --- a/win32/test/test_win32gui.py +++ b/win32/test/test_win32gui.py @@ -102,11 +102,7 @@ def test_enumwindows(self): ): for data in self.type_data_set: self.assertRaises(TypeError, win32gui.EnumWindows, func, data) - if sys.version_info >= (3, 10): - for func in ( - self.enum_callback, - self.enum_callback_sle, - ): + if sys.version_info >= (3, 10): self.assertRaises( TypeError, win32gui.EnumWindows, func, self.enum_callback, 2.718282 ) @@ -136,11 +132,7 @@ def test_enumchildwindows(self): self.assertRaises( TypeError, win32gui.EnumChildWindows, None, func, data ) - if sys.version_info >= (3, 10): - for func in ( - self.enum_callback, - self.enum_callback_sle, - ): + if sys.version_info >= (3, 10): self.assertRaises( TypeError, win32gui.EnumChildWindows, @@ -197,11 +189,7 @@ def test_enumdesktopwindows(self): self.assertRaises( TypeError, win32gui.EnumDesktopWindows, 0, func, data ) - if sys.version_info >= (3, 10): - for func in ( - self.enum_callback, - self.enum_callback_sle, - ): + if sys.version_info >= (3, 10): for desktop in desktops: self.assertRaises( TypeError, win32gui.EnumDesktopWindows, 0, func, 2.718282