diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 260d16047b..a0583c3a9a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -95,13 +95,52 @@ 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 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 + diff --git a/Pythonwin/pywin/Demos/app/basictimerapp.py b/Pythonwin/pywin/Demos/app/basictimerapp.py index af2f95490b..a932ae0604 100644 --- a/Pythonwin/pywin/Demos/app/basictimerapp.py +++ b/Pythonwin/pywin/Demos/app/basictimerapp.py @@ -8,7 +8,7 @@ import win32api import win32con import win32ui -from pywin.framework import cmdline, dlgappcore +from pywin.framework import dlgappcore class TimerAppDialog(dlgappcore.AppDialog): diff --git a/Pythonwin/pywin/Demos/app/customprint.py b/Pythonwin/pywin/Demos/app/customprint.py index 356073e5fb..442737b5bd 100644 --- a/Pythonwin/pywin/Demos/app/customprint.py +++ b/Pythonwin/pywin/Demos/app/customprint.py @@ -5,7 +5,6 @@ # This sample was contributed by Roger Burnham. -import win32api import win32con import win32ui from pywin.framework import app diff --git a/Pythonwin/pywin/Demos/app/dlgappdemo.py b/Pythonwin/pywin/Demos/app/dlgappdemo.py index 38659a62fc..efb50a627e 100644 --- a/Pythonwin/pywin/Demos/app/dlgappdemo.py +++ b/Pythonwin/pywin/Demos/app/dlgappdemo.py @@ -9,7 +9,7 @@ import sys import win32ui -from pywin.framework import app, dlgappcore +from pywin.framework import dlgappcore class TestDialogApp(dlgappcore.DialogApp): @@ -43,7 +43,7 @@ def write(self, str): win32ui.OutputDebug("dlgapp - no edit control! >>\n%s\n<<\n" % str) -app.AppBuilder = TestDialogApp +app = TestDialogApp() if __name__ == "__main__": import demoutils diff --git a/Pythonwin/pywin/Demos/app/dojobapp.py b/Pythonwin/pywin/Demos/app/dojobapp.py index d97ce5e2a8..77197f4c25 100644 --- a/Pythonwin/pywin/Demos/app/dojobapp.py +++ b/Pythonwin/pywin/Demos/app/dojobapp.py @@ -6,10 +6,9 @@ # pythonwin /app demos\dojobapp.py -import win32api import win32con import win32ui -from pywin.framework import app, dlgappcore +from pywin.framework import dlgappcore class DoJobAppDialog(dlgappcore.AppDialog): @@ -57,7 +56,7 @@ def __init__(self): DoJobDialogApp.__init__(self) -app.AppBuilder = DoJobDialogApp +app = DoJobDialogApp() def t(): diff --git a/Pythonwin/pywin/framework/app.py b/Pythonwin/pywin/framework/app.py index 9793aa1307..96cf45368b 100644 --- a/Pythonwin/pywin/framework/app.py +++ b/Pythonwin/pywin/framework/app.py @@ -1,4 +1,3 @@ -# App.py # Application stuff. # The application is responsible for managing the main frame window. # @@ -17,29 +16,6 @@ from . import scriptutils -## NOTE: App and AppBuild should NOT be used - instead, you should contruct your -## APP class manually whenever you like (just ensure you leave these 2 params None!) -## Whoever wants the generic "Application" should get it via win32iu.GetApp() - -# These are "legacy" -AppBuilder = None -App = None # default - if used, must end up a CApp derived class. - - -# Helpers that should one day be removed! -def AddIdleHandler(handler): - print( - "app.AddIdleHandler is deprecated - please use win32ui.GetApp().AddIdleHandler() instead." - ) - return win32ui.GetApp().AddIdleHandler(handler) - - -def DeleteIdleHandler(handler): - print( - "app.DeleteIdleHandler is deprecated - please use win32ui.GetApp().DeleteIdleHandler() instead." - ) - return win32ui.GetApp().DeleteIdleHandler(handler) - # Helper for writing a Window position by name, and later loading it. def SaveWindowSize(section, rect, state=""): @@ -169,10 +145,6 @@ def ExitInstance(self): if self._obj_: self._obj_.AttachObject(None) self._obj_ = None - global App - global AppBuilder - App = None - AppBuilder = None return 0 def HaveIdleHandler(self, handler): 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 518e2a1528..6b42de93bf 100644 --- a/Pythonwin/pywin/framework/editor/color/coloreditor.py +++ b/Pythonwin/pywin/framework/editor/color/coloreditor.py @@ -9,7 +9,10 @@ from pywin.debugger import dbgcon from pywin.framework.editor import GetEditorOption from pywin.framework.editor.document import EditorDocumentBase +from pywin.framework.editor.frame import EditorFrame +from pywin.framework.editor.template import EditorTemplateBase from pywin.scintilla import bindings, scintillacon +from pywin.scintilla.view import CScintillaView as SyntEditViewParent # WARNING: Duplicated in document.py and editor.py MSG_CHECK_EXTERNAL_FILE = win32con.WM_USER + 1999 @@ -36,9 +39,6 @@ def FinalizeViewCreation(self, view): self.GetDocTemplate().CheckIDLEMenus(view.idle) -SyntEditViewParent = pywin.scintilla.view.CScintillaView - - class SyntEditView(SyntEditViewParent): "A view of a SyntEdit. Obtains data from document." @@ -571,9 +571,6 @@ def FoldCollapseAllEvent(self, event): win32ui.DoWaitCursor(-1) -from pywin.framework.editor.frame import EditorFrame - - class SplitterFrame(EditorFrame): def OnCreate(self, cs): self.HookCommand(self.OnWindowSplit, win32ui.ID_WINDOW_SPLIT) @@ -584,9 +581,6 @@ def OnWindowSplit(self, id, code): return 1 -from pywin.framework.editor.template import EditorTemplateBase - - class SyntEditTemplate(EditorTemplateBase): def __init__( self, res=win32ui.IDR_TEXTTYPE, makeDoc=None, makeFrame=None, makeView=None @@ -644,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/intpyapp.py b/Pythonwin/pywin/framework/intpyapp.py index e70e7c09f0..71690a9abe 100644 --- a/Pythonwin/pywin/framework/intpyapp.py +++ b/Pythonwin/pywin/framework/intpyapp.py @@ -551,8 +551,4 @@ def OnHelpIndex(self, id, code): help.SelectAndRunHelpFile() -# As per the comments in app.py, this use is depreciated. -# app.AppBuilder = InteractivePythonApp - -# Now all we do is create the application thisApp = InteractivePythonApp() 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/startup.py b/Pythonwin/pywin/framework/startup.py index 4a7323225d..5496063e38 100644 --- a/Pythonwin/pywin/framework/startup.py +++ b/Pythonwin/pywin/framework/startup.py @@ -64,17 +64,3 @@ # Import the application module. __import__(moduleName) - -try: - win32ui.GetApp()._obj_ - # This worked - an app already exists - do nothing more -except (AttributeError, win32ui.error): - # This means either no app object exists at all, or the one - # that does exist does not have a Python class (ie, was created - # by the host .EXE). In this case, we do the "old style" init... - from . import app - - if app.AppBuilder is None: - raise TypeError("No application object has been registered") - - app.App = app.AppBuilder() diff --git a/Pythonwin/pywin/framework/stdin.py b/Pythonwin/pywin/framework/stdin.py index 93f5c02df2..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() + sys.stdin = Stdin() # type: ignore[assignment] # Not an actual TextIO 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 80ab6ec681..b7b5a81c61 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 win32api import win32con import win32ui @@ -34,7 +36,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 160962d415..d7f5cd4671 100644 --- a/Pythonwin/pywin/tools/browseProjects.py +++ b/Pythonwin/pywin/tools/browseProjects.py @@ -23,7 +23,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/Pythonwin/pywin/tools/regpy.py b/Pythonwin/pywin/tools/regpy.py index 11ad63af56..2dfe228d81 100644 --- a/Pythonwin/pywin/tools/regpy.py +++ b/Pythonwin/pywin/tools/regpy.py @@ -1,8 +1,7 @@ # (sort-of) Registry editor import commctrl -import dialog import win32con -import win32ui +from pywin.mfc import dialog class RegistryControl: diff --git a/com/win32com/client/__init__.py b/com/win32com/client/__init__.py index 36f2c1d778..4cc903d506 100644 --- a/com/win32com/client/__init__.py +++ b/com/win32com/client/__init__.py @@ -21,7 +21,6 @@ def __WrapDispatch( userName=None, resultCLSID=None, typeinfo=None, - UnicodeToString=None, clsctx=pythoncom.CLSCTX_SERVER, WrapperClass=None, ): @@ -29,7 +28,6 @@ def __WrapDispatch( Helper function to return a makepy generated class for a CLSID if it exists, otherwise cope by using CDispatch. """ - assert UnicodeToString is None, "this is deprecated and will go away" if resultCLSID is None: try: typeinfo = dispatch.GetTypeInfo() @@ -110,11 +108,9 @@ def Dispatch( userName=None, resultCLSID=None, typeinfo=None, - UnicodeToString=None, clsctx=pythoncom.CLSCTX_SERVER, ): """Creates a Dispatch based COM object.""" - assert UnicodeToString is None, "this is deprecated and will go away" dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch, userName, clsctx) return __WrapDispatch(dispatch, userName, resultCLSID, typeinfo, clsctx=clsctx) @@ -125,11 +121,9 @@ def DispatchEx( userName=None, resultCLSID=None, typeinfo=None, - UnicodeToString=None, clsctx=None, ): """Creates a Dispatch based COM object on a specific machine.""" - assert UnicodeToString is None, "this is deprecated and will go away" # If InProc is registered, DCOM will use it regardless of the machine name # (and regardless of the DCOM config for the object.) So unless the user # specifies otherwise, we exclude inproc apps when a remote machine is used. @@ -157,11 +151,8 @@ class CDispatch(dynamic.CDispatch): if/when possible. """ - def _wrap_dispatch_( - self, ob, userName=None, returnCLSID=None, UnicodeToString=None - ): - assert UnicodeToString is None, "this is deprecated and will go away" - return Dispatch(ob, userName, returnCLSID, None) + def _wrap_dispatch_(self, ob, userName=None, returnCLSID=None): + return Dispatch(ob, userName, returnCLSID) def __dir__(self): return dynamic.CDispatch.__dir__(self) diff --git a/com/win32com/client/build.py b/com/win32com/client/build.py index 1035ecdff9..8318b88949 100644 --- a/com/win32com/client/build.py +++ b/com/win32com/client/build.py @@ -69,9 +69,7 @@ class NotSupportedException(Exception): pythoncom.VT_VOID, ] -NoTranslateMap = {} -for v in NoTranslateTypes: - NoTranslateMap[v] = None +NoTranslateMap = set(NoTranslateTypes) class MapEntry: diff --git a/com/win32com/client/dynamic.py b/com/win32com/client/dynamic.py index 51d4702f8a..18dd751bbb 100644 --- a/com/win32com/client/dynamic.py +++ b/com/win32com/client/dynamic.py @@ -126,10 +126,8 @@ def Dispatch( userName=None, createClass=None, typeinfo=None, - UnicodeToString=None, clsctx=pythoncom.CLSCTX_SERVER, ): - assert UnicodeToString is None, "this is deprecated and will go away" IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch, userName, clsctx) if createClass is None: createClass = CDispatch @@ -181,11 +179,9 @@ def DumbDispatch( IDispatch, userName=None, createClass=None, - UnicodeToString=None, clsctx=pythoncom.CLSCTX_SERVER, ): "Dispatch with no type info" - assert UnicodeToString is None, "this is deprecated and will go away" IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch, userName, clsctx) if createClass is None: createClass = CDispatch @@ -193,10 +189,7 @@ def DumbDispatch( class CDispatch: - def __init__( - self, IDispatch, olerepr, userName=None, UnicodeToString=None, lazydata=None - ): - assert UnicodeToString is None, "this is deprecated and will go away" + def __init__(self, IDispatch, olerepr, userName=None, lazydata=None): if userName is None: userName = "" self.__dict__["_oleobj_"] = IDispatch @@ -370,10 +363,12 @@ def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *ar return self._get_good_object_(result, user, resultCLSID) def _wrap_dispatch_( - self, ob, userName=None, returnCLSID=None, UnicodeToString=None + self, + ob, + userName=None, + returnCLSID=None, ): # Given a dispatch object, wrap it in a class - assert UnicodeToString is None, "this is deprecated and will go away" return Dispatch(ob, userName) def _get_good_single_object_(self, ob, userName=None, ReturnCLSID=None): 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/client/genpy.py b/com/win32com/client/genpy.py index d23cd3079f..a45791ae35 100644 --- a/com/win32com/client/genpy.py +++ b/com/win32com/client/genpy.py @@ -877,9 +877,7 @@ def __init__( sourceFilename, progressObject, bBuildHidden=1, - bUnicodeToString=None, ): - assert bUnicodeToString is None, "this is deprecated and will go away" self.bHaveWrittenDispatchBaseClass = 0 self.bHaveWrittenCoClassBaseClass = 0 self.bHaveWrittenEventBaseClass = 0 diff --git a/com/win32com/client/makepy.py b/com/win32com/client/makepy.py index 5bcea6c830..53ff7e1c71 100644 --- a/com/win32com/client/makepy.py +++ b/com/win32com/client/makepy.py @@ -240,11 +240,9 @@ def GenerateFromTypeLibSpec( file=None, verboseLevel=None, progressInstance=None, - bUnicodeToString=None, bForDemand=bForDemandDefault, bBuildHidden=1, ): - assert bUnicodeToString is None, "this is deprecated and will go away" if verboseLevel is None: verboseLevel = 0 # By default, we use no gui and no verbose level! @@ -338,9 +336,8 @@ def GenerateFromTypeLibSpec( def GenerateChildFromTypeLibSpec( - child, typelibInfo, verboseLevel=None, progressInstance=None, bUnicodeToString=None + child, typelibInfo, verboseLevel=None, progressInstance=None ): - assert bUnicodeToString is None, "this is deprecated and will go away" if verboseLevel is None: verboseLevel = ( 0 # By default, we use no gui, and no verbose level for the children. diff --git a/com/win32com/demos/excelAddin.py b/com/win32com/demos/excelAddin.py index 8229477a8c..18a76eb8be 100644 --- a/com/win32com/demos/excelAddin.py +++ b/com/win32com/demos/excelAddin.py @@ -51,7 +51,6 @@ import pythoncom from win32com import universal from win32com.client import Dispatch, DispatchWithEvents, constants, gencache -from win32com.server.exception import COMException # Support for COM objects we use. gencache.EnsureModule( diff --git a/com/win32com/demos/outlookAddin.py b/com/win32com/demos/outlookAddin.py index 58f2f69878..244d5b325e 100644 --- a/com/win32com/demos/outlookAddin.py +++ b/com/win32com/demos/outlookAddin.py @@ -30,7 +30,6 @@ import pythoncom from win32com import universal from win32com.client import DispatchWithEvents, constants, gencache -from win32com.server.exception import COMException # Support for COM objects we use. gencache.EnsureModule( diff --git a/com/win32com/demos/trybag.py b/com/win32com/demos/trybag.py index 3d3918b6c2..07f0672fcb 100644 --- a/com/win32com/demos/trybag.py +++ b/com/win32com/demos/trybag.py @@ -1,5 +1,6 @@ import pythoncom -from win32com.server import exception, util +from win32com.server import util +from win32com.server.exception import COMException VT_EMPTY = pythoncom.VT_EMPTY @@ -18,7 +19,7 @@ def Read(self, propName, varType, errorLog): hr = 0x80070057 exc = pythoncom.com_error(0, "Bag.Read", "no such item", None, 0, hr) errorLog.AddError(propName, exc) - raise exception.Exception(scode=hr) + raise COMException(scode=hr) return self.data[propName] def Write(self, propName, value): @@ -31,7 +32,7 @@ class Target: _com_interfaces_ = [pythoncom.IID_IPersist, pythoncom.IID_IPersistPropertyBag] def GetClassID(self): - raise exception.Exception(scode=0x80004005) # E_FAIL + raise COMException(scode=0x80004005) # E_FAIL def InitNew(self): pass @@ -41,7 +42,7 @@ def Load(self, bag, log): print(bag.Read("prop2", VT_EMPTY, log)) try: print(bag.Read("prop3", VT_EMPTY, log)) - except exception.Exception: + except COMException: pass def Save(self, bag, clearDirty, saveAllProps): diff --git a/com/win32com/makegw/makegw.py b/com/win32com/makegw/makegw.py index 44050bcfc1..d99fa540a2 100644 --- a/com/win32com/makegw/makegw.py +++ b/com/win32com/makegw/makegw.py @@ -3,9 +3,9 @@ This module will generate a C++/Python binding for a specific COM interface. - At this stage, no command line interface exists. You must start Python, - import this module, change to the directory where the generated code should - be written, and run the public function. + Can be run from command line (passing required arguments) or the old way + (start Python, import this module, change to the directory where the generated code + should be written, and run the public function). This module is capable of generating both 'Interfaces' (ie, Python client side support for the interface) and 'Gateways' (ie, Python @@ -46,13 +46,19 @@ """ +import argparse +import os import re from . import makegwparse def make_framework_support( - header_file_name, interface_name, bMakeInterface=1, bMakeGateway=1 + header_file_name, + interface_name, + bMakeInterface=1, + bMakeGateway=1, + output_directory=None, ): """Generate C++ code for a Python Interface and Gateway @@ -86,7 +92,12 @@ def make_framework_support( ifc_cpp_writer = _write_ifc_cpp gw_cpp_writer = _write_gw_cpp - fout = open("Py%s.cpp" % interface.name, "w") + fout = open( + os.path.join( + directory if directory else os.getcwd(), f"Py{interface.name}.cpp" + ), + "w", + ) try: fout.write( f"""\ @@ -110,7 +121,10 @@ def make_framework_support( gw_cpp_writer(fout, interface) finally: fout.close() - fout = open("Py%s.h" % interface.name, "w") + fout = open( + os.path.join(directory if directory else os.getcwd(), f"Py{interface.name}.h"), + "w", + ) try: fout.write( f"""\ @@ -319,7 +333,7 @@ def _write_ifc_cpp(f, interface): ) ) else: - f.write("\tPy_INCREF(Py_None);\n\treturn Py_None;\n") + f.write("\tPy_RETURN_NONE;\n") f.write("\n}\n\n") f.write("// @object Py%s|Description of the interface\n" % (name)) @@ -418,9 +432,7 @@ def _write_gw_cpp(f, interface): // --------------------------------------------------- // // Gateway Implementation -""".format( - name=name, gname=gname, base_name=base_name - ) +""" ) for method in interface.methods: @@ -566,3 +578,63 @@ def test(): # make_framework_support("d:\\msdev\\include\\objidl.h", "IEnumSTATSTG") +# python -m com.win32com.makegw.makegw -f "C:\Windows Kits\10\Include\10.0.19041.0\um\ShObjIdl_core.h" -n IFolderView1 -o com\win32comext\shell\src + + +def parse_arguments(): + parser = argparse.ArgumentParser(description="COM interface wrapper generator") + parser.add_argument( + "--header_file", "-f", required=True, help="header file (system) to parse" + ) + parser.add_argument( + "--interface_name", "-n", required=True, help="interface name to search for" + ) + parser.add_argument( + "--no_create_interface", + "-i", + action="store_false", + dest="create_interface", + help="do not generate interface code", + ) + parser.add_argument( + "--no_create_gateway", + "-g", + action="store_false", + dest="create_gateway", + help="do not generate gateway code", + ) + parser.add_argument( + "--output_directory", "-o", help="directory where to generate files" + ) + + args, unk = parser.parse_known_args() + if unk: + print(f"Warning: Ignoring unknown arguments: {unk}") + + if not args.header_file or not os.path.isfile(args.header_file): + parser.exit(status=-1, message="Invalid header file\n") + if not args.interface_name: + parser.exit(status=-1, message="Invalid interface name\n") + + return ( + args.header_file, + args.interface_name, + args.create_interface, + args.create_gateway, + args.output_directory or None, + ) + + +if __name__ == "__main__": + header_file, interface_name, create_interface, create_gateway, directory = ( + parse_arguments() + ) + if directory: + os.makedirs(directory, exist_ok=True) + make_framework_support( + header_file_name=header_file, + interface_name=interface_name, + bMakeInterface=create_interface, + bMakeGateway=create_gateway, + output_directory=directory, + ) diff --git a/com/win32com/makegw/makegwparse.py b/com/win32com/makegw/makegwparse.py index 63b4692b8a..a1dff12ab1 100644 --- a/com/win32com/makegw/makegwparse.py +++ b/com/win32com/makegw/makegwparse.py @@ -759,6 +759,14 @@ def _GetPythonTypeDesc(self): "LPITEMIDLIST": (ArgFormatterIDLIST, 0, 1), "LPCITEMIDLIST": (ArgFormatterIDLIST, 0, 1), "const ITEMIDLIST": (ArgFormatterIDLIST, 0, 1), + "PITEMID_CHILD": (ArgFormatterIDLIST, 1), + "const PITEMID_CHILD": (ArgFormatterIDLIST, 0), + "PCITEMID_CHILD": (ArgFormatterIDLIST, 0), + "PUITEMID_CHILD": (ArgFormatterIDLIST, 1), + "PCUITEMID_CHILD": (ArgFormatterIDLIST, 0), + "const PUITEMID_CHILD": (ArgFormatterIDLIST, 0), + "PCUITEMID_CHILD_ARRAY": (ArgFormatterIDLIST, 2), + "const PCUITEMID_CHILD_ARRAY": (ArgFormatterIDLIST, 2), } # Auto-add all the simple types @@ -796,11 +804,20 @@ class Argument: # -------------- -------- ------------ ------ regex = re.compile(r"/\* \[([^\]]*.*?)] \*/[ \t](.*[* ]+)(\w+)(\[ *])?[\),]") + @classmethod + def drop_rpc_metadata(cls, type_): + return " ".join(e for e in type_.split(" ") if not e.startswith("__RPC_")) + def __init__(self, good_interface_names): self.good_interface_names = good_interface_names - self.inout = self.name = self.type = None + self.inout = None + self.name = None + self.type = None + self.raw_type = None + self.unc_type = None self.const = 0 self.arrayDecl = 0 + self.indirectionLevel = 0 def BuildFromFile(self, file): """Parse and build my data from a file @@ -817,21 +834,17 @@ def BuildFromFile(self, file): self.inout = mo.group(1).split("][") typ = mo.group(2).strip() self.raw_type = typ - self.indirectionLevel = 0 if mo.group(4): # Has "[ ]" decl self.arrayDecl = 1 - try: - pos = typ.rindex("__RPC_FAR") - self.indirectionLevel = self.indirectionLevel + 1 - typ = typ[:pos].strip() - except ValueError: - pass + typ_no_rpc = self.drop_rpc_metadata(typ) + if typ != typ_no_rpc: + self.indirectionLevel += 1 - typ = typ.replace("__RPC_FAR", "") + typ = self.drop_rpc_metadata(typ) while 1: try: pos = typ.rindex("*") - self.indirectionLevel = self.indirectionLevel + 1 + self.indirectionLevel += 1 typ = typ[:pos].strip() except ValueError: break diff --git a/com/win32com/server/connect.py b/com/win32com/server/connect.py index f9ffcd29ae..6efe4f7111 100644 --- a/com/win32com/server/connect.py +++ b/com/win32com/server/connect.py @@ -8,7 +8,7 @@ import winerror from win32com import olectl -from .exception import Exception +from .exception import COMException # Methods implemented by the interfaces. IConnectionPointContainer_methods = ["EnumConnectionPoints", "FindConnectionPoint"] @@ -35,10 +35,10 @@ def __init__(self): # IConnectionPoint interfaces def EnumConnections(self): - raise Exception(winerror.E_NOTIMPL) + raise COMException(winerror.E_NOTIMPL) def GetConnectionInterface(self): - raise Exception(winerror.E_NOTIMPL) + raise COMException(winerror.E_NOTIMPL) def GetConnectionPointContainer(self): return win32com.server.util.wrap(self) @@ -51,7 +51,7 @@ def Advise(self, pUnk): self._connect_interfaces_[0], pythoncom.IID_IDispatch ) except pythoncom.com_error: - raise Exception(scode=olectl.CONNECT_E_NOCONNECTION) + raise COMException(scode=olectl.CONNECT_E_NOCONNECTION) self.cookieNo = self.cookieNo + 1 self.connections[self.cookieNo] = interface return self.cookieNo @@ -61,11 +61,11 @@ def Unadvise(self, cookie): try: del self.connections[cookie] except KeyError: - raise Exception(scode=winerror.E_UNEXPECTED) + raise COMException(scode=winerror.E_UNEXPECTED) # IConnectionPointContainer interfaces def EnumConnectionPoints(self): - raise Exception(winerror.E_NOTIMPL) + raise COMException(winerror.E_NOTIMPL) def FindConnectionPoint(self, iid): # Find a connection we support. Only support the single event interface. 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/server/exception.py b/com/win32com/server/exception.py index 47e0368506..887186b4d4 100644 --- a/com/win32com/server/exception.py +++ b/com/win32com/server/exception.py @@ -18,7 +18,7 @@ import pythoncom -# Note that we derive from com_error, which derives from exceptions.Exception +# Note that we derive from com_error, which derives from builtin Exception # Also note that we dont support "self.args", as we dont support tuple-unpacking class COMException(pythoncom.com_error): """An Exception object that is understood by the framework. @@ -82,25 +82,19 @@ def __repr__(self): return f"" -# Old name for the COMException class. -# Do NOT use the name Exception, as it is now a built-in -# COMException is the new, official name. -Exception = COMException - - def IsCOMException(t=None): if t is None: t = sys.exc_info()[0] - try: - return issubclass(t, pythoncom.com_error) - except TypeError: # 1.5 in -X mode? - return t is pythoncon.com_error + if not t is type: + # t is not a class (likely None or a str) + return False + return issubclass(t, pythoncom.com_error) def IsCOMServerException(t=None): if t is None: t = sys.exc_info()[0] - try: - return issubclass(t, COMException) - except TypeError: # String exception - return 0 + if not t is type: + # t is not a class (likely None or a str) + return False + return issubclass(t, COMException) diff --git a/com/win32com/server/policy.py b/com/win32com/server/policy.py index a36213eb9c..a66fc0ca5f 100644 --- a/com/win32com/server/policy.py +++ b/com/win32com/server/policy.py @@ -46,13 +46,13 @@ not be Python. Therefore, general Python exceptions and tracebacks aren't much use. - In general, there is an Exception class that should be raised, to allow + In general, there is an COMException class that should be raised, to allow the framework to extract rich COM type error information. The general rule is that the **only** exception returned from Python COM - Server code should be an Exception instance. Any other Python exception + Server code should be an COMException instance. Any other Python exception should be considered an implementation bug in the server (if not, it - should be handled, and an appropriate Exception instance raised). Any + should be handled, and an appropriate COMException instance raised). Any other exception is considered "unexpected", and a dispatcher may take special action (see Dispatchers above) diff --git a/com/win32com/servers/interp.py b/com/win32com/servers/interp.py index 9636837b85..37d83bd497 100644 --- a/com/win32com/servers/interp.py +++ b/com/win32com/servers/interp.py @@ -12,7 +12,7 @@ """ import winerror -from win32com.server.exception import Exception +from win32com.server.exception import COMException # Expose the Python interpreter. @@ -33,14 +33,18 @@ def __init__(self): def Eval(self, exp): """Evaluate an expression.""" if not isinstance(exp, str): - raise Exception(desc="Must be a string", scode=winerror.DISP_E_TYPEMISMATCH) + raise COMException( + desc="Must be a string", scode=winerror.DISP_E_TYPEMISMATCH + ) return eval(str(exp), self.dict) def Exec(self, exp): """Execute a statement.""" if not isinstance(exp, str): - raise Exception(desc="Must be a string", scode=winerror.DISP_E_TYPEMISMATCH) + raise COMException( + desc="Must be a string", scode=winerror.DISP_E_TYPEMISMATCH + ) exec(str(exp), self.dict) diff --git a/com/win32com/servers/perfmon.py b/com/win32com/servers/perfmon.py index 0a0aaaf812..8921c73fa9 100644 --- a/com/win32com/servers/perfmon.py +++ b/com/win32com/servers/perfmon.py @@ -8,7 +8,8 @@ import pythoncom import win32pdhutil import winerror -from win32com.server import exception, register +from win32com.server import register +from win32com.server.exception import COMException class PerfMonQuery: @@ -25,9 +26,9 @@ def Query(self, object, counter, instance=None, machine=None): object, counter, instance, machine=machine ) except win32pdhutil.error as exc: - raise exception.Exception(desc=exc.strerror) + raise COMException(desc=exc.strerror) except TypeError as desc: - raise exception.Exception(desc=desc, scode=winerror.DISP_E_TYPEMISMATCH) + raise COMException(desc=desc, scode=winerror.DISP_E_TYPEMISMATCH) if __name__ == "__main__": diff --git a/com/win32com/test/testDynamic.py b/com/win32com/test/testDynamic.py index ed0c5d1ddf..0d073c4a87 100644 --- a/com/win32com/test/testDynamic.py +++ b/com/win32com/test/testDynamic.py @@ -2,7 +2,7 @@ import pythoncom import winerror -from win32com.server.exception import Exception +from win32com.server.exception import COMException error = "testDynamic error" @@ -22,7 +22,7 @@ def _dynamic_(self, name, lcid, wFlags, args): ret = list(ret) return ret except KeyError: # Probably a method request. - raise Exception(scode=winerror.DISP_E_MEMBERNOTFOUND) + raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND) if wFlags & ( pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_PROPERTYPUTREF @@ -30,11 +30,11 @@ def _dynamic_(self, name, lcid, wFlags, args): setattr(self, name, args[0]) return - raise Exception(scode=winerror.E_INVALIDARG, desc="invalid wFlags") + raise COMException(scode=winerror.E_INVALIDARG, desc="invalid wFlags") def write(self, *args): if len(args) == 0: - raise Exception( + raise COMException( scode=winerror.DISP_E_BADPARAMCOUNT ) # Probably call as PROPGET. diff --git a/com/win32com/test/testMSOffice.py b/com/win32com/test/testMSOffice.py index f4893dfde8..8b3e0df9e0 100644 --- a/com/win32com/test/testMSOffice.py +++ b/com/win32com/test/testMSOffice.py @@ -11,7 +11,6 @@ import win32api import win32com import win32com.client.dynamic -from pywintypes import Unicode from win32com.client import gencache from win32com.test.util import CheckClean @@ -112,7 +111,7 @@ def TextExcel(xl): if not xl.Visible: raise error("Visible property not true.") - if int(xl.Version[0]) >= 8: + if int(xl.Version.split(".")[0]) >= 8: xl.Workbooks.Add() else: xl.Workbooks().Add() @@ -127,16 +126,16 @@ def TextExcel(xl): if xl.Range("A1").Value != "Hi 0": raise error("Single cell range failed") - if xl.Range("A1:B1").Value != ((Unicode("Hi 0"), 2),): + if xl.Range("A1:B1").Value != (("Hi 0", 2),): raise error("flat-horizontal cell range failed") - if xl.Range("A1:A2").Value != ((Unicode("Hi 0"),), (Unicode("x"),)): + if xl.Range("A1:A2").Value != (("Hi 0",), ("x",)): raise error("flat-vertical cell range failed") if xl.Range("A1:C3").Value != ( - (Unicode("Hi 0"), 2, 3), - (Unicode("x"), Unicode("Hi 1"), Unicode("z")), - (3, 2, Unicode("Hi 2")), + ("Hi 0", 2, 3), + ("x", "Hi 1", "z"), + (3, 2, "Hi 2"), ): raise error("square cell range failed") @@ -144,7 +143,7 @@ def TextExcel(xl): if xl.Range("A1:C3").Value != ( (3, 2, 1), - (Unicode("x"), Unicode("y"), Unicode("z")), + ("x", "y", "z"), (1, 2, 3), ): raise error("Range was not what I set it to!") 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/__init__.py b/com/win32comext/adsi/__init__.py index 9ba8d4b531..1b464d10a3 100644 --- a/com/win32comext/adsi/__init__.py +++ b/com/win32comext/adsi/__init__.py @@ -25,7 +25,7 @@ # interface, as well as via IDispatch. import pythoncom -from .adsi import * # nopycln: import # win32comext/adsi/adsi.pyd +from .adsi import * # nopycln: import # Re-export everything from win32comext/adsi/adsi.pyd LCID = 0 @@ -51,8 +51,8 @@ def _get_good_ret( class ADSIEnumerator: def __init__(self, ob): # Query the object for the container interface. - self._cont_ = ob.QueryInterface(IID_IADsContainer) - self._oleobj_ = ADsBuildEnumerator(self._cont_) # a PyIADsEnumVARIANT + self._cont_ = ob.QueryInterface(adsi.IID_IADsContainer) + self._oleobj_ = adsi.ADsBuildEnumerator(self._cont_) # a PyIADsEnumVARIANT self.index = -1 def __getitem__(self, index): @@ -68,20 +68,17 @@ def __GetIndex(self, index): # Index requested out of sequence. raise ValueError("You must index this object sequentially") self.index = index - result = ADsEnumerateNext(self._oleobj_, 1) + result = adsi.ADsEnumerateNext(self._oleobj_, 1) if len(result): return _get_good_ret(result[0]) # Failed - reset for next time around. self.index = -1 - self._oleobj_ = ADsBuildEnumerator(self._cont_) # a PyIADsEnumVARIANT + self._oleobj_ = adsi.ADsBuildEnumerator(self._cont_) # a PyIADsEnumVARIANT raise IndexError("list index out of range") class ADSIDispatch(win32com.client.CDispatch): - def _wrap_dispatch_( - self, ob, userName=None, returnCLSID=None, UnicodeToString=None - ): - assert UnicodeToString is None, "this is deprectated and will be removed" + def _wrap_dispatch_(self, ob, userName=None, returnCLSID=None): if not userName: userName = "ADSI-object" olerepr = win32com.client.dynamic.MakeOleRepr(ob, None, None) @@ -105,18 +102,12 @@ def QueryInterface(self, iid): return _get_good_ret(ret) -# We override the global methods to do the right thing. -_ADsGetObject = ADsGetObject # The one in the .pyd - - +# We override the adsi.pyd methods to do the right thing. def ADsGetObject(path, iid=pythoncom.IID_IDispatch): - ret = _ADsGetObject(path, iid) + ret = adsi.ADsGetObject(path, iid) return _get_good_ret(ret) -_ADsOpenObject = ADsOpenObject - - def ADsOpenObject(path, username, password, reserved=0, iid=pythoncom.IID_IDispatch): - ret = _ADsOpenObject(path, username, password, reserved, iid) + ret = adsi.ADsOpenObject(path, username, password, reserved, iid) return _get_good_ret(ret) 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/axdebug/Test/host.py b/com/win32comext/axdebug/Test/host.py index b3d1317462..44b6f9a7c7 100644 --- a/com/win32comext/axdebug/Test/host.py +++ b/com/win32comext/axdebug/Test/host.py @@ -6,7 +6,7 @@ import winerror from win32com.axdebug import codecontainer, gateways from win32com.axdebug.util import _wrap, trace -from win32com.server.exception import Exception +from win32com.server.exception import COMException class ExternalConnection: @@ -57,7 +57,7 @@ def _GetCodeContainer(self): try: codeText = open(self.module.__file__, "rt").read() except OSError as details: - codeText = f"# Exception opening file\n# {details}" + codeText = f"# COMException opening file\n# {details}" self.codeContainer = codecontainer.SourceCodeContainer( codeText, self.module.__file__ @@ -79,23 +79,23 @@ def GetDeferredText(self, dwTextStartCookie, maxChars, bWantAttr): def GetScriptTextAttributes(self, codeText, delimterText, flags): # Result must be an attribute sequence of same "length" as the code. trace("GetScriptTextAttributes", delimterText, flags) - raise Exception(scode=winerror.E_NOTIMPL) + raise COMException(scode=winerror.E_NOTIMPL) def OnCreateDocumentContext(self): # Result must be a PyIUnknown trace("OnCreateDocumentContext") - raise Exception(scode=winerror.E_NOTIMPL) + raise COMException(scode=winerror.E_NOTIMPL) def GetPathName(self): # Result must be (string, int) where the int is a BOOL # - TRUE if the path refers to the original file for the document. # - FALSE if the path refers to a newly created temporary file. - # - raise Exception(scode=E_FAIL) if no source file can be created/determined. + # - raise COMException(scode=E_FAIL) if no source file can be created/determined. trace("GetPathName") try: return win32api.GetFullPathName(self.module.__file__), 1 except (AttributeError, win32api.error): - raise Exception(scode=winerror.E_FAIL) + raise COMException(scode=winerror.E_FAIL) def GetFileName(self): # Result is a string with just the name of the document, no path information. @@ -104,7 +104,7 @@ def GetFileName(self): def NotifyChanged(): trace("NotifyChanged") - raise Exception(scode=winerror.E_NOTIMPL) + raise COMException(scode=winerror.E_NOTIMPL) def TestSmartProvider(): diff --git a/com/win32comext/axdebug/codecontainer.py b/com/win32comext/axdebug/codecontainer.py index 69a9f8e458..2030cf6954 100644 --- a/com/win32comext/axdebug/codecontainer.py +++ b/com/win32comext/axdebug/codecontainer.py @@ -12,7 +12,7 @@ import winerror from win32com.axdebug import axdebug, contexts from win32com.axdebug.util import _wrap -from win32com.server.exception import Exception +from win32com.server.exception import COMException _keywords = {} # set of Python keywords for name in """ @@ -65,7 +65,7 @@ def GetPositionOfLine(self, cLineNumber): try: return self.lineOffsets[cLineNumber] except IndexError: - raise Exception(scode=winerror.S_FALSE) + raise COMException(scode=winerror.S_FALSE) def GetLineOfPosition(self, charPos): self.GetText() # Prime us. @@ -78,7 +78,7 @@ def GetLineOfPosition(self, charPos): lineNo = lineNo + 1 else: # for not broken. # print("Cant find", charPos, "in", self.lineOffsets) - raise Exception(scode=winerror.S_FALSE) + raise COMException(scode=winerror.S_FALSE) # print("GLOP ret=", lineNo, (charPos - lastOffset)) return lineNo, (charPos - lastOffset) @@ -225,7 +225,7 @@ def GetText(self): try: self.text = open(fname, "r").read() except OSError as details: - self.text = f"# Exception opening file\n# {repr(details)}" + self.text = f"# COMException opening file\n# {repr(details)}" else: self.text = f"# No file available for module '{self.module}'" self._buildlines() @@ -248,7 +248,7 @@ def GetName(self, dnt): elif dnt == axdebug.DOCUMENTNAMETYPE_URL: return f"file:{fname}" else: - raise Exception(scode=winerror.E_UNEXPECTED) + raise COMException(scode=winerror.E_UNEXPECTED) if __name__ == "__main__": diff --git a/com/win32comext/axdebug/gateways.py b/com/win32comext/axdebug/gateways.py index d87e85b335..19f803eaf6 100644 --- a/com/win32comext/axdebug/gateways.py +++ b/com/win32comext/axdebug/gateways.py @@ -5,7 +5,7 @@ import winerror from win32com.axdebug import axdebug from win32com.axdebug.util import RaiseNotImpl, _wrap -from win32com.server.exception import Exception +from win32com.server.exception import COMException from win32com.server.util import ListEnumeratorGateway @@ -233,7 +233,7 @@ def GetPathName(self): - if fIsOriginalPath is TRUE if the path refers to the original file for the document. - if fIsOriginalPath is FALSE if the path refers to a newly created temporary file. - raise Exception(winerror.E_FAIL) if no source file can be created/determined. + raise COMException(winerror.E_FAIL) if no source file can be created/determined. """ RaiseNotImpl("GetPathName") @@ -398,7 +398,7 @@ def GetPathName(self): # Result must be (string, int) where the int is a BOOL # - TRUE if the path refers to the original file for the document. # - FALSE if the path refers to a newly created temporary file. - # - raise Exception(scode=E_FAIL) if no source file can be created/determined. + # - raise COMException(scode=E_FAIL) if no source file can be created/determined. RaiseNotImpl("GetPathName") def GetFileName(self): @@ -449,7 +449,7 @@ def Unadvise(self, cookie): try: del self.connections[cookie] except KeyError: - return Exception(scode=winerror.E_UNEXPECTED) + return COMException(scode=winerror.E_UNEXPECTED) # IConnectionPointContainer interfaces def EnumConnectionPoints(self): @@ -459,7 +459,7 @@ def FindConnectionPoint(self, iid): # Find a connection we support. Only support the single event interface. if iid == axdebug.IID_IDebugDocumentTextEvents: return _wrap(self) - raise Exception(scode=winerror.E_NOINTERFACE) # ?? + raise COMException(scode=winerror.E_NOINTERFACE) # ?? class RemoteDebugApplicationEvents: diff --git a/com/win32comext/axdebug/util.py b/com/win32comext/axdebug/util.py index 805d21b28b..46806ae547 100644 --- a/com/win32comext/axdebug/util.py +++ b/com/win32comext/axdebug/util.py @@ -7,7 +7,7 @@ import win32api import win32com.server.util import winerror -from win32com.server.exception import Exception +from win32com.server.exception import COMException try: os.environ["DEBUG_AXDEBUG"] @@ -66,7 +66,7 @@ def RaiseNotImpl(who=None): frame = frame.f_back # and raise the exception for COM - raise Exception(scode=winerror.E_NOTIMPL) + raise COMException(scode=winerror.E_NOTIMPL) import win32com.server.policy @@ -101,7 +101,7 @@ def _Invoke_(self, dispid, lcid, wFlags, args): ) # print("Invoke of", dispid, "returning", rc) return rc - except Exception: + except COMException: t, v, tb = sys.exc_info() tb = None # A cycle scode = v.scode diff --git a/com/win32comext/axscript/client/error.py b/com/win32comext/axscript/client/error.py index c4d24db9de..f10c6db574 100644 --- a/com/win32comext/axscript/client/error.py +++ b/com/win32comext/axscript/client/error.py @@ -8,10 +8,10 @@ import traceback import pythoncom -import win32com.server.exception import win32com.server.util import winerror from win32com.axscript import axscript +from win32com.server.exception import COMException debugging = 0 @@ -64,7 +64,7 @@ def GetExceptionInfo(self): return self.exception -class AXScriptException(win32com.server.exception.COMException): +class AXScriptException(COMException): """A class used as a COM exception. Note this has attributes which conform to the standard attributes @@ -74,8 +74,7 @@ class AXScriptException(win32com.server.exception.COMException): def __init__(self, site, codeBlock, exc_type, exc_value, exc_traceback): # set properties base class shares via base ctor... - win32com.server.exception.COMException.__init__( - self, + super().__init__( description="Unknown Exception", scode=winerror.DISP_E_EXCEPTION, source="Python ActiveX Scripting Engine", @@ -248,7 +247,7 @@ def ProcessAXScriptException(scriptingSite, debugManager, exceptionInstance): if result == winerror.S_OK: # If the above returns NOERROR, it is assumed the error has been # correctly registered and the value SCRIPT_E_REPORTED is returned. - ret = win32com.server.exception.COMException(scode=axscript.SCRIPT_E_REPORTED) + ret = COMException(scode=axscript.SCRIPT_E_REPORTED) return ret else: # The error is taken to be unreported and is propagated up the call stack diff --git a/com/win32comext/axscript/client/framework.py b/com/win32comext/axscript/client/framework.py index 9dbdfd5533..44ef43ac9a 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 @@ -29,7 +31,7 @@ def RemoveCR(text): SCRIPTTEXT_ISEXPRESSION = 0x00000020 SCRIPTTEXT_ISPERSISTENT = 0x00000040 -from win32com.server.exception import Exception, IsCOMServerException +from win32com.server.exception import COMException, IsCOMServerException from . import error # ax.client.error @@ -114,7 +116,7 @@ def RaiseAssert(scode, desc): """A debugging function that raises an exception considered an "Assertion".""" print("**************** ASSERTION FAILED *******************") print(desc) - raise Exception(desc, scode) + raise COMException(desc, scode) class AXScriptCodeBlock: @@ -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 = {} @@ -207,7 +209,7 @@ def _invoke_(self, dispid, lcid, wFlags, args): try: event = self.events[dispid] except: - raise Exception(scode=winerror.DISP_E_MEMBERNOTFOUND) + raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND) # print("Invoke for ", event, "on", self.myScriptItem, " - calling", self.myInvokeMethod) return self.myInvokeMethod(self.myScriptItem, event, lcid, wFlags, args) @@ -404,10 +406,10 @@ def GetCreateSubItem(self, parentItem, name, dispatch, flags): rc = self.subItems[keyName] # No changes allowed to existing flags. if not rc.flags is None and not flags is None and rc.flags != flags: - raise Exception(scode=winerror.E_INVALIDARG) + raise COMException(scode=winerror.E_INVALIDARG) # Existing item must not have a dispatch. if not rc.dispatch is None and not dispatch is None: - raise Exception(scode=winerror.E_INVALIDARG) + raise COMException(scode=winerror.E_INVALIDARG) rc.flags = flags # Setup the real flags. rc.dispatch = dispatch except KeyError: @@ -770,7 +772,7 @@ def SetScriptSite(self, site): def GetScriptSite(self, iid): if self.scriptSite is None: - raise Exception(scode=winerror.S_FALSE) + raise COMException(scode=winerror.S_FALSE) return self.scriptSite.QueryInterface(iid) def SetScriptState(self, state): @@ -779,7 +781,7 @@ def SetScriptState(self, state): return # If closed, allow no other state transitions if self.scriptState == axscript.SCRIPTSTATE_CLOSED: - raise Exception(scode=winerror.E_INVALIDARG) + raise COMException(scode=winerror.E_INVALIDARG) if state == axscript.SCRIPTSTATE_INITIALIZED: # Re-initialize - shutdown then reset. @@ -821,7 +823,7 @@ def SetScriptState(self, state): self.Reset() self.ChangeScriptState(state) else: - raise Exception(scode=winerror.E_INVALIDARG) + raise COMException(scode=winerror.E_INVALIDARG) def GetScriptState(self): return self.scriptState @@ -862,12 +864,12 @@ def Close(self): def AddNamedItem(self, name, flags): if self.scriptSite is None: - raise Exception(scode=winerror.E_INVALIDARG) + raise COMException(scode=winerror.E_INVALIDARG) try: unknown = self.scriptSite.GetItemInfo(name, axscript.SCRIPTINFO_IUNKNOWN)[0] dispatch = unknown.QueryInterface(pythoncom.IID_IDispatch) except pythoncom.com_error: - raise Exception( + raise COMException( scode=winerror.E_NOINTERFACE, desc="Object has no dispatch interface available.", ) @@ -879,23 +881,23 @@ def AddNamedItem(self, name, flags): def GetScriptDispatch(self, name): # Base classes should override. - raise Exception(scode=winerror.E_NOTIMPL) + raise COMException(scode=winerror.E_NOTIMPL) def GetCurrentScriptThreadID(self): return self.baseThreadId def GetScriptThreadID(self, win32ThreadId): if self.baseThreadId == -1: - raise Exception(scode=winerror.E_UNEXPECTED) + raise COMException(scode=winerror.E_UNEXPECTED) if self.baseThreadId != win32ThreadId: - raise Exception(scode=winerror.E_INVALIDARG) + raise COMException(scode=winerror.E_INVALIDARG) return self.baseThreadId def GetScriptThreadState(self, scriptThreadId): if self.baseThreadId == -1: - raise Exception(scode=winerror.E_UNEXPECTED) + raise COMException(scode=winerror.E_UNEXPECTED) if scriptThreadId != self.baseThreadId: - raise Exception(scode=winerror.E_INVALIDARG) + raise COMException(scode=winerror.E_INVALIDARG) return self.threadState def AddTypeLib(self, uuid, major, minor, flags): @@ -907,10 +909,10 @@ def AddTypeLib(self, uuid, major, minor, flags): # This is never called by the C++ framework - it does magic. # See PyGActiveScript.cpp # def InterruptScriptThread(self, stidThread, exc_info, flags): - # raise Exception("Not Implemented", scode=winerror.E_NOTIMPL) + # raise COMException("Not Implemented", scode=winerror.E_NOTIMPL) def Clone(self): - raise Exception("Not Implemented", scode=winerror.E_NOTIMPL) + raise COMException("Not Implemented", scode=winerror.E_NOTIMPL) # # IObjectSafety @@ -940,7 +942,7 @@ def SetInterfaceSafetyOptions(self, iid, optionsMask, enabledOptions): supported = self._GetSupportedInterfaceSafetyOptions() self.safetyOptions = supported & optionsMask & enabledOptions else: - raise Exception(scode=winerror.E_NOINTERFACE) + raise COMException(scode=winerror.E_NOINTERFACE) def _GetSupportedInterfaceSafetyOptions(self): return 0 @@ -956,7 +958,7 @@ def GetInterfaceSafetyOptions(self, iid): supported = self._GetSupportedInterfaceSafetyOptions() return supported, self.safetyOptions else: - raise Exception(scode=winerror.E_NOINTERFACE) + raise COMException(scode=winerror.E_NOINTERFACE) # # Other helpers. @@ -1022,7 +1024,7 @@ def Run(self): self.scriptState != axscript.SCRIPTSTATE_INITIALIZED and self.scriptState != axscript.SCRIPTSTATE_STARTED ): - raise Exception(scode=winerror.E_UNEXPECTED) + raise COMException(scode=winerror.E_UNEXPECTED) # self._DumpNamedItems_() self.ExecutePendingScripts() self.DoRun() @@ -1200,7 +1202,7 @@ def HandleException(self, codeBlock): ): # Ensure the traceback doesnt cause a cycle. exc_traceback = None - raise Exception(scode=exc_value.hresult) + raise COMException(scode=exc_value.hresult) exception = error.AXScriptException( self, codeBlock, exc_type, exc_value, exc_traceback @@ -1227,12 +1229,12 @@ def HandleException(self, codeBlock): def BeginScriptedSection(self): if self.scriptSite is None: - raise Exception(scode=winerror.E_UNEXPECTED) + raise COMException(scode=winerror.E_UNEXPECTED) self.scriptSite.OnEnterScript() def EndScriptedSection(self): if self.scriptSite is None: - raise Exception(scode=winerror.E_UNEXPECTED) + raise COMException(scode=winerror.E_UNEXPECTED) self.scriptSite.OnLeaveScript() def DisableInterrupts(self): @@ -1245,7 +1247,7 @@ def GetNamedItem(self, name): try: return self.subItems[name] except KeyError: - raise Exception(scode=winerror.E_INVALIDARG) + raise COMException(scode=winerror.E_INVALIDARG) def GetNamedItemClass(self): return ScriptItem diff --git a/com/win32comext/axscript/client/pydumper.py b/com/win32comext/axscript/client/pydumper.py index 3d0725aeff..39bf892845 100644 --- a/com/win32comext/axscript/client/pydumper.py +++ b/com/win32comext/axscript/client/pydumper.py @@ -16,7 +16,7 @@ from win32com.axscript import axscript from . import pyscript -from .pyscript import SCRIPTTEXT_FORCEEXECUTION, Exception, RaiseAssert, trace +from .pyscript import SCRIPTTEXT_FORCEEXECUTION, RaiseAssert, trace PyDump_CLSID = "{ac527e60-c693-11d0-9c25-00aa00125a98}" diff --git a/com/win32comext/axscript/client/pyscript.py b/com/win32comext/axscript/client/pyscript.py index dfa52e9b9a..d53559e300 100644 --- a/com/win32comext/axscript/client/pyscript.py +++ b/com/win32comext/axscript/client/pyscript.py @@ -22,10 +22,10 @@ SCRIPTTEXT_FORCEEXECUTION, SCRIPTTEXT_ISEXPRESSION, SCRIPTTEXT_ISPERSISTENT, - Exception, RaiseAssert, trace, ) +from win32com.server.exception import COMException PyScript_CLSID = "{DF630910-1C1D-11d0-AE36-8C0F5E000000}" @@ -366,7 +366,7 @@ def DoProcessScriptItemEvent(self, item, event, lcid, wFlags, args): item.scriptlets[funcName] = function if function is None: - raise Exception(scode=winerror.DISP_E_MEMBERNOTFOUND) + raise COMException(scode=winerror.DISP_E_MEMBERNOTFOUND) return self.ApplyInScriptedSection(codeBlock, function, args) def DoParseScriptText( 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/axscript/server/axsite.py b/com/win32comext/axscript/server/axsite.py index 29fa0ebe93..f907789397 100644 --- a/com/win32comext/axscript/server/axsite.py +++ b/com/win32comext/axscript/server/axsite.py @@ -2,7 +2,8 @@ import win32com.axscript.axscript import winerror from win32com.axscript import axscript -from win32com.server import exception, util +from win32com.server import util +from win32com.server.exception import COMException class AXEngine: @@ -115,7 +116,7 @@ def GetLCID(self): def GetItemInfo(self, name, returnMask): if name not in self.objModel: - raise exception.Exception( + raise COMException( scode=winerror.TYPE_E_ELEMENTNOTFOUND, desc="item not found" ) diff --git a/com/win32comext/axscript/server/error.py b/com/win32comext/axscript/server/error.py index 82491a6ebe..4ae196a96a 100644 --- a/com/win32comext/axscript/server/error.py +++ b/com/win32comext/axscript/server/error.py @@ -8,7 +8,7 @@ """ -class Exception: +class Exception: # unused and shadows builtin Exception def __init__(self, activeScriptError): self.activeScriptError = activeScriptError diff --git a/com/win32comext/axscript/test/leakTest.py b/com/win32comext/axscript/test/leakTest.py index b75948eaed..98a589d3ef 100644 --- a/com/win32comext/axscript/test/leakTest.py +++ b/com/win32comext/axscript/test/leakTest.py @@ -4,7 +4,6 @@ import win32com.server.policy from win32com.axscript import axscript from win32com.axscript.server import axsite -from win32com.axscript.server.error import Exception from win32com.server import connect, util diff --git a/com/win32comext/axscript/test/testHost.py b/com/win32comext/axscript/test/testHost.py index ee6210fa65..e0b9995acb 100644 --- a/com/win32comext/axscript/test/testHost.py +++ b/com/win32comext/axscript/test/testHost.py @@ -6,10 +6,8 @@ import win32com.test.util from win32com.axscript import axscript from win32com.axscript.server import axsite -from win32com.axscript.server.error import Exception from win32com.client.dynamic import Dispatch from win32com.server import connect, util -from win32com.server.exception import COMException verbose = "-v" in sys.argv diff --git a/com/win32comext/axscript/test/testHost4Dbg.py b/com/win32comext/axscript/test/testHost4Dbg.py index 2092b045c0..c618a41206 100644 --- a/com/win32comext/axscript/test/testHost4Dbg.py +++ b/com/win32comext/axscript/test/testHost4Dbg.py @@ -6,7 +6,6 @@ import win32ui from win32com.axscript import axscript from win32com.axscript.server import axsite -from win32com.axscript.server.error import Exception from win32com.server import util version = "0.0.1" 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/com/win32comext/mapi/src/PyIMsgStore.i b/com/win32comext/mapi/src/PyIMsgStore.i index ddb43229af..a78fb93802 100644 --- a/com/win32comext/mapi/src/PyIMsgStore.i +++ b/com/win32comext/mapi/src/PyIMsgStore.i @@ -79,6 +79,32 @@ PyObject *PyIMsgStore::OpenEntry(PyObject *self, PyObject *args) %} +%native(StoreLogoff) StoreLogoff; +%{ +// @pyswig |StoreLogoff|Enables the orderly logoff of the message store. +PyObject *PyIMsgStore::StoreLogoff(PyObject *self, PyObject *args) +{ + HRESULT _result; + unsigned long flags; + + IMsgStore *_swig_self; + if ((_swig_self=GetI(self))==NULL) return NULL; + // @pyparm int|flags||Bitmask of flags that controls how the message store is closed. + if(!PyArg_ParseTuple(args,"k:StoreLogoff", &flags)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + _result = (HRESULT )_swig_self->StoreLogoff(&flags); + Py_END_ALLOW_THREADS + if (FAILED(_result)) { + return OleSetOleError(_result); + } + + return PyLong_FromLong(flags); +} + +%} + %native(GetReceiveFolder) GetReceiveFolder; %{ // @pyswig , string|GetReceiveFolder|Obtains the folder that was established as the destination for incoming messages of a specified message class or the default receive folder for the message store. diff --git a/com/win32comext/mapi/src/mapi.i b/com/win32comext/mapi/src/mapi.i index 6e769b7f47..fae114b43e 100644 --- a/com/win32comext/mapi/src/mapi.i +++ b/com/win32comext/mapi/src/mapi.i @@ -269,6 +269,16 @@ static PyObject *PyMAPIUninitialize(PyObject *self, PyObject *args) #define MAPI_BEST_ACCESS MAPI_BEST_ACCESS #define MAPI_MODIFY MAPI_MODIFY +#define LOGOFF_NO_WAIT LOGOFF_NO_WAIT +#define LOGOFF_ORDERLY LOGOFF_ORDERLY +#define LOGOFF_PURGE LOGOFF_PURGE +#define LOGOFF_ABORT LOGOFF_ABORT +#define LOGOFF_QUIET LOGOFF_QUIET +#define LOGOFF_COMPLETE LOGOFF_COMPLETE +#define LOGOFF_INBOUND LOGOFF_INBOUND +#define LOGOFF_OUTBOUND LOGOFF_OUTBOUND +#define LOGOFF_OUTBOUND_QUEUE LOGOFF_OUTBOUND_QUEUE + #define MAPI_DEFERRED_ERRORS MAPI_DEFERRED_ERRORS // Allows a method to return successfully, possibly before the changes have been fully committed. #define MAPI_INIT_VERSION MAPI_INIT_VERSION diff --git a/com/win32comext/shell/shellcon.py b/com/win32comext/shell/shellcon.py index 769adf7379..37841a9710 100644 --- a/com/win32comext/shell/shellcon.py +++ b/com/win32comext/shell/shellcon.py @@ -229,6 +229,12 @@ SVSI_ENSUREVISIBLE = 8 SVSI_FOCUSED = 16 SVSI_TRANSLATEPT = 32 +SVSI_SELECTIONMARK = 64 +SVSI_POSITIONITEM = 128 +SVSI_CHECK = 256 +SVSI_CHECK2 = 512 +SVSI_KEYBOARDSELECT = 1025 +SVSI_NOTAKEFOCUS = 1073741824 SVGIO_BACKGROUND = 0 SVGIO_SELECTION = 1 SVGIO_ALLVIEW = 2 @@ -415,6 +421,18 @@ QIF_CACHED = 1 QIF_DONTEXPANDFOLDER = 2 +# ShellWindowFindWindowOptions +SWFO_NEEDDISPATCH = 1 +SWFO_INCLUDEPENDING = 2 +SWFO_COOKIEPASSED = 4 + +# ShellWindowTypeConstants +SWC_EXPLORER = 0 +SWC_BROWSER = 1 +SWC_3RDPARTY = 2 +SWC_CALLBACK = 4 +SWC_DESKTOP = 8 + # SHARD enum for SHAddToRecentDocs SHARD_PIDL = 1 SHARD_PATHA = 2 diff --git a/com/win32comext/shell/src/PyIFolderView.cpp b/com/win32comext/shell/src/PyIFolderView.cpp new file mode 100644 index 0000000000..e9de6b8b69 --- /dev/null +++ b/com/win32comext/shell/src/PyIFolderView.cpp @@ -0,0 +1,432 @@ +// This file implements the IFolderView Interface for Python. +// Generated by makegw.py + +#include "shell_pch.h" +#include "PyIFolderView.h" + +// Helper functions + +static PyObject *PyObject_FromPOINT(const POINT &p) { return Py_BuildValue("ll", p.x, p.y); } + +static BOOL toPIDL(PyObject *pObjId, LPITEMIDLIST *ppidl, IFolderView *self) +{ + if (PyLong_Check(pObjId)) { + int idx = (int)PyLong_AsLong(pObjId); + HRESULT hr = self->Item(idx, ppidl); + if (FAILED(hr)) { + PyCom_BuildPyException(hr, self, IID_IFolderView); + return FALSE; + } + } + else if (PyBytes_Check(pObjId)) { + // Gainarie: Create an "artificial" list ot be passed to PyObject_AsPIDL + PyObject *list = PyList_New(1); + if (list == NULL) + return FALSE; + Py_INCREF(pObjId); + PyList_SET_ITEM(list, 0, pObjId); + BOOL res = PyObject_AsPIDL(list, ppidl); + Py_DECREF(list); + if (!res) + return FALSE; + } + else { + PyErr_Format(PyExc_TypeError, "Integer (index) or bytes (PIDL) required"); + return FALSE; + } + return TRUE; +} + +// @doc - This file contains autoduck documentation +// --------------------------------------------------- +// +// Interface Implementation + +PyIFolderView::PyIFolderView(IUnknown *pdisp) : PyIUnknown(pdisp) { ob_type = &type; } + +PyIFolderView::~PyIFolderView() {} + +/* static */ IFolderView *PyIFolderView::GetI(PyObject *self) { return (IFolderView *)PyIUnknown::GetI(self); } + +// @pymethod |PyIFolderView|GetCurrentViewMode|Description of GetCurrentViewMode. +PyObject *PyIFolderView::GetCurrentViewMode(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + UINT viewMode; + if (!PyArg_ParseTuple(args, ":GetCurrentViewMode")) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->GetCurrentViewMode(&viewMode); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + return Py_BuildValue("I", viewMode); +} + +// @pymethod |PyIFolderView|SetCurrentViewMode|Description of SetCurrentViewMode. +PyObject *PyIFolderView::SetCurrentViewMode(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + // @pyparm int|ViewMode||Description for ViewMode + UINT viewMode; + if (!PyArg_ParseTuple(args, "I:SetCurrentViewMode", &viewMode)) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->SetCurrentViewMode(viewMode); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + Py_RETURN_NONE; +} + +// @pymethod |PyIFolderView|GetFolder|Description of GetFolder. +PyObject *PyIFolderView::GetFolder(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + // @pyparm |riid||Description for riid + PyObject *obriid; + IID riid; + if (!PyArg_ParseTuple(args, "O:GetFolder", &obriid)) + return NULL; + if (!PyWinObject_AsIID(obriid, &riid)) + return NULL; + HRESULT hr; + IShellFolder *ppshf = NULL; + PY_INTERFACE_PRECALL; + hr = pIFV->GetFolder(riid, (void **)&ppshf); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + return PyCom_PyObjectFromIUnknown(ppshf, IID_IShellFolder, FALSE); +} + +// @pymethod |PyIFolderView|Item|Description of Item. +PyObject *PyIFolderView::Item(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + // @pyparm int|iItemIndex||Description for iItemIndex + int iItemIndex; + PITEMID_CHILD pidl = NULL; + if (!PyArg_ParseTuple(args, "i:Item", &iItemIndex)) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->Item(iItemIndex, &pidl); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + PyObject *obppidl; + obppidl = PyObject_FromPIDL(pidl, TRUE); + if ((obppidl == Py_None) || (!PyList_Check(obppidl)) || (PyList_Size(obppidl) != 1)) { + Py_XDECREF(obppidl); + Py_RETURN_NONE; + } + PyObject *pyretval = PyList_GetItem(obppidl, 0); + Py_INCREF(pyretval); + Py_DECREF(obppidl); + return pyretval; +} + +// @pymethod |PyIFolderView|ItemCount|Description of ItemCount. +PyObject *PyIFolderView::ItemCount(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + // @pyparm int|uFlags||Description for uFlags + UINT uFlags; + int pcItems; + if (!PyArg_ParseTuple(args, "I:ItemCount", &uFlags)) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->ItemCount(uFlags, &pcItems); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + return Py_BuildValue("i", pcItems); +} + +// @pymethod |PyIFolderView|Items|Description of Items. +PyObject *PyIFolderView::Items(PyObject *self, PyObject *args) +{ + return PyErr_Format(PyExc_NotImplementedError, "Not implemented."); + /* + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + // @pyparm int|uFlags||Description for uFlags + // @pyparm |riid||Description for riid + // *** The input argument ppv of type "__RPC__deref_out_opt void **" was not processed *** + // Please check the conversion function is appropriate and exists! + void ppv; + PyObject *obppv; + // @pyparm |ppv||Description for ppv + PyObject *obriid; + UINT uFlags; + IID riid; + if (!PyArg_ParseTuple(args, "iOO:Items", &uFlags, &obriid, &obppv)) + return NULL; + BOOL bPythonIsHappy = TRUE; + if (!PyWinObject_AsIID(obriid, &riid)) + bPythonIsHappy = FALSE; + if (bPythonIsHappy && !PyObject_Asvoid(obppv, &ppv)) + bPythonIsHappy = FALSE; + if (!bPythonIsHappy) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->Items(uFlags, riid, ppv); + PyObject_Freevoid(ppv); + + PY_INTERFACE_POSTCALL; + + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + // *** The output argument ppv of type "__RPC__deref_out_opt void **" was not processed *** + // The type 'void' (ppv) is unknown. + Py_RETURN_NONE; + //*/ +} + +// @pymethod |PyIFolderView|GetSelectionMarkedItem|Description of GetSelectionMarkedItem. +PyObject *PyIFolderView::GetSelectionMarkedItem(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + int piItem; + if (!PyArg_ParseTuple(args, ":GetSelectionMarkedItem")) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->GetSelectionMarkedItem(&piItem); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + return Py_BuildValue("i", piItem); +} + +// @pymethod |PyIFolderView|GetFocusedItem|Description of GetFocusedItem. +PyObject *PyIFolderView::GetFocusedItem(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + int item; + if (!PyArg_ParseTuple(args, ":GetFocusedItem")) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->GetFocusedItem(&item); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + return Py_BuildValue("i", item); +} + +// @pymethod |PyIFolderView|GetItemPosition|Description of GetItemPosition. +PyObject *PyIFolderView::GetItemPosition(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + // @pyparm |pidl||Description for pidl + POINT pt; + PyObject *obpidl; + PCUITEMID_CHILD pidl = NULL; + if (!PyArg_ParseTuple(args, "O:GetItemPosition", &obpidl)) + return NULL; + if (!toPIDL(obpidl, (LPITEMIDLIST *)&pidl, pIFV)) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->GetItemPosition(pidl, &pt); + PyObject_FreePIDL(pidl); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + return PyObject_FromPOINT(pt); +} + +// @pymethod |PyIFolderView|GetSpacing|Description of GetSpacing. +PyObject *PyIFolderView::GetSpacing(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + // @pyparm (int, int)|pt||Coordinates of an item + POINT pt; + if (!PyArg_ParseTuple(args, "ll:GetSpacing", &pt.x, &pt.y)) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->GetSpacing(&pt); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + return PyObject_FromPOINT(pt); +} + +// @pymethod |PyIFolderView|GetDefaultSpacing|Description of GetDefaultSpacing. +PyObject *PyIFolderView::GetDefaultSpacing(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + POINT pt; + if (!PyArg_ParseTuple(args, ":GetDefaultSpacing")) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->GetDefaultSpacing(&pt); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + return PyObject_FromPOINT(pt); +} + +// @pymethod |PyIFolderView|GetAutoArrange|Description of GetAutoArrange. +PyObject *PyIFolderView::GetAutoArrange(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + if (!PyArg_ParseTuple(args, ":GetAutoArrange")) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->GetAutoArrange(); + PY_INTERFACE_POSTCALL; + if (hr == S_OK) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +// @pymethod |PyIFolderView|SelectItem|Description of SelectItem. +PyObject *PyIFolderView::SelectItem(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + // @pyparm int|iItem||Description for iItem + // @pyparm int|dwFlags||Description for dwFlags + int item; + DWORD dwFlags; + if (!PyArg_ParseTuple(args, "ik:SelectItem", &item, &dwFlags)) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->SelectItem(item, dwFlags); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + Py_RETURN_NONE; +} + +// @pymethod |PyIFolderView|SelectAndPositionItems|Description of SelectAndPositionItems. +PyObject *PyIFolderView::SelectAndPositionItems(PyObject *self, PyObject *args) +{ + return PyErr_Format(PyExc_NotImplementedError, "Not implemented. Use SelectAndPositionItem instead."); + /* + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + // @pyparm int|cidl||Description for cidl + // @pyparm |apidl||Description for apidl + // *** The input argument apt of type "__RPC__in_ecount_full_opt(cidl) POINT *" was not processed *** + // Please check the conversion function is appropriate and exists! + POINT apt; + PyObject *obapt; + // @pyparm |apt||Description for apt + // @pyparm int|dwFlags||Description for dwFlags + PyObject *obapidl; + UINT cidl; + PCUITEMID_CHILD_ARRAY apidl; + DWORD dwFlags; + if (!PyArg_ParseTuple(args, "iOOl:SelectAndPositionItems", &cidl, &obapidl, &obapt, &dwFlags)) + return NULL; + BOOL bPythonIsHappy = TRUE; + if (bPythonIsHappy && !PyObject_AsPIDL(obapidl, &*apidl)) + bPythonIsHappy = FALSE; + if (bPythonIsHappy && !PyObject_AsPOINT(obapt, &apt)) + bPythonIsHappy = FALSE; + if (!bPythonIsHappy) + return NULL; + HRESULT hr; + PY_INTERFACE_PRECALL; + hr = pIFV->SelectAndPositionItems(cidl, apidl, apt, dwFlags); + PyObject_FreePIDL(apidl); + PyObject_FreePOINT(apt); + + PY_INTERFACE_POSTCALL; + + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + Py_RETURN_NONE; +//*/ +} + +// @pymethod |PyIFolderView|SelectAndPositionItem|Description of SelectAndPositionItem. +PyObject *PyIFolderView::SelectAndPositionItem(PyObject *self, PyObject *args) +{ + IFolderView *pIFV = GetI(self); + if (pIFV == NULL) + return NULL; + LPITEMIDLIST pidl = NULL; + PyObject *obpidl; + POINT pt; + // @pyparm |apidl||Description for apidl + // @pyparm (int, int)|pt||Coordinates where drag operation entered the window + // @pyparm int|dwFlags||Description for dwFlags + DWORD dwFlags; + if (!PyArg_ParseTuple(args, "O(ll)k:SelectAndPositionItem", &obpidl, &pt.x, &pt.y, &dwFlags)) + return NULL; + if (!toPIDL(obpidl, &pidl, pIFV)) + return NULL; + PY_INTERFACE_PRECALL; + HRESULT hr = pIFV->SelectAndPositionItems(1, (LPCITEMIDLIST *)&pidl, &pt, dwFlags); + PyObject_FreePIDL(pidl); + PY_INTERFACE_POSTCALL; + if (FAILED(hr)) + return PyCom_BuildPyException(hr, pIFV, IID_IFolderView); + Py_RETURN_NONE; +} + +// @object PyIFolderView|Description of the interface +static struct PyMethodDef PyIFolderView_methods[] = { + {"GetCurrentViewMode", PyIFolderView::GetCurrentViewMode, + 1}, // @pymeth GetCurrentViewMode|Description of GetCurrentViewMode + {"SetCurrentViewMode", PyIFolderView::SetCurrentViewMode, + 1}, // @pymeth SetCurrentViewMode|Description of SetCurrentViewMode + {"GetFolder", PyIFolderView::GetFolder, 1}, // @pymeth GetFolder|Description of GetFolder + {"Item", PyIFolderView::Item, 1}, // @pymeth Item|Description of Item + {"ItemCount", PyIFolderView::ItemCount, 1}, // @pymeth ItemCount|Description of ItemCount + {"Items", PyIFolderView::Items, 1}, // @pymeth Items|Description of Items + {"GetSelectionMarkedItem", PyIFolderView::GetSelectionMarkedItem, + 1}, // @pymeth GetSelectionMarkedItem|Description of GetSelectionMarkedItem + {"GetFocusedItem", PyIFolderView::GetFocusedItem, 1}, // @pymeth GetFocusedItem|Description of GetFocusedItem + {"GetItemPosition", PyIFolderView::GetItemPosition, 1}, // @pymeth GetItemPosition|Description of GetItemPosition + {"GetSpacing", PyIFolderView::GetSpacing, 1}, // @pymeth GetSpacing|Description of GetSpacing + {"GetDefaultSpacing", PyIFolderView::GetDefaultSpacing, + 1}, // @pymeth GetDefaultSpacing|Description of GetDefaultSpacing + {"GetAutoArrange", PyIFolderView::GetAutoArrange, 1}, // @pymeth GetAutoArrange|Description of GetAutoArrange + {"SelectItem", PyIFolderView::SelectItem, 1}, // @pymeth SelectItem|Description of SelectItem + {"SelectAndPositionItems", PyIFolderView::SelectAndPositionItems, + 1}, // @pymeth SelectAndPositionItems|Description of SelectAndPositionItems + {"SelectAndPositionItem", PyIFolderView::SelectAndPositionItem, + 1}, // @pymeth SelectAndPositionItem|Description of SelectAndPositionItem + {NULL}}; + +PyComTypeObject PyIFolderView::type("PyIFolderView", &PyIUnknown::type, sizeof(PyIFolderView), PyIFolderView_methods, + GET_PYCOM_CTOR(PyIFolderView)); diff --git a/com/win32comext/shell/src/PyIFolderView.h b/com/win32comext/shell/src/PyIFolderView.h new file mode 100644 index 0000000000..8dd85384bf --- /dev/null +++ b/com/win32comext/shell/src/PyIFolderView.h @@ -0,0 +1,33 @@ +// This file declares the IFolderView Interface for Python. +// Generated by makegw.py +// --------------------------------------------------- +// +// Interface Declaration + +class PyIFolderView : public PyIUnknown { + public: + MAKE_PYCOM_CTOR(PyIFolderView); + static IFolderView *GetI(PyObject *self); + static PyComTypeObject type; + + // The Python methods + static PyObject *GetCurrentViewMode(PyObject *self, PyObject *args); + static PyObject *SetCurrentViewMode(PyObject *self, PyObject *args); + static PyObject *GetFolder(PyObject *self, PyObject *args); + static PyObject *Item(PyObject *self, PyObject *args); + static PyObject *ItemCount(PyObject *self, PyObject *args); + static PyObject *Items(PyObject *self, PyObject *args); + static PyObject *GetSelectionMarkedItem(PyObject *self, PyObject *args); + static PyObject *GetFocusedItem(PyObject *self, PyObject *args); + static PyObject *GetItemPosition(PyObject *self, PyObject *args); + static PyObject *GetSpacing(PyObject *self, PyObject *args); + static PyObject *GetDefaultSpacing(PyObject *self, PyObject *args); + static PyObject *GetAutoArrange(PyObject *self, PyObject *args); + static PyObject *SelectItem(PyObject *self, PyObject *args); + static PyObject *SelectAndPositionItems(PyObject *self, PyObject *args); + static PyObject *SelectAndPositionItem(PyObject *self, PyObject *args); + + protected: + PyIFolderView(IUnknown *pdisp); + ~PyIFolderView(); +}; diff --git a/com/win32comext/shell/src/shell.cpp b/com/win32comext/shell/src/shell.cpp index 1c5b15f43c..09a48dab9f 100644 --- a/com/win32comext/shell/src/shell.cpp +++ b/com/win32comext/shell/src/shell.cpp @@ -74,6 +74,7 @@ generates Windows .hlp files. #include "PyITransferAdviseSink.h" #include "PyIShellItemResources.h" #include "PyIEnumResources.h" +#include "PyIFolderView.h" #include "PyIRelatedItem.h" // Next 4 all derived from IRelatedItem #include "PyIDisplayItem.h" @@ -595,9 +596,7 @@ BOOL PyObject_AsCMINVOKECOMMANDINFO(PyObject *ob, CMINVOKECOMMANDINFO *pci) return FALSE; return TRUE; } -void PyObject_FreeCMINVOKECOMMANDINFO(CMINVOKECOMMANDINFO *pci) { - PyWinObject_FreeResourceIdA((char *)pci->lpVerb); -} +void PyObject_FreeCMINVOKECOMMANDINFO(CMINVOKECOMMANDINFO *pci) { PyWinObject_FreeResourceIdA((char *)pci->lpVerb); } static PyObject *PyString_FromMaybeNullString(const char *sz) { @@ -947,12 +946,12 @@ BOOL PyObject_AsSHFILEOPSTRUCT(PyObject *ob, SHFILEOPSTRUCT *p) memset(p, 0, sizeof(*p)); if (!PyArg_ParseTuple( ob, "OiOO|iOO", - &obhwnd, // @tupleitem 0|int|hwnd|Handle of window in which to display status messages - &p->wFunc, // @tupleitem 1|int|wFunc|One of the shellcon.FO_* values - &obFrom, // @tupleitem 2|string|From|String containing source file name(s) separated by nulls - &obTo, // @tupleitem 3|string|To|String containing destination file name(s) separated by nulls, can be - // None - &p->fFlags, // @tupleitem 4|int|flags|Combination of shellcon.FOF_* flags. Default=0 + &obhwnd, // @tupleitem 0|int|hwnd|Handle of window in which to display status messages + &p->wFunc, // @tupleitem 1|int|wFunc|One of the shellcon.FO_* values + &obFrom, // @tupleitem 2|string|From|String containing source file name(s) separated by nulls + &obTo, // @tupleitem 3|string|To|String containing destination file name(s) separated by nulls, can be + // None + &p->fFlags, // @tupleitem 4|int|flags|Combination of shellcon.FOF_* flags. Default=0 &obNameMappings, // @tupleitem 5|None|NameMappings|Maps input file names to their new names. This is // actually output, and must be None if passed as input. Default=None &obProgressTitle)) // @tupleitem 6|string|ProgressTitle|Title for progress dialog (flags must contain @@ -1275,16 +1274,20 @@ static PyObject *PySHGetSpecialFolderPath(PyObject *self, PyObject *args) return PyWinObject_FromWCHAR(buf); } -// @pymethod string|shell|SHGetKnownFolderPath|Retrieves the full path of a known folder identified by the folder's KNOWNFOLDERID. +// @pymethod string|shell|SHGetKnownFolderPath|Retrieves the full path of a known folder identified by the folder's +// KNOWNFOLDERID. static PyObject *PySHGetKnownFolderPath(PyObject *self, PyObject *args) { PyObject *obfid; PyObject *obHandle = Py_None; long flags = 0; if (!PyArg_ParseTuple(args, "O|lO:SHGetKnownFolderPath", - &obfid, // @pyparm |fid||One of the KNOWNFOLDERID constants. - &flags, // @pyparm int|flags|0|Flags that specify special retrieval options. This value can be 0; otherwise, one or more of the KNOWN_FOLDER_FLAG values. - &obHandle)) // @pyparm |token|None|An access token that represents a particular user. If this parameter is NULL, which is the most common usage, the function requests the known folder for the current user. + &obfid, // @pyparm |fid||One of the KNOWNFOLDERID constants. + &flags, // @pyparm int|flags|0|Flags that specify special retrieval options. This value can + // be 0; otherwise, one or more of the KNOWN_FOLDER_FLAG values. + &obHandle)) // @pyparm |token|None|An access token that represents a particular + // user. If this parameter is NULL, which is the most common usage, the function + // requests the known folder for the current user. return NULL; KNOWNFOLDERID fid; if (!PyWinObject_AsIID(obfid, &fid)) @@ -3651,7 +3654,8 @@ static struct PyMethodDef shell_methods[] = { 1}, // @pymeth SHGetSpecialFolderPath|Retrieves the path of a special folder. {"SHGetSpecialFolderLocation", PySHGetSpecialFolderLocation, 1}, // @pymeth SHGetSpecialFolderLocation|Retrieves the of a special folder. - {"SHGetKnownFolderPath", PySHGetKnownFolderPath, 1}, // @pymeth SHGetKnownFolderPath|Retrieves the full path of a known folder identified by the folder's KNOWNFOLDERID. + {"SHGetKnownFolderPath", PySHGetKnownFolderPath, 1}, // @pymeth SHGetKnownFolderPath|Retrieves the full path of a + // known folder identified by the folder's KNOWNFOLDERID. {"SHAddToRecentDocs", PySHAddToRecentDocs, 1}, // @pymeth SHAddToRecentDocs|Adds a document to the shell's list of recently used documents or clears all // documents from the list. The user gains access to the list through the Start menu of the Windows taskbar. @@ -3807,6 +3811,7 @@ static const PyCom_InterfaceSupportInfo g_interfaceSupportData[] = { PYCOM_INTERFACE_FULL(TransferAdviseSink), PYCOM_INTERFACE_FULL(ShellItemResources), PYCOM_INTERFACE_FULL(EnumResources), + PYCOM_INTERFACE_CLIENT_ONLY(FolderView), PYCOM_INTERFACE_FULL(RelatedItem), PYCOM_INTERFACE_FULL(TransferMediumItem), // based on IRelatedItem with no extra methods PYCOM_INTERFACE_FULL(CurrentItem), // based on IRelatedItem with no extra methods 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/isapi/threaded_extension.py b/isapi/threaded_extension.py index daa2ebd9ae..ecf24c8460 100644 --- a/isapi/threaded_extension.py +++ b/isapi/threaded_extension.py @@ -166,10 +166,11 @@ def HandleDispatchError(self, ecb): list = traceback.format_tb( exc_tb, limit ) + traceback.format_exception_only(exc_typ, exc_val) + bold = list.pop() print( "
{}{}
".format( - cgi.escape("".join(list[:-1])), - cgi.escape(list[-1]), + cgi.escape("".join(list)), + cgi.escape(bold), ), file=ecb, ) 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..1dd371af23 --- /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 + // 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", +} diff --git a/pywin32_postinstall.py b/pywin32_postinstall.py index 19b971ba44..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) -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 @@ -72,7 +67,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 +93,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 @@ -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. diff --git a/setup.py b/setup.py index ee28e9a91c..4181b0f4d0 100644 --- a/setup.py +++ b/setup.py @@ -1729,6 +1729,7 @@ def finalize_options(self): {shell}/PyIExtractImage.cpp {shell}/PyIFileOperation.cpp {shell}/PyIFileOperationProgressSink.cpp + {shell}/PyIFolderView.cpp {shell}/PyIIdentityName.cpp {shell}/PyIInputObject.cpp {shell}/PyIKnownFolder.cpp diff --git a/win32/Lib/pywin32_testutil.py b/win32/Lib/pywin32_testutil.py index d512404972..4dc33d1ae8 100644 --- a/win32/Lib/pywin32_testutil.py +++ b/win32/Lib/pywin32_testutil.py @@ -207,16 +207,9 @@ class TestSkipped(Exception): pass -# This appears to have been "upgraded" to non-private in 3.11 -try: - TextTestResult = unittest._TextTestResult -except AttributeError: - TextTestResult = unittest.TextTestResult - - # The 'TestResult' subclass that records the failures and has the special # handling for the TestSkipped exception. -class TestResult(TextTestResult): +class TestResult(unittest.TextTestResult): def __init__(self, *args, **kw): super().__init__(*args, **kw) self.skips = {} # count of skips for each reason. 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 9b909898ec..8f065bb8eb 100644 --- a/win32/Lib/win32rcparser.py +++ b/win32/Lib/win32rcparser.py @@ -7,8 +7,7 @@ This is a parser for Windows .rc files, which are text files which define dialogs and other Windows UI resources. """ -__author__ = "Adam Walker" -__version__ = "0.11" +from __future__ import annotations import os import pprint @@ -19,6 +18,9 @@ import commctrl import win32con +__author__ = "Adam Walker" +__version__ = "0.11" + _controlMap = { "DEFPUSHBUTTON": 0x80, "PUSHBUTTON": 0x80, @@ -171,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 = "" @@ -654,7 +656,7 @@ def GenerateFrozenResource(rc_name, output_name, h_name=None): else: filename = sys.argv[1] if "-v" in sys.argv: - RCParser.debugEnabled = 1 + RCParser.debugEnabled = True print("Dumping all resources in '%s'" % filename) resources = Parse(filename) for id, ddef in resources.dialogs.items(): diff --git a/win32/Lib/win32serviceutil.py b/win32/Lib/win32serviceutil.py index 73423ea031..fd4be7a5e9 100644 --- a/win32/Lib/win32serviceutil.py +++ b/win32/Lib/win32serviceutil.py @@ -442,8 +442,6 @@ def ControlService(serviceName, code, machine=None): def __FindSvcDeps(findName): - if isinstance(findName, pywintypes.UnicodeType): - findName = str(findName) dict = {} k = win32api.RegOpenKey( win32con.HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Services" diff --git a/win32/Lib/win32timezone.py b/win32/Lib/win32timezone.py index 8af89c1df5..dc41514e03 100644 --- a/win32/Lib/win32timezone.py +++ b/win32/Lib/win32timezone.py @@ -231,7 +231,7 @@ datetime.datetime(2011, 11, 6, 1, 0, tzinfo=TimeZoneInfo('Pacific Standard Time')) """ -__author__ = "Jason R. Coombs " +from __future__ import annotations import datetime import logging @@ -243,13 +243,15 @@ import win32api +__author__ = "Jason R. Coombs " + log = logging.getLogger(__file__) # A couple of objects for working with objects as if they were native C-type # structures. class _SimpleStruct: - _fields_ = None # 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/VersionStamp/BrandProject.py b/win32/scripts/VersionStamp/BrandProject.py index 2612dbc94b..9fc96033c8 100644 --- a/win32/scripts/VersionStamp/BrandProject.py +++ b/win32/scripts/VersionStamp/BrandProject.py @@ -62,11 +62,12 @@ def usage(msg): if __name__ == "__main__": + import getopt + try: - import getopt opts, args = getopt.getopt(sys.argv[1:], "af:d:r") - except getopts.error as msg: + except getopt.GetoptError as msg: usage(msg) bAuto = bRebrand = 0 stampFiles = [] 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 diff --git a/win32/src/PyWinTypesmodule.cpp b/win32/src/PyWinTypesmodule.cpp index a6a37318fb..3dfaefc397 100644 --- a/win32/src/PyWinTypesmodule.cpp +++ b/win32/src/PyWinTypesmodule.cpp @@ -334,16 +334,6 @@ PyObject *PyWin_SetBasicCOMError(HRESULT hr) return NULL; } -// @pymethod string|pywintypes|Unicode|Creates a new Unicode object -PYWINTYPES_EXPORT PyObject *PyWin_NewUnicode(PyObject *self, PyObject *args) -{ - char *string; - int slen; - if (!PyArg_ParseTuple(args, "t#", &string, &slen)) - return NULL; - return PyUnicode_DecodeMBCS(string, slen, NULL); -} - // @pymethod string|pywintypes|UnicodeFromRaw|Creates a new Unicode object from raw binary data static PyObject *PyWin_NewUnicodeFromRaw(PyObject *self, PyObject *args) { @@ -760,7 +750,6 @@ static struct PyMethodDef pywintypes_functions[] = { {"DosDateTimeToTime", PyWin_DosDateTimeToTime, 1}, // @pymeth DosDateTimeToTime|Converts an MS-DOS Date/Time to a standard Time object #endif - {"Unicode", PyWin_NewUnicode, 1}, // @pymeth Unicode|Creates a new string object {"UnicodeFromRaw", PyWin_NewUnicodeFromRaw, 1}, // @pymeth UnicodeFromRaw|Creates a new string object from raw binary data {"IsTextUnicode", PyWin_IsTextUnicode, @@ -960,10 +949,6 @@ PYWIN_MODULE_INIT_FUNC(pywintypes) PYWIN_MODULE_INIT_RETURN_ERROR; ADD_CONSTANT(WAVE_FORMAT_PCM); - // Add a few types. - if (PyDict_SetItemString(dict, "UnicodeType", (PyObject *)&PyUnicode_Type) == -1) - PYWIN_MODULE_INIT_RETURN_ERROR; - if (!_PyWinDateTime_PrepareModuleDict(dict)) PYWIN_MODULE_INIT_RETURN_ERROR; #ifndef NO_PYWINTYPES_IID diff --git a/win32/src/win32apimodule.cpp b/win32/src/win32apimodule.cpp index f58e67e973..eee50d845e 100644 --- a/win32/src/win32apimodule.cpp +++ b/win32/src/win32apimodule.cpp @@ -5177,9 +5177,6 @@ PyObject *PyEnumResourceLanguages(PyObject *self, PyObject *args) return ret; } -// @pymethod |win32api|Unicode|Creates a new Unicode object -PYWINTYPES_EXPORT PyObject *PyWin_NewUnicode(PyObject *self, PyObject *args); - /////////////////// // // Win32 Exception Handler. @@ -6159,7 +6156,7 @@ static struct PyMethodDef win32api_functions[] = { // specified offset into the extra class memory for the window. {"SetClassWord", PySetClassWord, 1}, // @pymeth SetClassWord|Replaces the specified 32-bit (long) value at the // specified offset into the extra class memory for the window. - {"SetClassWord", PySetWindowWord, 1}, // @pymeth SetWindowWord| + {"SetWindowWord", PySetWindowWord, 1}, // @pymeth SetWindowWord| {"SetCursor", PySetCursor, 1}, // @pymeth SetCursor|Set the cursor to the HCURSOR object. // @pymeth SetEnvironmentVariable|Creates, deletes, or changes the value of an environment variable. {"SetEnvironmentVariable", PySetEnvironmentVariableW, 1}, @@ -6182,7 +6179,6 @@ static struct PyMethodDef win32api_functions[] = { {"TerminateProcess", PyTerminateProcess, 1}, // @pymeth TerminateProcess|Terminates a process. {"ToAsciiEx", PyToAsciiEx, 1}, // @pymeth ToAsciiEx|Translates the specified virtual-key code and keyboard state to // the corresponding character or characters. - {"Unicode", PyWin_NewUnicode, 1}, // @pymeth Unicode|Creates a new object {"UpdateResource", PyUpdateResource, 1}, // @pymeth UpdateResource|Updates a resource in a PE file. {"VkKeyScan", PyVkKeyScan, 1}, // @pymeth VkKeyScan|Translates a character to the corresponding virtual-key code and shift state. diff --git a/win32/src/win32clipboardmodule.cpp b/win32/src/win32clipboardmodule.cpp index 2dde670caa..8f552f6917 100644 --- a/win32/src/win32clipboardmodule.cpp +++ b/win32/src/win32clipboardmodule.cpp @@ -805,6 +805,11 @@ static PyObject *py_register_clipboard_format(PyObject *self, PyObject *args) // info. } +static bool isTextFormat(int format) +{ + return ((format == CF_TEXT) || (format == CF_UNICODETEXT) || (format == CF_OEMTEXT)); +} + //***************************************************************************** // // @pymethod int|win32clipboard|SetClipboardData|The SetClipboardData function @@ -845,15 +850,19 @@ static PyObject *py_set_clipboard_data(PyObject *self, PyObject *args) PyWinBufferView pybuf; // In py3k, unicode no longer supports buffer interface if (PyUnicode_Check(obhandle)) { - buf = tmpw = obhandle; if (!tmpw) return NULL; - bufSize = (tmpw.length + 1) * sizeof(WCHAR); + buf = tmpw = obhandle; + if (!tmpw) + return NULL; + bufSize = tmpw.length * sizeof(WCHAR); + if (isTextFormat(format)) + bufSize += sizeof(WCHAR); } else { if (!pybuf.init(obhandle)) return NULL; buf = pybuf.ptr(); bufSize = pybuf.len(); - if (PyBytes_Check(obhandle)) + if ((PyBytes_Check(obhandle)) && (isTextFormat(format))) bufSize++; // size doesnt include nulls! // else assume buffer needs no terminator... } diff --git a/win32/src/win32credmodule.cpp b/win32/src/win32credmodule.cpp index 7fd9f947fc..4856010e15 100644 --- a/win32/src/win32credmodule.cpp +++ b/win32/src/win32credmodule.cpp @@ -62,14 +62,14 @@ BOOL PyWinObject_AsCREDENTIAL_ATTRIBUTE(PyObject *obattr, PCREDENTIAL_ATTRIBUTE if (!PyWinObject_AsWCHAR(obKeyword, &attr->Keyword, FALSE)) { goto done; } - // Handle `Value`: the docs https://docs.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credential_attributew - // say it's an LPBYTE Value (meaning it's just bytes) but then the description says "Data - // associated with the attribute. By convention, if Value is a text string, then Value should - // not include the trailing zero character and should be in UNICODE." + // Handle `Value`: the docs + // https://docs.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credential_attributew say it's an LPBYTE + // Value (meaning it's just bytes) but then the description says "Data associated with the attribute. By convention, + // if Value is a text string, then Value should not include the trailing zero character and should be in UNICODE." if (PyUnicode_Check(obValue)) { Py_ssize_t nchars = PyUnicode_GetLength(obValue); Py_ssize_t nbytes = nchars * sizeof(wchar_t); - attr->ValueSize = nbytes; + attr->ValueSize = nbytes; if (attr->ValueSize == -1) { goto done; } @@ -82,7 +82,8 @@ BOOL PyWinObject_AsCREDENTIAL_ATTRIBUTE(PyObject *obattr, PCREDENTIAL_ATTRIBUTE if (PyUnicode_AsWideChar(obValue, (wchar_t *)attr->Value, nchars) == -1) { goto done; } - } else { + } + else { // Use the buffer API to get bytes if possible. if (!pybuf.init(obValue)) { goto done; @@ -472,7 +473,7 @@ PyObject *PyCredUnmarshalCredential(PyObject *self, PyObject *args, PyObject *kw case CertCredential: ret = Py_BuildValue("kN", credtype, PyBytes_FromStringAndSize((char *)&((PCERT_CREDENTIAL_INFO)credential)->rgbHashOfCert, - CERT_HASH_LENGTH)); + CERT_HASH_LENGTH)); break; // @flag UsernameTargetCredential|Unicode string containing username case UsernameTargetCredential: @@ -622,16 +623,53 @@ PyObject *PyCredReadDomainCredentials(PyObject *self, PyObject *args, PyObject * PyObject *PyCredDelete(PyObject *self, PyObject *args, PyObject *kwargs) { static char *keywords[] = {"TargetName", "Type", "Flags", NULL}; - PyObject *obtargetname, *ret = NULL; + PyObject *obtargetname, *ret = NULL, *obtarget; WCHAR *targetname; DWORD cred_type, flags = 0; - if (!PyArg_ParseTupleAndKeywords( args, kwargs, "Ok|k:CredDelete", keywords, &obtargetname, // @pyparm |TargetName||Target of credential to be deleted &cred_type, // @pyparm int|Type||One of the CRED_TYPE_* values &flags)) // @pyparm int|Flags|0|Reserved, use only 0 - return NULL; + { + char *kwds[] = {"Target", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O:CredDelete", kwds, + &obtarget // @pyparm |Target||Credential to be deleted + )) + return NULL; + PyErr_Clear(); + if (!PyDict_Check(obtarget)) { + PyErr_SetString(PyExc_TypeError, + "First argument must be either a dictionary (no other arguments allowed) or a string " + "(other arguments required)"); + return NULL; + } + obtargetname = PyDict_GetItemString(obtarget, keywords[0]); + if (!obtargetname) { + PyErr_SetString(PyExc_KeyError, keywords[0]); + return NULL; + } + PyObject *val = PyDict_GetItemString(obtarget, keywords[1]); + if (!val) { + PyErr_SetString(PyExc_KeyError, keywords[1]); + return NULL; + } + if (!PyLong_Check(val)) { + PyErr_SetString(PyExc_TypeError, "Argument should be int"); + return NULL; + } + cred_type = PyLong_AsUnsignedLong(val); + val = PyDict_GetItemString(obtarget, keywords[2]); + if (!val) { + PyErr_SetString(PyExc_KeyError, keywords[2]); + return NULL; + } + if (!PyLong_Check(val)) { + PyErr_SetString(PyExc_TypeError, "Argument should be int"); + return NULL; + } + flags = PyLong_AsUnsignedLong(val); + } if (!PyWinObject_AsWCHAR(obtargetname, &targetname, FALSE)) return NULL; if (!CredDelete(targetname, cred_type, flags)) @@ -1169,5 +1207,7 @@ PYWIN_MODULE_INIT_FUNC(win32cred) PyModule_AddIntConstant(module, "CREDUI_MAX_USERNAME_LENGTH", CREDUI_MAX_USERNAME_LENGTH); PyModule_AddIntConstant(module, "CREDUI_MAX_PASSWORD_LENGTH", CREDUI_MAX_PASSWORD_LENGTH); + PyModule_AddIntConstant(module, "CRED_ENUMERATE_ALL_CREDENTIALS", CRED_ENUMERATE_ALL_CREDENTIALS); + PYWIN_MODULE_INIT_RETURN_SUCCESS; } diff --git a/win32/src/win32gui.i b/win32/src/win32gui.i index 3c429c940f..0bb66da960 100644 --- a/win32/src/win32gui.i +++ b/win32/src/win32gui.i @@ -7699,3 +7699,9 @@ BOOLAPI UnregisterDeviceNotification(HANDLE); // @pyparm int|Modifiers||Control keys, combination of win32con.MOD_* // @pyparm int|vk||Virtual key code BOOLAPI RegisterHotKey(HWND, int, UINT, UINT); + +// @pyswig |UnregisterHotKey|Unregisters a previously registeredhotkey +// @pyseeapi UnregisterHotKey +// @pyparm |hWnd||A handle to the window associated with the hot key to be freed +// @pyparm int|id||The identifier of the hot key +BOOLAPI UnregisterHotKey(HWND, int); diff --git a/win32/test/test_win32clipboard.py b/win32/test/test_win32clipboard.py new file mode 100644 index 0000000000..f5d5c28d14 --- /dev/null +++ b/win32/test/test_win32clipboard.py @@ -0,0 +1,60 @@ +# tests for win32gui +import unittest + +import win32clipboard + + +class TestGetSetClipboardData(unittest.TestCase): + + def copyData(self, data, format_): + win32clipboard.OpenClipboard() + ret = None + try: + win32clipboard.SetClipboardData(format_, data) + ret = win32clipboard.GetClipboardData(format_) + finally: + win32clipboard.CloseClipboard() + return ret + + def copyText(self, data, format_): + win32clipboard.OpenClipboard() + ret = None + try: + win32clipboard.SetClipboardText(data, format_) + ret = win32clipboard.GetClipboardData(format_) + finally: + win32clipboard.CloseClipboard() + return ret + + def test_data(self): + test_data = { + "Dummy str": win32clipboard.CF_UNICODETEXT, + b"Dummy bytes text": win32clipboard.CF_TEXT, + b"Dummy\x00\xFF bytes": win32clipboard.CF_DIB, + } + for data, fmt in test_data.items(): + self.assertEqual(data, self.copyData(data, fmt)) + test_data = { + "Dummy str": (win32clipboard.CF_TEXT, win32clipboard.CF_DIB), + b"Dummy\x00\xFF bytes": (win32clipboard.CF_UNICODETEXT,), + } + for data, formats in test_data.items(): + for fmt in formats: + self.assertNotEqual(data, self.copyData(data, fmt)) + + def test_text(self): + test_data = { + "Dummy str": win32clipboard.CF_UNICODETEXT, + b"Dummy bytes": win32clipboard.CF_TEXT, + } + for data, fmt in test_data.items(): + self.assertEqual(data, self.copyText(data, fmt)) + self.assertRaises(ValueError, self.copyText, data, win32clipboard.CF_DIB) + s = "Dummy str" + self.assertEqual( + s.encode(), self.copyText(s, win32clipboard.CF_TEXT) + ) # @TODO - cfati: Do we want this? + + +if __name__ == "__main__": + unittest.main() diff --git a/win32/test/test_win32cred.py b/win32/test/test_win32cred.py new file mode 100644 index 0000000000..e4af181353 --- /dev/null +++ b/win32/test/test_win32cred.py @@ -0,0 +1,73 @@ +import copy +import unittest + +import win32cred + + +class TestCredFunctions(unittest.TestCase): + + def setUp(self): + self.flags = 0 + self.dummy_cred = { + "TargetName": "DumyyUser", + "Type": win32cred.CRED_TYPE_GENERIC, + "Flags": self.flags, + } + + def create_dummy_cred(self): + cred = copy.deepcopy(self.dummy_cred) + cred.update( + { + "Persist": win32cred.CRED_PERSIST_SESSION, + } + ) + try: + win32cred.CredWrite(cred, self.flags) + except Exception as e: + print(e) + + def is_dummy_cred(self): + return ( + len( + [ + e + for e in win32cred.CredEnumerate() + if e["TargetName"] == self.dummy_cred["TargetName"] + ] + ) + == 1 + ) + + def test_creddelete(self): + self.create_dummy_cred() + self.assertTrue(self.is_dummy_cred()) + self.assertIsNone(win32cred.CredDelete(**self.dummy_cred)) + self.assertFalse(self.is_dummy_cred()) + self.create_dummy_cred() + self.assertTrue(self.is_dummy_cred()) + self.assertIsNone(win32cred.CredDelete(*self.dummy_cred.values())) + self.assertFalse(self.is_dummy_cred()) + self.create_dummy_cred() + self.assertTrue(self.is_dummy_cred()) + self.assertIsNone( + win32cred.CredDelete(self.dummy_cred["TargetName"], self.dummy_cred["Type"]) + ) + self.assertFalse(self.is_dummy_cred()) + self.create_dummy_cred() + self.assertTrue(self.is_dummy_cred()) + self.assertIsNone(win32cred.CredDelete(self.dummy_cred)) + self.assertFalse(self.is_dummy_cred()) + self.create_dummy_cred() + self.assertTrue(self.is_dummy_cred()) + self.assertIsNone(win32cred.CredDelete(Target=self.dummy_cred)) + self.assertFalse(self.is_dummy_cred()) + self.create_dummy_cred() + self.assertTrue(self.is_dummy_cred()) + self.assertRaises(TypeError, win32cred.CredDelete, "") + self.assertRaises(KeyError, win32cred.CredDelete, {}) + self.assertRaises(KeyError, win32cred.CredDelete, {"TargetName": ""}) + self.assertRaises( + TypeError, win32cred.CredDelete, {"TargetName": "", "Type": 3.141593} + ) + self.assertIsNone(win32cred.CredDelete(self.dummy_cred)) + self.assertFalse(self.is_dummy_cred())