Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dropping support for Python 3.8 #2413

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
architecture: ["x64", "x86"]

steps:
Expand Down Expand Up @@ -46,7 +46,7 @@ jobs:
- name: Run tests
# Run the tests directly from the source dir so support files (eg, .wav files etc)
# can be found - they aren't installed into the Python tree.
run: python pywin32_testall.py -v -skip-adodbapi
run: python -X dev pywin32_testall.py -v -skip-adodbapi

- name: Build wheels
run: |
Expand All @@ -69,7 +69,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13-dev"]
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -111,7 +111,7 @@ jobs:
- uses: actions/setup-python@v5
with:
# This job only needs to target the oldest supported version
python-version: "3.8"
python-version: "3.9"
cache: pip
cache-dependency-path: .github/workflows/main.yml
- run: pip install clang-format pycln
Expand All @@ -133,8 +133,7 @@ jobs:
strategy:
fail-fast: false
matrix:
# mypy won't understand "3.13-dev", keeping the CI simple by just omitting it
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand All @@ -152,7 +151,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13-dev"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
Expand Down
5 changes: 1 addition & 4 deletions Pythonwin/pywin/framework/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import os
import sys
import traceback
from typing import TYPE_CHECKING
from typing import Literal

import regutil
import win32api
Expand All @@ -20,9 +20,6 @@

from . import scriptutils

if TYPE_CHECKING:
from typing_extensions import Literal


# Helper for writing a Window position by name, and later loading it.
def SaveWindowSize(section, rect, state=""):
Expand Down
5 changes: 3 additions & 2 deletions Pythonwin/pywin/framework/intpyapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
import sys
import traceback
from collections.abc import Sequence

import __main__
import commctrl
Expand Down Expand Up @@ -268,7 +269,7 @@ def Activate(self):
if frame.GetWindowPlacement()[1] == win32con.SW_SHOWMINIMIZED:
frame.ShowWindow(win32con.SW_RESTORE)

def ProcessArgs(self, args, dde=None):
def ProcessArgs(self, args: Sequence[str], dde=None):
# If we are going to talk to a remote app via DDE, then
# activate it!
if (
Expand All @@ -290,7 +291,7 @@ def ProcessArgs(self, args, dde=None):
).lower()
i -= 1 # arg is /edit's parameter
par = i < len(args) and args[i] or "MISSING"
if argType in ("/nodde", "/new", "-nodde", "-new"):
if argType in ("/nodde", "/new"):
# Already handled
pass
elif argType.startswith("/goto:"):
Expand Down
4 changes: 1 addition & 3 deletions Pythonwin/pywin/idle/CallTips.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,8 @@ def get_object_at_cursor(
# How is this for a hack!
import __main__

namespace = sys.modules.copy()
namespace.update(__main__.__dict__)
try:
return eval(word, namespace)
return eval(word, sys.modules | __main__.__dict__)
except:
pass
return None # Can't find an object.
Expand Down
5 changes: 2 additions & 3 deletions Pythonwin/pywin/scintilla/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ def list2dict(l):
# extra attributes of win32ui objects
if hasattr(ob, "_obj_"):
try:
items_dict.update(list2dict(dir(ob._obj_)))
items_dict = list2dict(dir(ob._obj_))
except AttributeError:
pass # object has no __dict__

Expand Down Expand Up @@ -650,8 +650,7 @@ def _GetObjectAtPos(self, pos=-1, bAllowCalls=0):
left, right = self._GetWordSplit(pos, bAllowCalls)
if left: # It is an attribute lookup
# How is this for a hack!
namespace = sys.modules.copy()
namespace.update(__main__.__dict__)
namespace = sys.modules | __main__.__dict__
# Get the debugger's context.
try:
from pywin.framework import interact
Expand Down
15 changes: 10 additions & 5 deletions Pythonwin/pywin/test/test_pywin.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,13 @@ def t_Browse(*args):
if __name__ != "__main__":
# make T findable by browser in __main__ namespace
setattr(__main__, __class__.__qualname__, __class__)
with mock.patch(
"pywin.mfc.dialog.GetSimpleInput", (lambda *args: __class__.__qualname__)
), mock.patch("pywin.tools.browser.Browse", t_Browse):
with (
mock.patch(
"pywin.mfc.dialog.GetSimpleInput",
(lambda *args: __class__.__qualname__),
),
mock.patch("pywin.tools.browser.Browse", t_Browse),
):
self.app.OnViewBrowse(0, 0)
hl = o.dlg.hier_list
self.assertGreater(len(hl.itemHandleMap), 10)
Expand Down Expand Up @@ -443,8 +447,9 @@ def t_brk(self):
GUIAboutToBreak()

dmod = types.ModuleType("__main__", "debugger test main")
with mock.patch("pywin.framework.scriptutils.__main__", dmod), mock.patch(
"pywin.debugger.debugger.Debugger.GUIAboutToBreak", t_brk
with (
mock.patch("pywin.framework.scriptutils.__main__", dmod),
mock.patch("pywin.debugger.debugger.Debugger.GUIAboutToBreak", t_brk),
):
mf.SendMessage(wc.WM_COMMAND, cmGo) # debh.OnGo(0, 0)
self.assertFalse(cmds_brk_next, "break commands remaining")
Expand Down
13 changes: 0 additions & 13 deletions Pythonwin/win32cmdui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,6 @@ inline void *GetPythonOleProcAddress(const char *procName)
#endif
hMod = GetModuleHandle(buf);

// XXX It is unclear why the code previously tried to identify a loaded PythonCOM DLL of
// any Python version 1.5 .. 3.9. If some InprocServer would load the DLL of a different
// Python version that would likely cause a crash. Thus deactivated.
//
// for (int i = 0; hMod == NULL && i < 40; i++) {
// #ifdef _DEBUG
// wsprintf(buf, _T("PythonCOM3%d_d.dll"), i);
// #else
// wsprintf(buf, _T("PythonCOM3%d.dll"), i);
// #endif
// hMod = GetModuleHandle(buf);
// }

if (hMod) {
void *rc = GetProcAddress(hMod, procName);
if (rc == NULL)
Expand Down
6 changes: 3 additions & 3 deletions Pythonwin/win32uimodule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2559,9 +2559,9 @@ extern "C" PYW_EXPORT BOOL Win32uiApplicationInit(Win32uiHostGlue *pGlue, const
PyObject *argv = PySys_GetObject("argv");
PyInit_win32ui();
// Decide if we render sys.argv from command line.
// PY3.6- Py_Initialize sets sys.argv=NULL .
// PY3.7 Py_Initialize or intentional script triggers set sys.argv=[] .
// PY3.8+ Py_Initialize sets sys.argv=[''] - cannot be distinguished
// Python 3.6- Py_Initialize sets sys.argv=NULL .
// Python 3.7 Py_Initialize or intentional script triggers set sys.argv=[] .
// Python 3.8+ Py_Initialize sets sys.argv=[''] - cannot be distinguished
// from a pre-existing command line setup anymore. So we need to check
// another flag regarding the intended type of invokation, e.g. `cmd`
// (or untangle all that crossover startup + module + app init here)
Expand Down
3 changes: 1 addition & 2 deletions adodbapi/apibase.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import sys
import time
from collections.abc import Callable, Iterable, Mapping
from typing import Dict

# noinspection PyUnresolvedReferences
from . import ado_consts as adc
Expand Down Expand Up @@ -465,7 +464,7 @@ def convert_to_python(variant, func): # convert DB value into Python value
return func(variant) # call the appropriate conversion function


class MultiMap(Dict[int, Callable[[object], object]]):
class MultiMap(dict[int, Callable[[object], object]]):
# builds a dictionary from {(iterable,of,keys) : function}
"""A dictionary of ado.type : function
-- but you can set multiple items by passing an iterable of keys"""
Expand Down
60 changes: 4 additions & 56 deletions adodbapi/test/adodbapitest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import decimal
import random
import string
import sys
import time
import unittest

Expand All @@ -37,14 +36,6 @@
import adodbapi
import adodbapi.apibase as api

try:
import adodbapi.ado_consts as ado_consts
except ImportError: # we are doing a shortcut import as a module -- so
try:
import ado_consts
except ImportError:
from adodbapi import ado_consts


def randomstring(length):
return "".join([random.choice(string.ascii_letters) for n in range(32)])
Expand Down Expand Up @@ -188,49 +179,6 @@ def testUserDefinedConversions(self):
pass
self.helpRollbackTblTemp()

def testUserDefinedConversionForExactNumericTypes(self):
# variantConversions is a dictionary of conversion functions
# held internally in adodbapi.apibase
#
# !!! this test intentionally alters the value of what should be constant in the module
# !!! no new code should use this example, to is only a test to see that the
# !!! deprecated way of doing this still works. (use connection.variantConversions)
#
if sys.version_info < (3, 0): ### Py3 need different test
oldconverter = adodbapi.variantConversions[
ado_consts.adNumeric
] # keep old function to restore later
# By default decimal and "numbers" are returned as decimals.
# Instead, make numbers return as floats
try:
adodbapi.variantConversions[ado_consts.adNumeric] = adodbapi.cvtFloat
self.helpTestDataType(
"decimal(18,2)", "NUMBER", 3.45, compareAlmostEqual=1
)
self.helpTestDataType(
"numeric(18,2)", "NUMBER", 3.45, compareAlmostEqual=1
)
# now return strings
adodbapi.variantConversions[ado_consts.adNumeric] = adodbapi.cvtString
self.helpTestDataType("numeric(18,2)", "NUMBER", "3.45")
# now a completly weird user defined convertion
adodbapi.variantConversions[ado_consts.adNumeric] = (
lambda x: "!!This function returns a funny unicode string %s!!" % x
)
self.helpTestDataType(
"numeric(18,2)",
"NUMBER",
"3.45",
allowedReturnValues=[
"!!This function returns a funny unicode string 3.45!!"
],
)
finally:
# now reset the converter to its original function
adodbapi.variantConversions[ado_consts.adNumeric] = (
oldconverter # Restore the original convertion function
)

def helpTestDataType(
self,
sqlDataTypeString,
Expand Down Expand Up @@ -432,7 +380,7 @@ def testDataTypeInt(self):
"bigint",
"NUMBER",
3000000000,
allowedReturnValues=[3000000000, int(3000000000)],
allowedReturnValues=[3000000000, 3000000000],
)
self.helpTestDataType("int", "NUMBER", 2147483647)

Expand Down Expand Up @@ -1136,7 +1084,7 @@ def getConnection(self):
return self.conn

def getAnotherConnection(self, addkeys=None):
keys = dict(config.connStrSQLServer[1])
keys = config.connStrSQLServer[1].copy()
if addkeys:
keys.update(addkeys)
return config.dbSqlServerconnect(*config.connStrSQLServer[0], **keys)
Expand Down Expand Up @@ -1316,7 +1264,7 @@ def getConnection(self):
return self.conn

def getAnotherConnection(self, addkeys=None):
keys = dict(config.connStrMySql[1])
keys = config.connStrMySql[1].copy()
if addkeys:
keys.update(addkeys)
return config.dbMySqlconnect(*config.connStrMySql[0], **keys)
Expand Down Expand Up @@ -1382,7 +1330,7 @@ def getConnection(self):
return self.conn

def getAnotherConnection(self, addkeys=None):
keys = dict(config.connStrPostgres[1])
keys = config.connStrPostgres[1].copy()
if addkeys:
keys.update(addkeys)
return config.dbPostgresConnect(*config.connStrPostgres[0], **keys)
Expand Down
12 changes: 3 additions & 9 deletions adodbapi/test/dbapi20.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
__version__ = "$Revision: 1.15.0 $"[11:-2]
__author__ = "Stuart Bishop <[email protected]>"

import sys
import time
import unittest

Expand Down Expand Up @@ -197,14 +196,9 @@ def test_paramstyle(self):
self.fail("Driver doesn't define paramstyle")

def test_Exceptions(self):
# Make sure required exceptions exist, and are in the
# defined heirarchy.
if sys.version[0] == "3": # under Python 3 StardardError no longer exists
self.assertTrue(issubclass(self.driver.Warning, Exception))
self.assertTrue(issubclass(self.driver.Error, Exception))
else:
self.failUnless(issubclass(self.driver.Warning, Exception))
self.failUnless(issubclass(self.driver.Error, Exception))
# Make sure required exceptions exist, and are in the defined hierarchy.
self.assertTrue(issubclass(self.driver.Warning, Exception))
self.assertTrue(issubclass(self.driver.Error, Exception))

self.assertTrue(issubclass(self.driver.InterfaceError, self.driver.Error))
self.assertTrue(issubclass(self.driver.DatabaseError, self.driver.Error))
Expand Down
4 changes: 0 additions & 4 deletions build_all.bat
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
py -3.8-32 setup.py -q build
@if errorlevel 1 goto failed
py -3.8 setup.py -q build
@if errorlevel 1 goto failed
py -3.9-32 setup.py -q build
@if errorlevel 1 goto failed
py -3.9 setup.py -q build
Expand Down
2 changes: 1 addition & 1 deletion build_env.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This describes how to setup the build environment for pywin32.
Double check the compiler version you need in the [Python wiki](https://wiki.python.org/moin/WindowsCompilers)
but note that Python 3.5 -> 3.13 all use version 14.X of the compiler, which,
confusingly, report themselves as V.19XX (eg, note in Python's banner,
3.5's "MSC v.1900", even 3.9b4's "MSC v.1924")
3.5's "MSC v.1900", even 3.13's "MSC v.1941")

This compiler first shipped with Visual Studio 2015, although Visual Studio
2017, 2019 and 2022 all have this compiler available, just not installed
Expand Down
1 change: 1 addition & 0 deletions com/win32com/client/gencache.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ def _LoadDicts():
arc_path = loader.archive
dicts_path = os.path.join(win32com.__gen_path__, "dicts.dat")
if dicts_path.startswith(arc_path):
# Remove the leading slash as well
dicts_path = dicts_path[len(arc_path) + 1 :]
else:
# Hm. See below.
Expand Down
8 changes: 3 additions & 5 deletions isapi/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ def _PatchParamsModule(params, dll_name, file_must_exist=True):
sm.Module = dll_name


def GetLoaderModuleName(mod_name, check_module=None):
def GetLoaderModuleName(mod_name: str, check_module=None):
# find the name of the DLL hosting us.
# By default, this is "_{module_base_name}.dll"
if hasattr(sys, "frozen"):
Expand All @@ -635,8 +635,7 @@ def GetLoaderModuleName(mod_name, check_module=None):
base, ext = os.path.splitext(mod_name)
path, base = os.path.split(base)
# handle the common case of 'foo.exe'/'foow.exe'
if base.endswith("w"):
base = base[:-1]
base.removesuffix("w")
# For py2exe, we have '_foo.dll' as the standard pyisapi loader - but
# 'foo.dll' is what we use (it just delegates).
# So no leading '_' on the installed name.
Expand Down Expand Up @@ -760,8 +759,7 @@ def HandleCommandLine(

# build a usage string if we don't have one.
if not parser.get_usage():
all_handlers = standard_arguments.copy()
all_handlers.update(custom_arg_handlers)
all_handlers = standard_arguments | custom_arg_handlers
parser.set_usage(build_usage(all_handlers))

# allow the user to use uninstall as a synonym for remove if it wasn't
Expand Down
Loading
Loading