diff --git a/.gitattributes b/.gitattributes index 2550d38f87..6e87b76d19 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,8 @@ AutoDuck/*.fmt text eol=crlf *.dsp text eol=crlf *.dsw text eol=crlf *.py text eol=crlf +*.pyw text eol=crlf +*.pys text eol=crlf # Files that can't be in UTF-8 encoding com/TestSources/PyCOMTest/PyCOMTest.idl working-tree-encoding=latin1 diff --git a/.github/ISSUE_TEMPLATE/issue_template.md b/.github/ISSUE_TEMPLATE/issue_template.md index efeabf00cb..5b7a4feae2 100644 --- a/.github/ISSUE_TEMPLATE/issue_template.md +++ b/.github/ISSUE_TEMPLATE/issue_template.md @@ -25,7 +25,7 @@ For all bugs, please provide the following information: 3. ... ```python - + ``` ## System information diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b2883ccdcb..2eeca36d17 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -154,8 +154,6 @@ jobs: - run: uv pip install --group=checkers # !cancelled(): Show issues even if the previous steps failed. But still fail the job - - run: pycln . --config=pycln.toml --check - - name: Run Ruff linter uses: astral-sh/ruff-action@v3 if: ${{ !cancelled() }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b4b30496e..a9f6a28e29 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,14 +19,8 @@ repos: args: [--autofix, --indent, "2", --offset, "2", --preserve-quotes] - id: pretty-format-ini args: [--autofix] - - repo: https://github.com/hadialqattan/pycln - rev: v2.4.0 - hooks: - - id: pycln - args: [--config=pycln.toml] - verbose: true - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.0 + rev: v0.15.0 hooks: - id: ruff # Run the linter. args: [--fix] diff --git a/CHANGES.md b/CHANGES.md index ccdba79904..1634cc74ed 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -405,9 +405,9 @@ Build 300, released 2020-11-14 (ie, not when normal params were passed) For example: ```python - arg1 = VARIANT(pythoncom.VT_R4 | pythoncom.VT_BYREF, 2.0) - arg2 = VARIANT(pythoncom.VT_BOOL | pythoncom.VT_BYREF, True) - object.SomeFunction(arg1, arg2) + arg1 = VARIANT(pythoncom.VT_R4 | pythoncom.VT_BYREF, 2.0) + arg2 = VARIANT(pythoncom.VT_BOOL | pythoncom.VT_BYREF, True) + object.SomeFunction(arg1, arg2) ``` after this call, `arg1.value` was actually the value for `arg2`, and diff --git a/Pythonwin/pywin/Demos/openGLDemo.py b/Pythonwin/pywin/Demos/openGLDemo.py index d51c08c978..f97f504a9d 100644 --- a/Pythonwin/pywin/Demos/openGLDemo.py +++ b/Pythonwin/pywin/Demos/openGLDemo.py @@ -136,9 +136,8 @@ def OnSize(self, params): self.oldrect = self.oldrect[0], self.oldrect[1], cx, cy def OnInitialUpdate(self): - self.SetScaleToFitSize( - (100, 100) - ) # or SetScrollSizes() - A Pythonwin requirement + self.SetScaleToFitSize((100, 100)) + # or SetScrollSizes() - A Pythonwin requirement return self._obj_.OnInitialUpdate() # return rc diff --git a/Pythonwin/pywin/default.cfg b/Pythonwin/pywin/default.cfg index 9366e2dfb6..ce79f46ccf 100644 --- a/Pythonwin/pywin/default.cfg +++ b/Pythonwin/pywin/default.cfg @@ -2,7 +2,7 @@ # # The format of this file is very similar to a Windows INI file. # Sections are identified with [Section] lines, but comments -# use the standatd Python # character. Depending on the section, +# use the standard Python # character. Depending on the section, # lines may not be in the standard "key=value" format. # NOTE: You should not need to modify this file. @@ -47,9 +47,9 @@ Ctrl+Shift+F = ViewFixedFont # Auto-complete, call-tips, etc. Alt+/ = <> -Ctrl+Space = <> -( = <> -) = <> +Ctrl+Space = <> +( = <> +) = <> Up = <> Down = <> Left = <> @@ -134,53 +134,59 @@ Ctrl+Shift+Tab = MDIPrev # Add a simple file/class/function simple banner def AddBanner(editor_window, event): + text = editor_window.text + big_line = "#" * 70 + banner = "%s\n## \n## \n## \n%s\n" % (big_line, big_line) - text = editor_window.text - big_line = "#" * 70 - banner = "%s\n## \n## \n## \n%s\n" % (big_line, big_line) + # Insert at the start of the current line. + pos = text.index("insert linestart") - # Insert at the start of the current line. - pos = text.index("insert linestart") + text.undo_block_start() # Allow action to be undone as a single unit. + text.insert(pos, banner) + text.undo_block_stop() - text.undo_block_start() # Allow action to be undone as a single unit. - text.insert(pos, banner) - text.undo_block_stop() - - # Now set the insert point to the middle of the banner. - line, col = [int(s) for s in pos.split(".")] - text.mark_set("insert", "%d.1 lineend" % (line+2, ) ) + # Now set the insert point to the middle of the banner. + line, col = [int(s) for s in pos.split(".")] + text.mark_set("insert", "%d.1 lineend" % (line + 2,)) # Here is a sample event bound to the "Home" key in the # interactive window def InteractiveHome(editor_window, event): - return _DoInteractiveHome(editor_window.text, 0) + return _DoInteractiveHome(editor_window.text, 0) + def InteractiveHomeExtend(editor_window, event): - return _DoInteractiveHome(editor_window.text, 1) + return _DoInteractiveHome(editor_window.text, 1) + def _DoInteractiveHome(text, extend): - import sys - # If Scintilla has an autocomplete window open, then let Scintilla handle it. - if text.edit.SCIAutoCActive(): - return 1 - of_interest = "insert linestart + %d c" % len(sys.ps1) - if not text.compare("insert", "==", of_interest) and \ - text.get("insert linestart", of_interest) in [sys.ps1, sys.ps2]: # Not sys.ps? line - end = of_interest - else: - end = "insert linestart" - - if extend: start = "insert" - else: start = end - text.tag_add("sel", start, end) + import sys + + # If Scintilla has an autocomplete window open, then let Scintilla handle it. + if text.edit.SCIAutoCActive(): + return 1 + of_interest = "insert linestart + %d c" % len(sys.ps1) + if not text.compare("insert", "==", of_interest) and text.get( + "insert linestart", of_interest + ) in [sys.ps1, sys.ps2]: + # Not sys.ps? line + end = of_interest + else: + end = "insert linestart" + + if extend: + start = "insert" + else: + start = end + text.tag_add("sel", start, end) + # From Niki Spahie def AutoFindNext(editor_window, event): - "find selected text or word under cursor" + """find selected text or word under cursor""" - from pywin.scintilla import find - from pywin.scintilla import scintillacon + from pywin.scintilla import find, scintillacon try: sci = editor_window.edit @@ -189,26 +195,29 @@ def AutoFindNext(editor_window, event): find.lastSearch.findText = word find.lastSearch.sel = sci.GetSel() else: - pos = sci.SendScintilla( scintillacon.SCI_GETCURRENTPOS ) - start = sci.SendScintilla( scintillacon.SCI_WORDSTARTPOSITION, pos, 1 ) - end = sci.SendScintilla( scintillacon.SCI_WORDENDPOSITION, pos, 1 ) - word = sci.GetTextRange( start, end ) + pos = sci.SendScintilla(scintillacon.SCI_GETCURRENTPOS) + start = sci.SendScintilla(scintillacon.SCI_WORDSTARTPOSITION, pos, 1) + end = sci.SendScintilla(scintillacon.SCI_WORDENDPOSITION, pos, 1) + word = sci.GetTextRange(start, end) if word: find.lastSearch.findText = word - find.lastSearch.sel = (start,end) + find.lastSearch.sel = (start, end) except Exception: import traceback + traceback.print_exc() find.FindNext() # A couple of generic events. def Beep(editor_window, event): - editor_window.text.beep() + editor_window.text.beep() + def DoNothing(editor_window, event): - pass + pass + def ContinueEvent(editor_window, event): - # Almost an "unbind" - allows Pythonwin/MFC to handle the keystroke - return 1 + # Almost an "unbind" - allows Pythonwin/MFC to handle the keystroke + return 1 diff --git a/Pythonwin/pywin/framework/editor/__init__.py b/Pythonwin/pywin/framework/editor/__init__.py index f237244924..727463a56d 100644 --- a/Pythonwin/pywin/framework/editor/__init__.py +++ b/Pythonwin/pywin/framework/editor/__init__.py @@ -100,6 +100,6 @@ def SetEditorFontOption(option, newValue): SetEditorOption(option, str(newValue)) -from pywin.framework.editor.color.coloreditor import ( # nopycln: import +from pywin.framework.editor.color.coloreditor import ( editorTemplate as editorTemplate, # Adds doc template & Re-export ) diff --git a/Pythonwin/pywin/framework/interact.py b/Pythonwin/pywin/framework/interact.py index c26bf6e829..9541f9c5d9 100644 --- a/Pythonwin/pywin/framework/interact.py +++ b/Pythonwin/pywin/framework/interact.py @@ -14,7 +14,7 @@ import pywin.framework.app import pywin.scintilla.control import pywin.scintilla.formatter -import pywin.scintilla.IDLEenvironment # nopycln: import # Injects fast_readline into the IDLE auto-indent extension +import pywin.scintilla.IDLEenvironment # Injects fast_readline into the IDLE auto-indent extension import win32api import win32clipboard import win32con diff --git a/Pythonwin/pywin/framework/intpydde.py b/Pythonwin/pywin/framework/intpydde.py index 4b2365ab57..9a05142df8 100644 --- a/Pythonwin/pywin/framework/intpydde.py +++ b/Pythonwin/pywin/framework/intpydde.py @@ -9,7 +9,7 @@ import traceback import win32ui -from dde import ( # nopycln: import # Re-exported for intpyapp.py +from dde import ( # PLC0414: Re-exported for intpyapp.py CBF_FAIL_SELFCONNECTIONS as CBF_FAIL_SELFCONNECTIONS, # noqa: PLC0414 CreateConversation as CreateConversation, # noqa: PLC0414 CreateServer, diff --git a/Pythonwin/pywin/scintilla/bindings.py b/Pythonwin/pywin/scintilla/bindings.py index 76b7739e6e..b10e0521a0 100644 --- a/Pythonwin/pywin/scintilla/bindings.py +++ b/Pythonwin/pywin/scintilla/bindings.py @@ -6,8 +6,9 @@ import win32con import win32ui -from . import ( # nopycln: import # Injects fast_readline into the IDLE auto-indent extension - IDLEenvironment, +from . import ( + # https://github.com/astral-sh/ruff/issues/15705 + IDLEenvironment, # noqa: F401 # Injects fast_readline into the IDLE auto-indent extension ) HANDLER_ARGS_GUESS = 0 diff --git a/Pythonwin/pywin/scintilla/view.py b/Pythonwin/pywin/scintilla/view.py index 918d0730e7..35cdd098d4 100644 --- a/Pythonwin/pywin/scintilla/view.py +++ b/Pythonwin/pywin/scintilla/view.py @@ -12,7 +12,7 @@ from pywin.mfc import afxres, docview from . import ( - IDLEenvironment, # nopycln: import # Injects fast_readline into the IDLE auto-indent extension + IDLEenvironment, # Injects fast_readline into the IDLE auto-indent extension bindings, control, scintillacon, @@ -187,14 +187,13 @@ def GetTabWidth(self): def HookHandlers(self): # Create events for all the menu names. for name, val in event_commands: - # handler = lambda id, code, tosend=val, parent=parent: parent.OnCommand(tosend, 0) and 0 + # handler = lambda id, code, tosend=val, parent=parent: parent.OnCommand(tosend, 0) and 0 self.bindings.bind(name, None, cid=val) # Hook commands that do nothing other than send Scintilla messages. for command, reflection in command_reflectors: - handler = ( - lambda id, code, ss=self.SendScintilla, tosend=reflection: ss(tosend) - and 0 + handler = lambda id, code, ss=self.SendScintilla, tosend=reflection: ( + ss(tosend) and 0 ) self.HookCommand(handler, command) diff --git a/Pythonwin/pywin/test/test_pywin.py b/Pythonwin/pywin/test/test_pywin.py index b2802795f5..8b7147d00f 100644 --- a/Pythonwin/pywin/test/test_pywin.py +++ b/Pythonwin/pywin/test/test_pywin.py @@ -289,7 +289,7 @@ def OnPaint(self): w = PaintWnd() w.Create("Test Paint MDI Child") - self.addCleanup(lambda: (o.cnt_ondestroy or w.DestroyWindow())) + self.addCleanup(lambda: o.cnt_ondestroy or w.DestroyWindow()) win32gui.PumpWaitingMessages() dc = w.GetDC() self.assertGreater( @@ -379,7 +379,7 @@ def test_ia(self): def test_docedit(self): import tempfile - import pywin.scintilla.IDLEenvironment # nopycln: import # Injects fast_readline into the IDLE auto-indent extension + import pywin.scintilla.IDLEenvironment # Injects fast_readline into the IDLE auto-indent extension ##doc = pywin.framework.editor.editorTemplate.OpenDocumentFile(None) def t_print(*args): diff --git a/Pythonwin/start_pythonwin.pyw b/Pythonwin/start_pythonwin.pyw index 0a1b2e60ba..47d7a6dd28 100644 --- a/Pythonwin/start_pythonwin.pyw +++ b/Pythonwin/start_pythonwin.pyw @@ -1,13 +1,11 @@ -# A Python file that can be used to start Pythonwin, instead of using -# pythonwin.exe +"""A Python file that can be used to start Pythonwin, instead of using pythonwin.exe""" + import os import sys +import pywin.framework.intpyapp # noqa: F401 # InteractivePythonApp() import win32ui -import pywin.framework.intpyapp # InteractivePythonApp() - -assert pywin.framework.intpyapp # not unused # Pretend this script doesn't exist, or pythonwin tries to edit it sys.argv[:] = sys.argv[1:] or [""] # like PySys_SetArgv(Ex) if sys.path[0] not in ("", ".", os.getcwd()): diff --git a/adodbapi/__init__.py b/adodbapi/__init__.py index 6ab125fe39..ccc63af8ef 100644 --- a/adodbapi/__init__.py +++ b/adodbapi/__init__.py @@ -1,4 +1,3 @@ -# nopycln: file # undecidable cases due to explicit re-exports https://github.com/hadialqattan/pycln/issues/205 """adodbapi - A python DB API 2.0 (PEP 249) interface to Microsoft ADO Copyright (C) 2002 Henrik Ekelund, version 2.1 by Vernon Cole diff --git a/adodbapi/quick_reference.md b/adodbapi/quick_reference.md index 4c332a9e40..8c8fbb00cb 100644 --- a/adodbapi/quick_reference.md +++ b/adodbapi/quick_reference.md @@ -97,19 +97,20 @@ Part of the adodbapi package\'s \_\_init\_\_.py looks something like this: ```python -if sys.version_info < (3,0): # in Python 2, define all symbols, just like before - from apibase import * # using this is bad - from ado_consts import * # using this is worse -else: - # but if the user is running Python 3, then keep the dictionary clean - from apibase import apilevel, threadsafety, paramstyle - from apibase import Warning, Error, InterfaceError, DatabaseError, DataError - from apibase import OperationalError, IntegrityError - from apibase import InternalError, ProgrammingError, NotSupportedError - from apibase import NUMBER, STRING, BINARY, DATETIME, ROWID - -from adodbapi import connect, Connection, __version__ -version = 'adodbapi v' + __version__ +if sys.version_info < (3, 0): # in Python 2, define all symbols, just like before + from apibase import * # using this is bad + from ado_consts import * # using this is worse +else: + # but if the user is running Python 3, then keep the dictionary clean + from apibase import apilevel, threadsafety, paramstyle + from apibase import Warning, Error, InterfaceError, DatabaseError, DataError + from apibase import OperationalError, IntegrityError + from apibase import InternalError, ProgrammingError, NotSupportedError + from apibase import NUMBER, STRING, BINARY, DATETIME, ROWID + +from adodbapi import connect, Connection, __version__ + +version = "adodbapi v" + __version__ ``` Please, use only those last four symbols from adodbapi. All others @@ -124,8 +125,9 @@ As required by the PEP, the simplest way to connect is to use a "data set name ```python -import adodbapi -myConn = adodbapi.connect('myDataSetName') +import adodbapi + +myConn = adodbapi.connect("myDataSetName") ``` Which will work just fine, provided you (or someone) has done all of the @@ -137,15 +139,16 @@ Usually, life is not so simple \... ```python -import adodbapi -myhost = r".\SQLEXPRESS" -mydatabase = "Northwind" -myuser = "guest" -mypassword = "12345678" -connStr = """Provider=SQLOLEDB.1; User ID=%s; Password=%s; - Initial Catalog=%s;Data Source= %s""" -myConnStr = connStr % (myuser, mypassword, mydatabase, myhost) -myConn = adodbapi.connect(myConnStr) +import adodbapi + +myhost = r".\SQLEXPRESS" +mydatabase = "Northwind" +myuser = "guest" +mypassword = "12345678" +connStr = """Provider=SQLOLEDB.1; User ID=%s; Password=%s; + Initial Catalog=%s;Data Source= %s""" +myConnStr = connStr % (myuser, mypassword, mydatabase, myhost) +myConn = adodbapi.connect(myConnStr) ``` The PEP suggests that we should be able to create the connection using @@ -159,11 +162,11 @@ arguments are defined (by standard) as being \"user\", \"password\", \"host\", and \"database\". ```python -connStr = """Provider=SQLOLEDB.1; User ID=%(user)s; -Password=%(password)s;\"Initial Catalog=%(database)s; -Data Source= %(host)s""" - -myConn=adodbapi.connect(connStr, myuser, mypassword, myhost, mydatabase) +connStr = """Provider=SQLOLEDB.1; User ID=%(user)s; +Password=%(password)s;\"Initial Catalog=%(database)s; +Data Source= %(host)s""" + +myConn = adodbapi.connect(connStr, myuser, mypassword, myhost, mydatabase) ``` Which will work. @@ -172,7 +175,9 @@ It would be better documented, however, to use keyword, rather than positional arguments: ```python -myConn = adodbapi.connect(connStr, user=myuser, password=mypassword, host=myhost, database=mydatabase) +myConn = adodbapi.connect( + connStr, user=myuser, password=mypassword, host=myhost, database=mydatabase +) ``` In adodbapi, you may also pass keywords using a dictionary structure, @@ -193,15 +198,17 @@ the dictionary. As an extension, I allow the first (or second) positional argument to be the keyword dictionary. ```python -conn_args = {'host': r".\\SQLEXPRESS", - 'database' : "Northwind", - 'user': "guest", - 'password' : "12345678"} -conn_args['connection_string'] = """Provider=SQLOLEDB.1; - User ID=%(user)s; Password=%(password)s; - Initial Catalog=%(database)s; Data Source= %(host)s""" - -myConn = adodbapi.connect(conn_args) +conn_args = { + "host": r".\\SQLEXPRESS", + "database": "Northwind", + "user": "guest", + "password": "12345678", +} +conn_args["connection_string"] = """Provider=SQLOLEDB.1; + User ID=%(user)s; Password=%(password)s; + Initial Catalog=%(database)s; Data Source= %(host)s""" + +myConn = adodbapi.connect(conn_args) ``` Not pretty, I will admit, but it is about as readable as connection @@ -256,9 +263,9 @@ conn_keys['connection_string'] = "Provider=%(provider)s; ... and ... more ... st I thought it would be handy to let others do a similar thing. so: ```python -conn_keys['macro_getuser'] = 'database' - -conn_keys['connection_string'] = "...stuff...; Initial Catalog=%(database)s; ..." +conn_keys["macro_getuser"] = "database" + +conn_keys["connection_string"] = "...stuff...; Initial Catalog=%(database)s; ..." ``` - macro "auto_security": Build ADO security string automagically. @@ -267,20 +274,20 @@ login security ... otherwise use SQL Server security with "user" and "password". It runs this code: ```python -if macro_name == "auto_security": - if not 'user' in kwargs or not bool(kwargs['user']): - return new_key, 'Integrated Security=SSPI' - return new_key, 'User ID=%(user)s; Password=%(password)s' - - # note that %(user) and %(password) are not substituted here, - # they are put in place to be substituted before being sent to ADO. +if macro_name == "auto_security": + if not "user" in kwargs or not bool(kwargs["user"]): + return new_key, "Integrated Security=SSPI" + return new_key, "User ID=%(user)s; Password=%(password)s" + + # note that %(user) and %(password) are not substituted here, + # they are put in place to be substituted before being sent to ADO. ``` ```python -conn_keys['macro_auto_security'] = ['secure'] -conn_keys['user'] = None # username here for Server Security -conn_keys['password'] = 'xys' # ignored if "user" is blank or undefined -conn_keys['connection_string'] = "\...stuff...; %(secure)s" +conn_keys["macro_auto_security"] = ["secure"] +conn_keys["user"] = None # username here for Server Security +conn_keys["password"] = "xys" # ignored if "user" is blank or undefined +conn_keys["connection_string"] = "\...stuff...; %(secure)s" ``` \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-- @@ -497,10 +504,10 @@ operation strings. Calling .execute() with any other string, or calling For example: cursor.executemany() is programmed internally like: ```python -def executemany(self, operation, sequence_of_parameter_sequences): - self.prepare(operation) - for params in sequence_of_parameter_sequences: - self.execute(self.command, params) +def executemany(self, operation, sequence_of_parameter_sequences): + self.prepare(operation) + for params in sequence_of_parameter_sequences: + self.execute(self.command, params) ``` - .get\_returned\_parameters() # some providers will not return the (modified) parameter list, nor the @@ -552,9 +559,9 @@ mark where the parameters should go. The programmer then passes the parameters (as as sequence) in the correct order for the `?`s. ```python -sql = "UPDATE cheese SET qtyonhand = ? WHERE name = ?" -args = [0, 'MUNSTER'] -crsr.execute(sql, args) +sql = "UPDATE cheese SET qtyonhand = ? WHERE name = ?" +args = [0, "MUNSTER"] +crsr.execute(sql, args) ``` **\'format\'** is used my many database engines, and blindly expected by @@ -565,9 +572,9 @@ Again, the programmer supplies the parameters (as a sequence) in the correct order. ```python -sql = "UPDATE cheese SET qtyonhand = %s WHERE name = %s" -args = [0, 'MUNSTER'] -crsr.execute(sql, args) +sql = "UPDATE cheese SET qtyonhand = %s WHERE name = %s" +args = [0, "MUNSTER"] +crsr.execute(sql, args) ``` **\'named\'** is used by Oracle, and is superior because it eliminates @@ -580,9 +587,9 @@ example, the syntax looks needlessly complex, but in queries which take a dozen or more parameters, it really helps. ```python -sql = "UPDATE cheese SET qtyonhand = :qty WHERE name = :prodname" -args = {'qty' : 0, 'prodname' : 'MUNSTER'} -crsr.execute(sql, args) +sql = "UPDATE cheese SET qtyonhand = :qty WHERE name = :prodname" +args = {"qty": 0, "prodname": "MUNSTER"} +crsr.execute(sql, args) ``` **\'pyformat\'** also takes a dictionary of arguments, but uses a syntax @@ -647,30 +654,30 @@ sequence-like "Row" object. It can be indexed and sliced like a list. This is the PEP standard method: ```python -crsr.execute("SELECT prodname, price, qtyonhand FROM cheese") -row = crsr.fetchone() -while row: - value = row[1] * row[2] - print("Your {:10s} is worth {:10.2f}".format(row[0], value)) - row = crsr.fetchone() # returns None when no data remains +crsr.execute("SELECT prodname, price, qtyonhand FROM cheese") +row = crsr.fetchone() +while row: + value = row[1] * row[2] + print("Your {:10s} is worth {:10.2f}".format(row[0], value)) + row = crsr.fetchone() # returns None when no data remains ``` As an extension, a Row object can also be indexed by column name: ```python -crsr.execute("SELECT prodname, price, qtyonhand FROM cheese") -for row in crsr: # note extension: using crsr as an iterator - value = row["price"] * row["qtyonhand"] - print("Your {:10s} is worth {:10.2f}".format(row["prodname"], value)) +crsr.execute("SELECT prodname, price, qtyonhand FROM cheese") +for row in crsr: # note extension: using crsr as an iterator + value = row["price"] * row["qtyonhand"] + print("Your {:10s} is worth {:10.2f}".format(row["prodname"], value)) ``` But, _really_ lazy programmers, like me, use the column names as attributes: ```python -crsr.execute("SELECT prodname, price, qtyonhand FROM cheese") -for row in crsr: - value = row.price * row.qtyonhand - print("Your {:10s} is worth {:10.2f}".format(row.prodname, value)) +crsr.execute("SELECT prodname, price, qtyonhand FROM cheese") +for row in crsr: + value = row.price * row.qtyonhand + print("Your {:10s} is worth {:10.2f}".format(row.prodname, value)) ``` Now, isn't that easier to read and understand? @@ -706,33 +713,34 @@ mapping for each member of the sequence with the same value. So, to change several data retrieval functions: ```python -import adodbapi.ado_consts as adc -import adodbapi.apibase as api -conn.variantConversions = api.variantConversions # MAGIC: will make a copy. - -conn.variantConversions[(adc.adTinyInt, adc.adChar)] = myByteFunc +import adodbapi.ado_consts as adc +import adodbapi.apibase as api + +conn.variantConversions = api.variantConversions # MAGIC: will make a copy. + +conn.variantConversions[(adc.adTinyInt, adc.adChar)] = myByteFunc ``` which will be equivalent to: ```python -conn.variantConversions[adc.adTinyInt] = myByteFunc -conn.variantConversions[adc.adChar] = myByteFunc +conn.variantConversions[adc.adTinyInt] = myByteFunc +conn.variantConversions[adc.adChar] = myByteFunc ``` Also, there is a supply of sequences used to initialize to default map, in module adodbapi.apibase. For example: ```python -adoApproximateNumericTypes = (adc.adDouble, adc.adSingle) +adoApproximateNumericTypes = (adc.adDouble, adc.adSingle) ``` If I wish to retrieve all floating point values differently, I might use: ```python -conn.variantConversions = api.variantConversions -conn.variantConversions[api.adoApproximateNumericTypes] = myFloatFunc +conn.variantConversions = api.variantConversions +conn.variantConversions[api.adoApproximateNumericTypes] = myFloatFunc ``` \-\-\-\-\-\-- @@ -745,14 +753,14 @@ You may override the conversion for any column by altering the function for that column: ```python -crsr.conversions[4] = myFunc # change the reader for the fifth column -crsr.fetchone() +crsr.conversions[4] = myFunc # change the reader for the fifth column +crsr.fetchone() ``` To do this by column name, use: ```python -crsr.conversions[crsr.columnNames['mycolumn']] = myFunc +crsr.conversions[crsr.columnNames["mycolumn"]] = myFunc ``` The Examples folder diff --git a/adodbapi/test/adodbapitest.py b/adodbapi/test/adodbapitest.py index 597d845e41..a80c195b09 100644 --- a/adodbapi/test/adodbapitest.py +++ b/adodbapi/test/adodbapitest.py @@ -81,9 +81,8 @@ def testDefaultErrorHandlerConnection(self): assert conn.messages[0][0] == api.ProgrammingError def testOwnErrorHandlerConnection(self): - mycallable = ( - lambda connection, cursor, errorclass, errorvalue: 1 - ) # does not raise anything + # does not raise anything + mycallable = lambda connection, cursor, errorclass, errorvalue: 1 conn = self.getConnection() conn.errorhandler = mycallable conn.close() @@ -112,9 +111,8 @@ def testDefaultErrorHandlerCursor(self): assert crsr.messages[0][0] == api.DatabaseError def testOwnErrorHandlerCursor(self): - mycallable = ( - lambda connection, cursor, errorclass, errorvalue: 1 - ) # does not raise anything + # does not raise anything + mycallable = lambda connection, cursor, errorclass, errorvalue: 1 crsr = self.getConnection().cursor() crsr.errorhandler = mycallable crsr.execute("SELECT abbtytddrf FROM dasdasd") diff --git a/com/win32com/client/combrowse.py b/com/win32com/client/combrowse.py index 8ba479d404..46d0fef2af 100644 --- a/com/win32com/client/combrowse.py +++ b/com/win32com/client/combrowse.py @@ -188,8 +188,6 @@ def GetBitmapColumn(self): class HLIRegisteredTypeLibrary(HLICOM): def GetSubList(self): - import os - clsidstr, versionStr = self.myobject collected = [] helpPath = "" diff --git a/com/win32com/client/dynamic.py b/com/win32com/client/dynamic.py index 3738da9031..655f2e39c3 100644 --- a/com/win32com/client/dynamic.py +++ b/com/win32com/client/dynamic.py @@ -389,10 +389,9 @@ def _get_good_object_(self, ob, userName=None, ReturnCLSID=None): elif isinstance(ob, tuple): return tuple( map( - lambda o, - s=self, - oun=userName, - rc=ReturnCLSID: s._get_good_single_object_(o, oun, rc), + lambda o, s=self, oun=userName, rc=ReturnCLSID: ( + s._get_good_single_object_(o, oun, rc) + ), ob, ) ) diff --git a/com/win32com/client/makepy.py b/com/win32com/client/makepy.py index 7bf6f2a4b1..3ab2a65a2f 100644 --- a/com/win32com/client/makepy.py +++ b/com/win32com/client/makepy.py @@ -152,8 +152,8 @@ def LogWarning(self, desc): class GUIProgress(SimpleProgress): def __init__(self, verboseLevel): - # Import some modules we need to we can trap failure now. - import pywin # nopycln: import + # Import some modules we need so we can trap failure now. + import pywin # noqa: F401 import win32ui SimpleProgress.__init__(self, verboseLevel) diff --git a/com/win32com/server/dispatcher.py b/com/win32com/server/dispatcher.py index 7aba2cb22c..ae4829a187 100644 --- a/com/win32com/server/dispatcher.py +++ b/com/win32com/server/dispatcher.py @@ -232,7 +232,7 @@ def _trace_(self, *args): try: - import win32trace # nopycln: import # Check for win32traceutil w/o importing it + import win32trace # noqa: F401 # Check for win32traceutil w/o importing it DefaultDebugDispatcher: type[DispatcherTrace] = DispatcherWin32trace except ImportError: # no win32trace module - just use a print based one. diff --git a/com/win32comext/adsi/__init__.py b/com/win32comext/adsi/__init__.py index 60ca6c4b77..8da609b543 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 # Re-export everything from win32comext/adsi/adsi.pyd +from .adsi import * # Re-export everything from win32comext/adsi/adsi.pyd LCID = 0 diff --git a/com/win32comext/adsi/demos/test.py b/com/win32comext/adsi/demos/test.py index 5e7467c223..ec32f12bfc 100644 --- a/com/win32comext/adsi/demos/test.py +++ b/com/win32comext/adsi/demos/test.py @@ -259,7 +259,7 @@ def main(): break else: print("Test '%s' unknown - skipping" % arg) - if not len(dotests): + if not dotests: print("Nothing to do!") usage(tests) for test in dotests: diff --git a/com/win32comext/axscript/demos/client/wsh/excel.pys b/com/win32comext/axscript/demos/client/wsh/excel.pys index 67094cd423..b10a80d731 100644 --- a/com/win32comext/axscript/demos/client/wsh/excel.pys +++ b/com/win32comext/axscript/demos/client/wsh/excel.pys @@ -1,18 +1,20 @@ -#app=WScript.Application -#app._print_details_() # Use this to see what Python knows about a COM object. - +# app=WScript.Application +# app._print_details_() # Use this to see what Python knows about a COM object. g_index = 1 + + # A procedure, using a global. -def Show(desc, value = None): - global g_index # Need global for g_index, as I locally assign. - # No global needed to "xl" object, as only referenced. - # Also note "xl" is assigned later in the script - ie, Python is very late bound. - xl.Cells(g_index, 1).Value = desc - if value: xl.Cells(g_index, 2).Value = value - g_index = g_index + 1 +def Show(desc, value=None): + global g_index # Need global for g_index, as I locally assign. + # No global needed to "xl" object, as only referenced. + # Also note "xl" is assigned later in the script - ie, Python is very late bound. + xl.Cells(g_index, 1).Value = desc + if value: + xl.Cells(g_index, 2).Value = value + g_index = g_index + 1 + xl = WScript.CreateObject("Excel.Application") -import sys xl.Visible = 1 xl.Workbooks.Add() @@ -27,5 +29,5 @@ Show("State of Interactive Mode", WScript.Interactive) Show("All script arguments:") args = WScript.Arguments -for i in range(0,args.Count()): - Show("Arg %d" % i, args(i)) +for i in range(0, args.Count()): + Show("Arg %d" % i, args(i)) diff --git a/com/win32comext/axscript/demos/client/wsh/registry.pys b/com/win32comext/axscript/demos/client/wsh/registry.pys index 76e91d8fe1..265450b213 100644 --- a/com/win32comext/axscript/demos/client/wsh/registry.pys +++ b/com/win32comext/axscript/demos/client/wsh/registry.pys @@ -1,4 +1,4 @@ -""" Windows Script Host Sample Script +"""Windows Script Host Sample Script ' Ported to Python ' ' ------------------------------------------------------------------------ @@ -21,24 +21,24 @@ WshShell.Popup("Create key HKCU\\Foo with value 'Top level key'") WshShell.RegWrite("HKCU\\Foo\\", "Top level key") WshShell.Popup("Create key HKCU\\Foo\\Bar with value 'Second level key'") -WshShell.RegWrite( "HKCU\\Foo\\Bar\\", "Second level key") +WshShell.RegWrite("HKCU\\Foo\\Bar\\", "Second level key") -WshShell.Popup ("Set value HKCU\\Foo\\Value to REG_SZ 1") -WshShell.RegWrite( "HKCU\\Foo\\Value", 1) +WshShell.Popup("Set value HKCU\\Foo\\Value to REG_SZ 1") +WshShell.RegWrite("HKCU\\Foo\\Value", 1) -WshShell.Popup ("Set value HKCU\\Foo\\Bar to REG_DWORD 2") -WshShell.RegWrite ("HKCU\\Foo\\Bar", 2, "REG_DWORD") +WshShell.Popup("Set value HKCU\\Foo\\Bar to REG_DWORD 2") +WshShell.RegWrite("HKCU\\Foo\\Bar", 2, "REG_DWORD") -WshShell.Popup ("Set value HKCU\\Foo\\Bar to REG_EXPAND_SZ '3'") -WshShell.RegWrite ("HKCU\\Foo\\Bar\\Baz", "%SystemRoot%\\Foo") +WshShell.Popup("Set value HKCU\\Foo\\Bar to REG_EXPAND_SZ '3'") +WshShell.RegWrite("HKCU\\Foo\\Bar\\Baz", "%SystemRoot%\\Foo") -WshShell.Popup ("Delete value HKCU\\Foo\\Bar\\Baz") -WshShell.RegDelete ("HKCU\\Foo\\Bar\\Baz") +WshShell.Popup("Delete value HKCU\\Foo\\Bar\\Baz") +WshShell.RegDelete("HKCU\\Foo\\Bar\\Baz") -WshShell.Popup ("Delete key HKCU\\Foo\\Bar") -WshShell.RegDelete ("HKCU\\Foo\\Bar\\") +WshShell.Popup("Delete key HKCU\\Foo\\Bar") +WshShell.RegDelete("HKCU\\Foo\\Bar\\") -WshShell.Popup ("Delete key HKCU\\Foo") -WshShell.RegDelete ("HKCU\\Foo\\") +WshShell.Popup("Delete key HKCU\\Foo") +WshShell.RegDelete("HKCU\\Foo\\") -WScript.Echo ("Done") +WScript.Echo("Done") diff --git a/com/win32comext/axscript/demos/client/wsh/test.pys b/com/win32comext/axscript/demos/client/wsh/test.pys index a3fcaf5006..d1a3d9f08b 100644 --- a/com/win32comext/axscript/demos/client/wsh/test.pys +++ b/com/win32comext/axscript/demos/client/wsh/test.pys @@ -2,13 +2,14 @@ # Test "Restricted Execution" (ie, IObjectSafety). # This will fail if in a "restricted execution" environment, but -# will silenty do nothing of not restricted. This same line in an MSIE +# will silently do nothing of not restricted. This same line in an MSIE # script would cause an exception. print("Importing win32api...") -import win32api -if 1==1: - print("Hi") +import win32api # noqa: F401 + +if 1 == 1: + print("Hi") WScript.Echo("Hello from WScript") -#fail +# fail diff --git a/com/win32comext/axscript/test/debugTest.pys b/com/win32comext/axscript/test/debugTest.pys index 8ceea0980b..9abc57623c 100644 --- a/com/win32comext/axscript/test/debugTest.pys +++ b/com/win32comext/axscript/test/debugTest.pys @@ -1,16 +1,17 @@ def Function(i): - Test.Echo(i) + Test.Echo(i) + print(dir()) -a=1 -b=a -c=b # And here is a comment -d="A string" +a = 1 +b = a +c = b # And here is a comment +d = "A string" print(a) Test.echo("Hello from Python") for i in range(2): - Function(i) + Function(i) a = """\ A multi-line string! """ diff --git a/com/win32comext/mapi/emsabtags.py b/com/win32comext/mapi/emsabtags.py index 1e90a7d8c0..43e8eb6a01 100644 --- a/com/win32comext/mapi/emsabtags.py +++ b/com/win32comext/mapi/emsabtags.py @@ -1,4 +1,4 @@ -# nopycln: file # Re-exporting many constants +# ruff: noqa: F401 # Re-exporting many constants # Converted "manually" from EMSABTAG.H from .mapitags import ( PROP_TAG, diff --git a/com/win32comext/propsys/test/testpropsys.py b/com/win32comext/propsys/test/testpropsys.py index de831970e9..356d3e6732 100644 --- a/com/win32comext/propsys/test/testpropsys.py +++ b/com/win32comext/propsys/test/testpropsys.py @@ -1,4 +1,4 @@ -from win32com.propsys import propsys, pscon # nopycln: import +from win32com.propsys import propsys, pscon print("propsys was imported (sorry - that is the extent of the tests,") print("but see the shell folder_view demo, which uses this module)") diff --git a/pycln.toml b/pycln.toml deleted file mode 100644 index c0d161e242..0000000000 --- a/pycln.toml +++ /dev/null @@ -1,13 +0,0 @@ -[tool.pycln] -all = true -skip_imports = [ - # Modules with known side-effects - "coloreditor", # Adds doc template - "IDLEenvironment", # Injects fast_readline into the IDLE auto-indent extension - "pythoncom", # pythoncomXX.dll loader - "pywintypes", # pywintypesXX.dll loader - "win32com", # Sets pythoncom.frozen and adds modules to path based on the registry - "win32traceutil", # Redirects output to win32trace remote collector - "win32ui", # Must always be imported before dde -] -# Note: com/win32com/client/gencache.py does read files but only to fill in it's own dicts from cache diff --git a/pyproject.toml b/pyproject.toml index 93e6776dcd..bc2fa6afb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,7 @@ build-backend = "setuptools.build_meta" [dependency-groups] checkers = [ # Keep these in sync with .pre-commit-config.yaml "clang-format ==18.1.8", - "pycln ==2.4.0", - "ruff ==0.11.0", + "ruff ==0.15.0", ] type-checkers = [ "types-setuptools", diff --git a/ruff.toml b/ruff.toml index e96c2eafef..583647fd3e 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,4 +1,10 @@ target-version = "py39" # Target the oldest supported version +extend-include = [ + "*.pyw", # Will be included by default in Ruff eventually + "*.pys", # PythonScript (Windows Script Host) + # Can be uncommented to format the [Extensions] section + # "**/default.cfg", +] # This file is not UTF-8 extend-exclude = ["Pythonwin/pywin/test/_dbgscript.py"] @@ -6,9 +12,11 @@ extend-exclude = ["Pythonwin/pywin/test/_dbgscript.py"] line-ending = "cr-lf" [lint] +future-annotations = true select = [ "B028", # no-explicit-stacklevel "C4", # flake8-comprehensions + "F401", # unused-import "F811", # redefined-while-unused "I", # isort "PLC", # Pylint Convention @@ -38,6 +46,8 @@ extend-ignore = [ "UP015", # redundant-open-modes # No such concerns for stdlib "TC003", # typing-only-standard-library-import + # Lots of lazy or side-effect imports + "PLC0415", # import-outside-top-level # TODO: Consider passing exception around to ensure methods are only ever used within exception handlers "PLE0704", # misplaced-bare-raise # TODO: Still lots of manual fixes needed @@ -46,13 +56,9 @@ extend-ignore = [ ] [lint.per-file-ignores] -# Explicit re-exports is fine in __init__.py, still a code smell elsewhere. -"__init__.py" = ["PLC0414"] # TODO: Make adodbapi changes in their own PRs "adodbapi/*" = [ "C4", - "UP031", - "UP032", # Dropping Python 3.8 support "UP005", "UP006", @@ -62,6 +68,19 @@ extend-ignore = [ "YTT301", ] +[lint.pyflakes] +allowed-unused-imports = [ + # Modules with known side-effects + "coloreditor", # Adds doc template + "IDLEenvironment", # Injects fast_readline into the IDLE auto-indent extension + "pythoncom", # pythoncomXX.dll loader + "pywintypes", # pywintypesXX.dll loader + "win32com", # Sets pythoncom.frozen and adds modules to path based on the registry + "win32traceutil", # Redirects output to win32trace remote collector + "win32ui", # Must always be imported before dde + # Note: com/win32com/client/gencache.py does read files on import, but only to fill in it's own dicts from cache +] + [lint.isort] combine-as-imports = true # Because pywin32 has a mix of relative and absolute imports, with undetectable first-party c-extensions diff --git a/win32/Demos/service/pipeTestService.py b/win32/Demos/service/pipeTestService.py index 56512e4480..14c030381b 100644 --- a/win32/Demos/service/pipeTestService.py +++ b/win32/Demos/service/pipeTestService.py @@ -24,11 +24,11 @@ # # Use "import *" to keep this looking as much as a "normal" service # as possible. Real code shouldn't do this. -from ntsecuritycon import * # nopycln: import -from win32api import * # nopycln: import -from win32event import * # nopycln: import -from win32file import * # nopycln: import -from win32pipe import * # nopycln: import +from ntsecuritycon import * +from win32api import * +from win32event import * +from win32file import * +from win32pipe import * def ApplyIgnoreError(fn, args): diff --git a/win32/Demos/service/pipeTestServiceClient.py b/win32/Demos/service/pipeTestServiceClient.py index 3ac26b793e..3fa398b8cb 100644 --- a/win32/Demos/service/pipeTestServiceClient.py +++ b/win32/Demos/service/pipeTestServiceClient.py @@ -16,9 +16,9 @@ # # Use "import *" to keep this looking as much as a "normal" service # as possible. Real code shouldn't do this. -from win32event import * # nopycln: import -from win32file import * # nopycln: import -from win32pipe import * # nopycln: import +from win32event import * +from win32file import * +from win32pipe import * verbose = 0 diff --git a/win32/Demos/win32netdemo.py b/win32/Demos/win32netdemo.py index 6b8750cedd..e75f6f50ef 100644 --- a/win32/Demos/win32netdemo.py +++ b/win32/Demos/win32netdemo.py @@ -256,7 +256,7 @@ def main(): break else: print("Test '%s' unknown - skipping" % arg) - if not len(dotests): + if not dotests: print("Nothing to do!") usage(tests) for test in dotests: diff --git a/win32/Lib/win2kras.py b/win32/Lib/win2kras.py index 90e4a3d529..27b9a60cbc 100644 --- a/win32/Lib/win2kras.py +++ b/win32/Lib/win2kras.py @@ -9,6 +9,6 @@ import warnings -from win32ras import * # nopycln: import +from win32ras import * warnings.warn(str(__doc__), category=DeprecationWarning, stacklevel=2) diff --git a/win32/Lib/winxptheme.py b/win32/Lib/winxptheme.py index 4fd7ed9626..e147eda58f 100644 --- a/win32/Lib/winxptheme.py +++ b/win32/Lib/winxptheme.py @@ -5,4 +5,4 @@ around _winxptheme. """ -from _winxptheme import * # nopycln: import +from _winxptheme import * diff --git a/win32/scripts/regsetup.py b/win32/scripts/regsetup.py index b8cc70a2d4..b8275271d2 100644 --- a/win32/scripts/regsetup.py +++ b/win32/scripts/regsetup.py @@ -162,9 +162,6 @@ def FindPythonExe(exeAlias, possibleRealNames, searchPaths): def QuotedFileName(fname): """Given a filename, return a quoted version if necessary""" - - import regutil - try: fname.index(" ") # Other chars forcing quote? return '"%s"' % fname @@ -183,8 +180,6 @@ def LocateFileName(fileNamesString, searchPaths): """ import os - import regutil - fileNames = fileNamesString.split(";") for path in searchPaths: for fileName in fileNames: diff --git a/win32/test/testall.py b/win32/test/testall.py index 33c14afe43..769fcb6693 100644 --- a/win32/test/testall.py +++ b/win32/test/testall.py @@ -106,8 +106,6 @@ def __call__(self): def get_demo_tests(): - import win32api - ret = [] demo_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "Demos")) assert os.path.isdir(demo_dir), demo_dir diff --git a/win32/winxpgui.py b/win32/winxpgui.py index 4933796017..a1f39bee2d 100644 --- a/win32/winxpgui.py +++ b/win32/winxpgui.py @@ -5,9 +5,9 @@ import warnings -from win32console import ( # nopycln: import +from win32console import ( GetConsoleWindow as GetConsoleWindow, # noqa: PLC0414 # Explicit re-export ) -from win32gui import * # nopycln: import +from win32gui import * warnings.warn(str(__doc__), category=DeprecationWarning, stacklevel=2)