diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3478f91f7d..f81e09133b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,7 +17,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12-dev"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev"] architecture: ["x64", "x86"] steps: @@ -30,6 +30,7 @@ jobs: architecture: ${{ matrix.architecture }} cache: pip cache-dependency-path: .github/workflows/main.yml + check-latest: true - name: Setup environment run: | @@ -66,8 +67,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12-dev"] - + python-version: ["3.10", "3.11", "3.12", "3.13-dev"] steps: - uses: actions/checkout@v4 @@ -78,6 +78,7 @@ jobs: architecture: "x64" cache: pip cache-dependency-path: .github/workflows/main.yml + check-latest: true - name: Setup Environment run: | @@ -137,6 +138,7 @@ jobs: python-version: ${{ matrix.python-version }} cache: pip cache-dependency-path: .github/workflows/main.yml + check-latest: true - run: pip install types-regex types-setuptools mypy==1.9 - run: mypy . --python-version=${{ matrix.python-version }} @@ -145,7 +147,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -153,6 +155,7 @@ jobs: python-version: ${{ matrix.python-version }} cache: pip cache-dependency-path: .github/workflows/main.yml + check-latest: true # 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 diff --git a/.gitignore b/.gitignore index dc0875a8f6..ca30ad34de 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,4 @@ venv.bak/ .idea/ .DS_Store vagrant_helpers/bootstrap-salt.ps1 +.vscode/ diff --git a/CHANGES.txt b/CHANGES.txt index 7fc7f8c941..c45cdaff69 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -16,6 +16,8 @@ Coming in build 307, as yet unreleased ### pywin32 * The `EvtSubscribe_push` demo now actually demonstrates the callback action and the event context being filled. (#2281, @Avasam) +* Add RealGetWindowClass (#2299, @CristiFati) +* Make it compile on Python 3.13 (#2260, @clin1234) * Fixed accidentally trying to raise a `str` instead of an `Exception` in (#2270, @Avasam) * `Pythonwin/pywin/debugger/debugger.py` * `Pythonwin/pywin/framework/dlgappcore.py` diff --git a/Pythonwin/win32thread.cpp b/Pythonwin/win32thread.cpp index c26e8c7a1e..f2b04f43c0 100644 --- a/Pythonwin/win32thread.cpp +++ b/Pythonwin/win32thread.cpp @@ -164,7 +164,7 @@ unsigned int ThreadWorkerEntryPoint(LPVOID lpvoid) { CPythonWinThread *pThis = (CPythonWinThread *)lpvoid; CEnterLeavePython _celp; - PyObject *result = PyEval_CallObject(pThis->obFunc, pThis->obArgs); + PyObject *result = PyObject_CallObject(pThis->obFunc, pThis->obArgs); if (result == NULL) { if (PyErr_Occurred() == PyExc_SystemExit) PyErr_Clear(); diff --git a/Pythonwin/win32uimodule.cpp b/Pythonwin/win32uimodule.cpp index 25cb0d8af1..e5e03878fa 100644 --- a/Pythonwin/win32uimodule.cpp +++ b/Pythonwin/win32uimodule.cpp @@ -600,7 +600,7 @@ static DWORD FilterFunc(DWORD dwExceptionCode) return (dwRet); } -PyObject *gui_call_object(PyObject *themeth, PyObject *thearglst) { return PyEval_CallObject(themeth, thearglst); } +PyObject *gui_call_object(PyObject *themeth, PyObject *thearglst) { return PyObject_CallObject(themeth, thearglst); } void gui_print_error(void) { diff --git a/adodbapi/adodbapi.py b/adodbapi/adodbapi.py index 0f84a890a6..4e85bc525b 100644 --- a/adodbapi/adodbapi.py +++ b/adodbapi/adodbapi.py @@ -418,9 +418,8 @@ def __setattr__(self, name, value): f"paramstyle={value!r} not in:{api.accepted_paramstyles!r}", ) elif name == "variantConversions": - value = copy.copy( - value - ) # make a new copy -- no changes in the default, please + # make a new copy -- no changes in the default, please + value = copy.copy(value) object.__setattr__(self, name, value) def __getattr__(self, item): diff --git a/adodbapi/apibase.py b/adodbapi/apibase.py index 3231f78ac3..749c378d38 100644 --- a/adodbapi/apibase.py +++ b/adodbapi/apibase.py @@ -5,11 +5,15 @@ * http://sourceforge.net/projects/adodbapi """ +from __future__ import annotations + import datetime import decimal import numbers import sys import time +from collections.abc import Callable, Iterable, Mapping +from typing import Dict # noinspection PyUnresolvedReferences from . import ado_consts as adc @@ -461,20 +465,25 @@ def convert_to_python(variant, func): # convert DB value into Python value return func(variant) # call the appropriate conversion function -class MultiMap(dict): # builds a dictionary from {(sequence,of,keys) : function} - """A dictionary of ado.type : function -- but you can set multiple items by passing a sequence of keys""" +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""" # useful for defining conversion functions for groups of similar data types. - def __init__(self, aDict): - for k, v in list(aDict.items()): + def __init__(self, aDict: Mapping[Iterable[int] | int, Callable[[object], object]]): + for k, v in aDict.items(): self[k] = v # we must call __setitem__ - def __setitem__(self, adoType, cvtFn): - "set a single item, or a whole sequence of items" - try: # user passed us a sequence, set them individually + def __setitem__( + self, adoType: Iterable[int] | int, cvtFn: Callable[[object], object] + ): + "set a single item, or a whole iterable of items" + if isinstance(adoType, Iterable): + # user passed us an iterable, set them individually for type in adoType: dict.__setitem__(self, type, cvtFn) - except TypeError: # a single value fails attempt to iterate + else: dict.__setitem__(self, adoType, cvtFn) diff --git a/adodbapi/setup.py b/adodbapi/setup.py index f6b274661d..ac796d371b 100644 --- a/adodbapi/setup.py +++ b/adodbapi/setup.py @@ -3,20 +3,6 @@ Adodbapi can be run on CPython 3.5 and later. """ -CLASSIFIERS = """\ -Development Status :: 5 - Production/Stable -Intended Audience :: Developers -License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) -Operating System :: Microsoft :: Windows -Operating System :: POSIX :: Linux -Programming Language :: Python -Programming Language :: Python :: 3 -Programming Language :: SQL -Topic :: Software Development -Topic :: Software Development :: Libraries :: Python Modules -Topic :: Database -""" - NAME = "adodbapi" MAINTAINER = "Vernon Cole" MAINTAINER_EMAIL = "vernondcole@gmail.com" @@ -25,7 +11,19 @@ ) URL = "http://sourceforge.net/projects/adodbapi" LICENSE = "LGPL" -CLASSIFIERS = filter(None, CLASSIFIERS.split("\n")) +CLASSIFIERS = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: SQL", + "Topic :: Software Development", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Database", +] AUTHOR = "Henrik Ekelund, Vernon Cole, et.al." AUTHOR_EMAIL = "vernondcole@gmail.com" PLATFORMS = ["Windows", "Linux"] diff --git a/adodbapi/test/adodbapitest.py b/adodbapi/test/adodbapitest.py index b8d98593db..fc5babf9bc 100644 --- a/adodbapi/test/adodbapitest.py +++ b/adodbapi/test/adodbapitest.py @@ -1546,17 +1546,29 @@ def testTimestamp(self): assert t1 < obj < t2, obj -suites = [unittest.makeSuite(TestPythonDateTimeConverter, "test")] +suites = [ + unittest.defaultTestLoader.loadTestsFromModule(TestPythonDateTimeConverter, "test") +] if config.doTimeTest: - suites.append(unittest.makeSuite(TestPythonTimeConverter, "test")) + suites.append( + unittest.defaultTestLoader.loadTestsFromModule(TestPythonTimeConverter, "test") + ) if config.doAccessTest: - suites.append(unittest.makeSuite(TestADOwithAccessDB, "test")) + suites.append( + unittest.defaultTestLoader.loadTestsFromModule(TestADOwithAccessDB, "test") + ) if config.doSqlServerTest: - suites.append(unittest.makeSuite(TestADOwithSQLServer, "test")) + suites.append( + unittest.defaultTestLoader.loadTestsFromModule(TestADOwithSQLServer, "test") + ) if config.doMySqlTest: - suites.append(unittest.makeSuite(TestADOwithMySql, "test")) + suites.append( + unittest.defaultTestLoader.loadTestsFromModule(TestADOwithMySql, "test") + ) if config.doPostgresTest: - suites.append(unittest.makeSuite(TestADOwithPostgres, "test")) + suites.append( + unittest.defaultTestLoader.loadTestsFromModule(TestADOwithPostgres, "test") + ) class cleanup_manager: diff --git a/build_all.bat b/build_all.bat index 616e03a5e1..536aee00dc 100644 --- a/build_all.bat +++ b/build_all.bat @@ -22,6 +22,10 @@ py -3.12-32 setup.py -q build @if errorlevel 1 goto failed py -3.12 setup.py -q build @if errorlevel 1 goto failed +py -3.13-32 setup.py -q build +@if errorlevel 1 goto failed +py -3.13 setup.py -q build +@if errorlevel 1 goto failed goto xit :failed diff --git a/com/win32com/src/oleargs.cpp b/com/win32com/src/oleargs.cpp index 0187949f6f..33c925987f 100644 --- a/com/win32com/src/oleargs.cpp +++ b/com/win32com/src/oleargs.cpp @@ -1671,7 +1671,7 @@ BOOL PyCom_MakeOlePythonCall(PyObject *handler, DISPPARAMS FAR *params, VARIANT argList = Py_BuildValue("OO", varArgs, addnlArgs); Py_DECREF(varArgs); } - PyObject *result = PyEval_CallObject(handler, argList); + PyObject *result = PyObject_CallObject(handler, argList); Py_XDECREF(argList); Py_XDECREF(namedArgList); // handlers reference cleaned up by virtual manager. diff --git a/com/win32com/src/univgw.cpp b/com/win32com/src/univgw.cpp index aec11ad124..73616e48a3 100644 --- a/com/win32com/src/univgw.cpp +++ b/com/win32com/src/univgw.cpp @@ -98,7 +98,7 @@ static HRESULT univgw_dispatch(DWORD index, gw_object *_this, va_list argPtr) PyTuple_SET_ITEM(obArgs, 2, obArgPtr); // call the provided method - PyObject *result = PyEval_CallObjectWithKeywords(vtbl->dispatcher, obArgs, NULL); + PyObject *result = PyObject_CallObject(vtbl->dispatcher, obArgs); // done with the arguments and the contained objects Py_DECREF(obArgs); diff --git a/com/win32com/test/testIterators.py b/com/win32com/test/testIterators.py index fe336e54e4..260cce673c 100644 --- a/com/win32com/test/testIterators.py +++ b/com/win32com/test/testIterators.py @@ -132,7 +132,7 @@ def suite(): and issubclass(item, unittest.TestCase) and item != _BaseTestCase ): - suite.addTest(unittest.makeSuite(item)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(item)) return suite diff --git a/com/win32comext/shell/src/shell.cpp b/com/win32comext/shell/src/shell.cpp index 27d51f8d6f..b6d1337346 100644 --- a/com/win32comext/shell/src/shell.cpp +++ b/com/win32comext/shell/src/shell.cpp @@ -1068,7 +1068,7 @@ static int CALLBACK PyBrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LP #endif if (!args) goto done; - result = PyEval_CallObject(pc->fn, args); + result = PyObject_CallObject(pc->fn, args); // API says must return 0, but there might be a good reason. if (result && PyLong_Check(result)) rc = PyLong_AsLong(result); diff --git a/make_all.bat b/make_all.bat index 527b292add..638d6a1667 100644 --- a/make_all.bat +++ b/make_all.bat @@ -40,6 +40,10 @@ py -3.9 setup.py -q bdist_wininst --skip-build --target-version=3.12 py -3.12-32 setup.py -q bdist_wheel --skip-build py -3.12 setup.py -q bdist_wheel --skip-build +py -3.9 setup.py -q bdist_wininst --skip-build --target-version=3.13 +py -3.13-32 setup.py -q bdist_wheel --skip-build +py -3.13 setup.py -q bdist_wheel --skip-build + rem ARM64 builds - requires you to select: rem * "Visual C++ compilers and libraries for ARM64" rem * "Visual C++ for MFC for ARM64" @@ -47,6 +51,7 @@ rem from "Individual Components" in VS setup. py -3.10 setup.py -q build_ext --plat-name win-arm64 build --plat-name win-arm64 bdist_wheel --plat-name win-arm64 py -3.11 setup.py -q build_ext --plat-name win-arm64 build --plat-name win-arm64 bdist_wheel --plat-name win-arm64 py -3.12 setup.py -q build_ext --plat-name win-arm64 build --plat-name win-arm64 bdist_wheel --plat-name win-arm64 +py -3.13 setup.py -q build_ext --plat-name win-arm64 build --plat-name win-arm64 bdist_wheel --plat-name win-arm64 @goto xit :couldnt_rm diff --git a/mypy.ini b/mypy.ini index eb8e23ae9e..a93198a2f6 100644 --- a/mypy.ini +++ b/mypy.ini @@ -40,8 +40,6 @@ exclude = (?x)( | ^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 separately - | ^adodbapi/ ; TODO: Ignoring non-public APIs until all public API is typed | ([Tt]est|[Dd]emos?)/ ) diff --git a/pyrightconfig.json b/pyrightconfig.json index 356d749bae..bc3c26fbc0 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -16,8 +16,6 @@ "**/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 diff --git a/setup.py b/setup.py index a55cbef77c..6ce1ed77de 100644 --- a/setup.py +++ b/setup.py @@ -2207,6 +2207,7 @@ def convert_optional_data_files(files): "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", ] diff --git a/win32/src/PyTime.cpp b/win32/src/PyTime.cpp index 502ed8787a..2110ef5b6c 100644 --- a/win32/src/PyTime.cpp +++ b/win32/src/PyTime.cpp @@ -273,7 +273,7 @@ PyObject *PyWin_NewTime(PyObject *timeOb) if (method == NULL) PyErr_Clear(); else { - timeOb = PyEval_CallObject(method, NULL); + timeOb = PyObject_CallObject(method, NULL); Py_DECREF(method); if (!timeOb) return NULL; diff --git a/win32/src/timermodule.cpp b/win32/src/timermodule.cpp index cc47889188..f06e8a4e6c 100644 --- a/win32/src/timermodule.cpp +++ b/win32/src/timermodule.cpp @@ -34,7 +34,7 @@ VOID CALLBACK py_win32_timer_callback(HWND hwnd, UINT msg, UINT_PTR timer_id, DW // the callback itself removes the function from the map. Py_INCREF(callback_function); PyObject *callback_args = Py_BuildValue("(Ok)", py_timer_id, time); - PyObject *result = PyEval_CallObject(callback_function, callback_args); + PyObject *result = PyObject_CallObject(callback_function, callback_args); if (!result) { // Is this necessary, or will python already have flagged diff --git a/win32/src/win32gui.i b/win32/src/win32gui.i index a59ae28232..518e84cf9d 100644 --- a/win32/src/win32gui.i +++ b/win32/src/win32gui.i @@ -5998,26 +5998,49 @@ PyGetScrollInfo (PyObject *self, PyObject *args) %native (GetScrollInfo) PyGetScrollInfo; %{ +#define MAX_CHARS 0x100 + // @pyswig string|GetClassName|Retrieves the name of the class to which the specified window belongs. static PyObject * PyGetClassName(PyObject *self, PyObject *args) { - HWND hwnd; - PyObject *obhwnd; - TCHAR buf[256]; - // @pyparm |hwnd||The handle to the window - if (!PyArg_ParseTuple(args, "O:GetClassName", &obhwnd)) - return NULL; - if (!PyWinObject_AsHANDLE(obhwnd, (HANDLE *)&hwnd)) - return NULL; - // don't bother with lock - no callback possible. - int nchars = GetClassName(hwnd, buf, sizeof buf/sizeof buf[0]); - if (nchars==0) - return PyWin_SetAPIError("GetClassName"); - return PyWinObject_FromTCHAR(buf, nchars); + HWND hwnd; + PyObject *obhwnd; + TCHAR buf[MAX_CHARS]; + // @pyparm |hwnd||The handle to the window + if (!PyArg_ParseTuple(args, "O:GetClassName", &obhwnd)) + return NULL; + if (!PyWinObject_AsHANDLE(obhwnd, (HANDLE*)&hwnd)) + return NULL; + // don't bother with lock - no callback possible. + int nchars = GetClassName(hwnd, buf, MAX_CHARS); + if (nchars == 0) + return PyWin_SetAPIErrorOrReturnNone("GetClassName"); + return PyWinObject_FromTCHAR(buf, nchars); } + +// @pyswig string|RealGetWindowClass|Retrieves the name of the class to which the specified window belongs. +static PyObject * +PyRealGetWindowClass(PyObject *self, PyObject *args) +{ + HWND hwnd; + PyObject *obhwnd; + TCHAR buf[MAX_CHARS]; + // @pyparm |hwnd||The handle to the window + if (!PyArg_ParseTuple(args, "O:RealGetWindowClass", &obhwnd)) + return NULL; + if (!PyWinObject_AsHANDLE(obhwnd, (HANDLE*)&hwnd)) + return NULL; + // don't bother with lock - no callback possible. + UINT nchars = RealGetWindowClass(hwnd, buf, MAX_CHARS); + if (nchars == 0) + return PyWin_SetAPIErrorOrReturnNone("RealGetWindowClass"); + return PyWinObject_FromTCHAR(buf, nchars); +} + %} %native (GetClassName) PyGetClassName; +%native (RealGetWindowClass) PyRealGetWindowClass; // @pyswig int|WindowFromPoint|Retrieves a handle to the window that contains the specified point. // @pyparm (int, int)|point||The point. @@ -6065,7 +6088,7 @@ static int CALLBACK PySortFunc( assert(!PyErr_Occurred()); args = Py_BuildValue("llO", lParam1, lParam2, pc->data); if (!args) goto done; - result = PyEval_CallObject(pc->fn, args); + result = PyObject_CallObject(pc->fn, args); // API says must return 0, but there might be a good reason. if (!result) goto done; if (!PyLong_Check(result)) { diff --git a/win32/src/win32pdhmodule.cpp b/win32/src/win32pdhmodule.cpp index 5631da52a7..8d3c7d7009 100644 --- a/win32/src/win32pdhmodule.cpp +++ b/win32/src/win32pdhmodule.cpp @@ -977,7 +977,7 @@ PDH_STATUS __stdcall PyCounterPathCallback(DWORD_PTR dwArg) rc = ERROR_OUTOFMEMORY; } else { - result = PyEval_CallObject(pMy->func, args); + result = PyObject_CallObject(pMy->func, args); if (result == NULL) { PyErr_Print(); // *Don't* leave exception hanging rc = PDH_INVALID_DATA; diff --git a/win32/src/win32process.i b/win32/src/win32process.i index beeb85981a..331ed0f237 100644 --- a/win32/src/win32process.i +++ b/win32/src/win32process.i @@ -361,7 +361,7 @@ unsigned __stdcall ThreadEntryPoint( void *arg ) { CEnterLeavePython _celp; PythonThreadData *ptd = (PythonThreadData *)arg; - PyObject *pyrc = PyEval_CallObject(ptd->m_obFunc, ptd->m_obArgs); + PyObject *pyrc = PyObject_CallObject(ptd->m_obFunc, ptd->m_obArgs); delete ptd; if (pyrc==NULL) { fprintf(stderr, "Unhandled exception in beginthreadex created thread:\n"); diff --git a/win32/src/win32rasmodule.cpp b/win32/src/win32rasmodule.cpp index fe19ee0536..3ed8c33fe1 100644 --- a/win32/src/win32rasmodule.cpp +++ b/win32/src/win32rasmodule.cpp @@ -480,7 +480,7 @@ VOID CALLBACK PyRasDialFunc1(HRASCONN hrasconn, // handle to RAS connection PyObject *args = Py_BuildValue("Niiii", PyWinLong_FromHANDLE(hrasconn), unMsg, rascs, dwError, dwExtendedError); if (args == NULL) return; - PyObject *res = PyEval_CallObject(handler, args); + PyObject *res = PyObject_CallObject(handler, args); Py_DECREF(args); if (res == NULL) { PyErr_Print(); diff --git a/win32/test/test_win32gui.py b/win32/test/test_win32gui.py index c5754722c3..e61bae1b8e 100644 --- a/win32/test/test_win32gui.py +++ b/win32/test/test_win32gui.py @@ -209,5 +209,20 @@ def test_enumdesktopwindows(self): ) +class TestWindowProperties(unittest.TestCase): + def setUp(self): + self.class_functions = ( + win32gui.GetClassName, + win32gui.RealGetWindowClass, + ) + + def test_classname(self): + for func in self.class_functions: + self.assertRaises(pywintypes.error, func, 0) + wnd = win32gui.GetDesktopWindow() + for func in self.class_functions: + self.assertTrue(func(wnd)) + + if __name__ == "__main__": unittest.main()