From 13f3b747f1ac198ca2d1928feae597c29341324c Mon Sep 17 00:00:00 2001 From: dieter Date: Mon, 9 Dec 2024 13:50:42 +0100 Subject: [PATCH 1/3] make signatures in `tb_format` compatible with Python 3.12+ --- CHANGES.rst | 3 ++- src/zope/testrunner/tb_format.py | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 3bc77c8..cf8acc9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,8 @@ 6.7 (unreleased) ================ -- Nothing changed yet. +- Make signatures in ``tb_format`` Python 3.12+ compatible + (`#186 `_) 6.6 (2024-10-16) diff --git a/src/zope/testrunner/tb_format.py b/src/zope/testrunner/tb_format.py index 97968bf..d19abf8 100644 --- a/src/zope/testrunner/tb_format.py +++ b/src/zope/testrunner/tb_format.py @@ -22,6 +22,13 @@ import zope.testrunner.feature +try: + from traceback import _sentinel +except ImportError: + # before 3.12 + _sentinel = False + + def _iter_chain(exc, custom_tb=None, seen=None): if seen is None: seen = set() @@ -47,9 +54,9 @@ def _parse_value_tb(exc, value, tb): # Taken straight from the traceback module code on Python 3.10, which # introduced the ability to call print_exception with an exception # instance as first parameter - if (value is None) != (tb is None): + if (value is _sentinel) != (tb is _sentinel): raise ValueError("Both or neither of value and tb must be given") - if value is tb is None: + if value is tb is _sentinel: if exc is not None: return exc, exc.__traceback__ else: @@ -57,7 +64,8 @@ def _parse_value_tb(exc, value, tb): return value, tb -def format_exception(t, v, tb, limit=None, chain=None): +def format_exception(t, value=_sentinel, tb=_sentinel, limit=None, chain=None): + v, tb = _parse_value_tb(t, value, tb) if chain: values = _iter_chain(v, tb) else: @@ -70,8 +78,9 @@ def format_exception(t, v, tb, limit=None, chain=None): return fmt.formatException(t, v, tb) -def print_exception(t, v=None, tb=None, limit=None, file=None, chain=None): - v, tb = _parse_value_tb(t, v, tb) +def print_exception(t, value=_sentinel, tb=_sentinel, + limit=None, file=None, chain=None): + v, tb = _parse_value_tb(t, value, tb) if chain: values = _iter_chain(v, tb) else: From 314656bdabcc8800b19d2c6e4ad329bf9f4207b6 Mon Sep 17 00:00:00 2001 From: dieter Date: Tue, 10 Dec 2024 08:41:03 +0100 Subject: [PATCH 2/3] fix fallback definition for `_sentinel` in `tb_format` --- src/zope/testrunner/tb_format.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zope/testrunner/tb_format.py b/src/zope/testrunner/tb_format.py index d19abf8..9c6afd4 100644 --- a/src/zope/testrunner/tb_format.py +++ b/src/zope/testrunner/tb_format.py @@ -25,8 +25,8 @@ try: from traceback import _sentinel except ImportError: - # before 3.12 - _sentinel = False + # before 3.10 + _sentinel = None def _iter_chain(exc, custom_tb=None, seen=None): From 3dc51e17ab44758dddbf89e3285682018d61c233 Mon Sep 17 00:00:00 2001 From: dieter Date: Tue, 10 Dec 2024 09:36:48 +0100 Subject: [PATCH 3/3] cannot use a `_sentinel` value used by the API --- src/zope/testrunner/tb_format.py | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/zope/testrunner/tb_format.py b/src/zope/testrunner/tb_format.py index 9c6afd4..451d686 100644 --- a/src/zope/testrunner/tb_format.py +++ b/src/zope/testrunner/tb_format.py @@ -23,10 +23,17 @@ try: + from traceback import _parse_value_tb from traceback import _sentinel except ImportError: # before 3.10 - _sentinel = None + # for Python before 3.10, the first 3 parameters of + # ``print_exception`` and ``format_exception`` are all mandatory + # and the first one (``etype`` alias ``t``) is ignored + _sentinel = object() + + def _parse_value_tb(ignored, value, tb): + return value, tb def _iter_chain(exc, custom_tb=None, seen=None): @@ -50,20 +57,6 @@ def _iter_chain(exc, custom_tb=None, seen=None): yield from it -def _parse_value_tb(exc, value, tb): - # Taken straight from the traceback module code on Python 3.10, which - # introduced the ability to call print_exception with an exception - # instance as first parameter - if (value is _sentinel) != (tb is _sentinel): - raise ValueError("Both or neither of value and tb must be given") - if value is tb is _sentinel: - if exc is not None: - return exc, exc.__traceback__ - else: - return None, None - return value, tb - - def format_exception(t, value=_sentinel, tb=_sentinel, limit=None, chain=None): v, tb = _parse_value_tb(t, value, tb) if chain: