diff --git a/.github/workflows/CI_build.yml b/.github/workflows/CI_build.yml
index 9b04c85a..5d066cc5 100644
--- a/.github/workflows/CI_build.yml
+++ b/.github/workflows/CI_build.yml
@@ -44,9 +44,9 @@ jobs:
if: matrix.build_configuration == 'Release'
working-directory: installer
run: |
- $env:PYTHONBUILDDIR_ARM64='${{ github.workspace }}\packages\pythonarm64.3.14.4\tools'
- $env:PYTHONBUILDDIR_X64='${{ github.workspace }}\packages\python.3.14.4\tools'
- $env:PYTHONBUILDDIR='${{ github.workspace }}\packages\pythonx86.3.14.4\tools'
+ $env:PYTHONBUILDDIR_ARM64='${{ github.workspace }}\packages\pythonarm64.3.14.5\tools'
+ $env:PYTHONBUILDDIR_X64='${{ github.workspace }}\packages\python.3.14.5\tools'
+ $env:PYTHONBUILDDIR='${{ github.workspace }}\packages\pythonx86.3.14.5\tools'
Rename-Item -Path ".\buildPaths.bat.orig" -NewName "buildPaths.bat"
dotnet tool install --global wix --version 7.0.0
.\buildAll.bat ${{ matrix.build_platform }}
diff --git a/PythonLib/full/_pyrepl/reader.py b/PythonLib/full/_pyrepl/reader.py
index 9ab92f64..f0116e74 100644
--- a/PythonLib/full/_pyrepl/reader.py
+++ b/PythonLib/full/_pyrepl/reader.py
@@ -644,6 +644,7 @@ def update_screen(self) -> None:
def refresh(self) -> None:
"""Recalculate and refresh the screen."""
+ self.console.height, self.console.width = self.console.getheightwidth()
# this call sets up self.cxy, so call it first.
self.screen = self.calc_screen()
self.console.refresh(self.screen, self.cxy)
diff --git a/PythonLib/full/_pyrepl/unix_console.py b/PythonLib/full/_pyrepl/unix_console.py
index 937b5df6..639d16db 100644
--- a/PythonLib/full/_pyrepl/unix_console.py
+++ b/PythonLib/full/_pyrepl/unix_console.py
@@ -776,7 +776,6 @@ def __move_tall(self, x, y):
self.__write_code(self._cup, y - self.__offset, x)
def __sigwinch(self, signum, frame):
- self.height, self.width = self.getheightwidth()
self.event_queue.insert(Event("resize", None))
def __hide_cursor(self):
diff --git a/PythonLib/full/annotationlib.py b/PythonLib/full/annotationlib.py
index 9fee2564..5c9a0812 100644
--- a/PythonLib/full/annotationlib.py
+++ b/PythonLib/full/annotationlib.py
@@ -47,6 +47,7 @@ class Format(enum.IntEnum):
"__cell__",
"__owner__",
"__stringifier_dict__",
+ "__resolved_str_cache__",
)
@@ -94,6 +95,7 @@ def __init__(
# value later.
self.__code__ = None
self.__ast_node__ = None
+ self.__resolved_str_cache__ = None
def __init_subclass__(cls, /, *args, **kwds):
raise TypeError("Cannot subclass ForwardRef")
@@ -113,7 +115,7 @@ def evaluate(
"""
match format:
case Format.STRING:
- return self.__forward_arg__
+ return self.__resolved_str__
case Format.VALUE:
is_forwardref_format = False
case Format.FORWARDREF:
@@ -258,6 +260,24 @@ def __forward_arg__(self):
"Attempted to access '__forward_arg__' on an uninitialized ForwardRef"
)
+ @property
+ def __resolved_str__(self):
+ # __forward_arg__ with any names from __extra_names__ replaced
+ # with the type_repr of the value they represent
+ if self.__resolved_str_cache__ is None:
+ resolved_str = self.__forward_arg__
+ names = self.__extra_names__
+
+ if names:
+ visitor = _ExtraNameFixer(names)
+ ast_expr = ast.parse(resolved_str, mode="eval").body
+ node = visitor.visit(ast_expr)
+ resolved_str = ast.unparse(node)
+
+ self.__resolved_str_cache__ = resolved_str
+
+ return self.__resolved_str_cache__
+
@property
def __forward_code__(self):
if self.__code__ is not None:
@@ -321,7 +341,7 @@ def __repr__(self):
extra.append(", is_class=True")
if self.__owner__ is not None:
extra.append(f", owner={self.__owner__!r}")
- return f"ForwardRef({self.__forward_arg__!r}{''.join(extra)})"
+ return f"ForwardRef({self.__resolved_str__!r}{''.join(extra)})"
_Template = type(t"")
@@ -357,6 +377,7 @@ def __init__(
self.__cell__ = cell
self.__owner__ = owner
self.__stringifier_dict__ = stringifier_dict
+ self.__resolved_str_cache__ = None # Needed for ForwardRef
def __convert_to_ast(self, other):
if isinstance(other, _Stringifier):
@@ -1163,3 +1184,14 @@ def _get_dunder_annotations(obj):
if not isinstance(ann, dict):
raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
return ann
+
+
+class _ExtraNameFixer(ast.NodeTransformer):
+ """Fixer for __extra_names__ items in ForwardRef __repr__ and string evaluation"""
+ def __init__(self, extra_names):
+ self.extra_names = extra_names
+
+ def visit_Name(self, node: ast.Name):
+ if (new_name := self.extra_names.get(node.id, _sentinel)) is not _sentinel:
+ node = ast.Name(id=type_repr(new_name))
+ return node
diff --git a/PythonLib/full/argparse.py b/PythonLib/full/argparse.py
index 8cf85694..24f47405 100644
--- a/PythonLib/full/argparse.py
+++ b/PythonLib/full/argparse.py
@@ -2678,7 +2678,7 @@ def _check_value(self, action, value):
if value not in choices:
args = {'value': str(value),
- 'choices': ', '.join(map(str, action.choices))}
+ 'choices': ', '.join(repr(str(choice)) for choice in action.choices)}
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
if self.suggest_on_error and isinstance(value, str):
diff --git a/PythonLib/full/asyncio/__main__.py b/PythonLib/full/asyncio/__main__.py
index 0e100358..44e14771 100644
--- a/PythonLib/full/asyncio/__main__.py
+++ b/PythonLib/full/asyncio/__main__.py
@@ -98,11 +98,15 @@ def run(self):
if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")):
sys.audit("cpython.run_startup", startup_path)
-
- import tokenize
- with tokenize.open(startup_path) as f:
- startup_code = compile(f.read(), startup_path, "exec")
+ try:
+ import tokenize
+ with tokenize.open(startup_path) as f:
+ startup_code = compile(f.read(), startup_path, "exec")
exec(startup_code, console.locals)
+ except SystemExit:
+ raise
+ except BaseException:
+ console.showtraceback()
ps1 = getattr(sys, "ps1", ">>> ")
if CAN_USE_PYREPL:
diff --git a/PythonLib/full/asyncio/windows_utils.py b/PythonLib/full/asyncio/windows_utils.py
index acd49441..d6393f0b 100644
--- a/PythonLib/full/asyncio/windows_utils.py
+++ b/PythonLib/full/asyncio/windows_utils.py
@@ -111,8 +111,9 @@ def fileno(self):
def close(self, *, CloseHandle=_winapi.CloseHandle):
if self._handle is not None:
- CloseHandle(self._handle)
+ handle = self._handle
self._handle = None
+ CloseHandle(handle)
def __del__(self, _warn=warnings.warn):
if self._handle is not None:
diff --git a/PythonLib/full/configparser.py b/PythonLib/full/configparser.py
index d435a5c2..a53ac872 100644
--- a/PythonLib/full/configparser.py
+++ b/PythonLib/full/configparser.py
@@ -315,12 +315,15 @@ def __init__(self, source, *args):
def append(self, lineno, line):
self.errors.append((lineno, line))
- self.message += '\n\t[line %2d]: %s' % (lineno, repr(line))
+ self.message += f'\n\t[line {lineno:2d}]: {line!r}'
def combine(self, others):
+ messages = [self.message]
for other in others:
- for error in other.errors:
- self.append(*error)
+ for lineno, line in other.errors:
+ self.errors.append((lineno, line))
+ messages.append(f'\n\t[line {lineno:2d}]: {line!r}')
+ self.message = "".join(messages)
return self
@staticmethod
@@ -613,7 +616,9 @@ class RawConfigParser(MutableMapping):
\] # ]
"""
_OPT_TMPL = r"""
- (?P