Python-checkins
Threads by month
- ----- 2025 -----
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
January 2025
- 1 participants
- 705 discussions
Jan. 20, 2025
https://github.com/python/cpython/commit/36aaf4137d55df3e324d98ec06b983b028…
commit: 36aaf4137d55df3e324d98ec06b983b0287d8949
branch: main
author: Serhiy Storchaka <storchaka(a)gmail.com>
committer: serhiy-storchaka <storchaka(a)gmail.com>
date: 2025-01-20T11:19:43+02:00
summary:
gh-71339: Use new assertion methods in test_abc (GH-128826)
files:
M Lib/test/test_abc.py
diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py
index 5ce57cc209ea85..e90a8dc617c094 100644
--- a/Lib/test/test_abc.py
+++ b/Lib/test/test_abc.py
@@ -20,7 +20,7 @@ def test_abstractproperty_basics(self):
def foo(self): pass
self.assertTrue(foo.__isabstractmethod__)
def bar(self): pass
- self.assertFalse(hasattr(bar, "__isabstractmethod__"))
+ self.assertNotHasAttr(bar, "__isabstractmethod__")
class C(metaclass=abc_ABCMeta):
@abc.abstractproperty
@@ -89,7 +89,7 @@ def test_abstractmethod_basics(self):
def foo(self): pass
self.assertTrue(foo.__isabstractmethod__)
def bar(self): pass
- self.assertFalse(hasattr(bar, "__isabstractmethod__"))
+ self.assertNotHasAttr(bar, "__isabstractmethod__")
def test_abstractproperty_basics(self):
@property
@@ -276,21 +276,21 @@ class A(metaclass=abc_ABCMeta):
class B(object):
pass
b = B()
- self.assertFalse(issubclass(B, A))
- self.assertFalse(issubclass(B, (A,)))
+ self.assertNotIsSubclass(B, A)
+ self.assertNotIsSubclass(B, (A,))
self.assertNotIsInstance(b, A)
self.assertNotIsInstance(b, (A,))
B1 = A.register(B)
- self.assertTrue(issubclass(B, A))
- self.assertTrue(issubclass(B, (A,)))
+ self.assertIsSubclass(B, A)
+ self.assertIsSubclass(B, (A,))
self.assertIsInstance(b, A)
self.assertIsInstance(b, (A,))
self.assertIs(B1, B)
class C(B):
pass
c = C()
- self.assertTrue(issubclass(C, A))
- self.assertTrue(issubclass(C, (A,)))
+ self.assertIsSubclass(C, A)
+ self.assertIsSubclass(C, (A,))
self.assertIsInstance(c, A)
self.assertIsInstance(c, (A,))
@@ -301,16 +301,16 @@ class A(metaclass=abc_ABCMeta):
class B(object):
pass
b = B()
- self.assertTrue(issubclass(B, A))
- self.assertTrue(issubclass(B, (A,)))
+ self.assertIsSubclass(B, A)
+ self.assertIsSubclass(B, (A,))
self.assertIsInstance(b, A)
self.assertIsInstance(b, (A,))
@A.register
class C(B):
pass
c = C()
- self.assertTrue(issubclass(C, A))
- self.assertTrue(issubclass(C, (A,)))
+ self.assertIsSubclass(C, A)
+ self.assertIsSubclass(C, (A,))
self.assertIsInstance(c, A)
self.assertIsInstance(c, (A,))
self.assertIs(C, A.register(C))
@@ -321,14 +321,14 @@ class A(metaclass=abc_ABCMeta):
class B:
pass
b = B()
- self.assertFalse(isinstance(b, A))
- self.assertFalse(isinstance(b, (A,)))
+ self.assertNotIsInstance(b, A)
+ self.assertNotIsInstance(b, (A,))
token_old = abc_get_cache_token()
A.register(B)
token_new = abc_get_cache_token()
self.assertGreater(token_new, token_old)
- self.assertTrue(isinstance(b, A))
- self.assertTrue(isinstance(b, (A,)))
+ self.assertIsInstance(b, A)
+ self.assertIsInstance(b, (A,))
def test_registration_builtins(self):
class A(metaclass=abc_ABCMeta):
@@ -336,18 +336,18 @@ class A(metaclass=abc_ABCMeta):
A.register(int)
self.assertIsInstance(42, A)
self.assertIsInstance(42, (A,))
- self.assertTrue(issubclass(int, A))
- self.assertTrue(issubclass(int, (A,)))
+ self.assertIsSubclass(int, A)
+ self.assertIsSubclass(int, (A,))
class B(A):
pass
B.register(str)
class C(str): pass
self.assertIsInstance("", A)
self.assertIsInstance("", (A,))
- self.assertTrue(issubclass(str, A))
- self.assertTrue(issubclass(str, (A,)))
- self.assertTrue(issubclass(C, A))
- self.assertTrue(issubclass(C, (A,)))
+ self.assertIsSubclass(str, A)
+ self.assertIsSubclass(str, (A,))
+ self.assertIsSubclass(C, A)
+ self.assertIsSubclass(C, (A,))
def test_registration_edge_cases(self):
class A(metaclass=abc_ABCMeta):
@@ -375,39 +375,39 @@ class A(metaclass=abc_ABCMeta):
def test_registration_transitiveness(self):
class A(metaclass=abc_ABCMeta):
pass
- self.assertTrue(issubclass(A, A))
- self.assertTrue(issubclass(A, (A,)))
+ self.assertIsSubclass(A, A)
+ self.assertIsSubclass(A, (A,))
class B(metaclass=abc_ABCMeta):
pass
- self.assertFalse(issubclass(A, B))
- self.assertFalse(issubclass(A, (B,)))
- self.assertFalse(issubclass(B, A))
- self.assertFalse(issubclass(B, (A,)))
+ self.assertNotIsSubclass(A, B)
+ self.assertNotIsSubclass(A, (B,))
+ self.assertNotIsSubclass(B, A)
+ self.assertNotIsSubclass(B, (A,))
class C(metaclass=abc_ABCMeta):
pass
A.register(B)
class B1(B):
pass
- self.assertTrue(issubclass(B1, A))
- self.assertTrue(issubclass(B1, (A,)))
+ self.assertIsSubclass(B1, A)
+ self.assertIsSubclass(B1, (A,))
class C1(C):
pass
B1.register(C1)
- self.assertFalse(issubclass(C, B))
- self.assertFalse(issubclass(C, (B,)))
- self.assertFalse(issubclass(C, B1))
- self.assertFalse(issubclass(C, (B1,)))
- self.assertTrue(issubclass(C1, A))
- self.assertTrue(issubclass(C1, (A,)))
- self.assertTrue(issubclass(C1, B))
- self.assertTrue(issubclass(C1, (B,)))
- self.assertTrue(issubclass(C1, B1))
- self.assertTrue(issubclass(C1, (B1,)))
+ self.assertNotIsSubclass(C, B)
+ self.assertNotIsSubclass(C, (B,))
+ self.assertNotIsSubclass(C, B1)
+ self.assertNotIsSubclass(C, (B1,))
+ self.assertIsSubclass(C1, A)
+ self.assertIsSubclass(C1, (A,))
+ self.assertIsSubclass(C1, B)
+ self.assertIsSubclass(C1, (B,))
+ self.assertIsSubclass(C1, B1)
+ self.assertIsSubclass(C1, (B1,))
C1.register(int)
class MyInt(int):
pass
- self.assertTrue(issubclass(MyInt, A))
- self.assertTrue(issubclass(MyInt, (A,)))
+ self.assertIsSubclass(MyInt, A)
+ self.assertIsSubclass(MyInt, (A,))
self.assertIsInstance(42, A)
self.assertIsInstance(42, (A,))
@@ -467,16 +467,16 @@ def __subclasshook__(cls, C):
if cls is A:
return 'foo' in C.__dict__
return NotImplemented
- self.assertFalse(issubclass(A, A))
- self.assertFalse(issubclass(A, (A,)))
+ self.assertNotIsSubclass(A, A)
+ self.assertNotIsSubclass(A, (A,))
class B:
foo = 42
- self.assertTrue(issubclass(B, A))
- self.assertTrue(issubclass(B, (A,)))
+ self.assertIsSubclass(B, A)
+ self.assertIsSubclass(B, (A,))
class C:
spam = 42
- self.assertFalse(issubclass(C, A))
- self.assertFalse(issubclass(C, (A,)))
+ self.assertNotIsSubclass(C, A)
+ self.assertNotIsSubclass(C, (A,))
def test_all_new_methods_are_called(self):
class A(metaclass=abc_ABCMeta):
@@ -493,7 +493,7 @@ class C(A, B):
self.assertEqual(B.counter, 1)
def test_ABC_has___slots__(self):
- self.assertTrue(hasattr(abc.ABC, '__slots__'))
+ self.assertHasAttr(abc.ABC, '__slots__')
def test_tricky_new_works(self):
def with_metaclass(meta, *bases):
@@ -515,7 +515,7 @@ def foo(self):
del A.foo
self.assertEqual(A.__abstractmethods__, {'foo'})
- self.assertFalse(hasattr(A, 'foo'))
+ self.assertNotHasAttr(A, 'foo')
abc.update_abstractmethods(A)
@@ -588,7 +588,7 @@ def updated_foo(self):
A.foo = updated_foo
abc.update_abstractmethods(A)
A()
- self.assertFalse(hasattr(A, '__abstractmethods__'))
+ self.assertNotHasAttr(A, '__abstractmethods__')
def test_update_del_implementation(self):
class A(metaclass=abc_ABCMeta):
1
0
[3.13] gh-71339: Add additional assertion methods in test.support (GH-128707) (GH-128815)
by serhiy-storchaka Jan. 20, 2025
by serhiy-storchaka Jan. 20, 2025
Jan. 20, 2025
https://github.com/python/cpython/commit/c6a566e47b9903d48e6e1e78a1af20e6c6…
commit: c6a566e47b9903d48e6e1e78a1af20e6c6c535cf
branch: 3.13
author: Serhiy Storchaka <storchaka(a)gmail.com>
committer: serhiy-storchaka <storchaka(a)gmail.com>
date: 2025-01-20T11:19:00+02:00
summary:
[3.13] gh-71339: Add additional assertion methods in test.support (GH-128707) (GH-128815)
Add a mix-in class ExtraAssertions containing the following methods:
* assertHasAttr() and assertNotHasAttr()
* assertIsSubclass() and assertNotIsSubclass()
* assertStartsWith() and assertNotStartsWith()
* assertEndsWith() and assertNotEndsWith()
(cherry picked from commit 06cad77a5b345adde88609be9c3c470c5cd9f417)
files:
M Lib/test/support/testcase.py
M Lib/test/test_descr.py
M Lib/test/test_gdb/util.py
M Lib/test/test_importlib/resources/test_functional.py
M Lib/test/test_pyclbr.py
M Lib/test/test_typing.py
M Lib/test/test_venv.py
diff --git a/Lib/test/support/testcase.py b/Lib/test/support/testcase.py
index fad1e4cb3499c0..fd32457d1467ca 100644
--- a/Lib/test/support/testcase.py
+++ b/Lib/test/support/testcase.py
@@ -1,6 +1,63 @@
from math import copysign, isnan
+class ExtraAssertions:
+
+ def assertIsSubclass(self, cls, superclass, msg=None):
+ if issubclass(cls, superclass):
+ return
+ standardMsg = f'{cls!r} is not a subclass of {superclass!r}'
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertNotIsSubclass(self, cls, superclass, msg=None):
+ if not issubclass(cls, superclass):
+ return
+ standardMsg = f'{cls!r} is a subclass of {superclass!r}'
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertHasAttr(self, obj, name, msg=None):
+ if not hasattr(obj, name):
+ if isinstance(obj, types.ModuleType):
+ standardMsg = f'module {obj.__name__!r} has no attribute {name!r}'
+ elif isinstance(obj, type):
+ standardMsg = f'type object {obj.__name__!r} has no attribute {name!r}'
+ else:
+ standardMsg = f'{type(obj).__name__!r} object has no attribute {name!r}'
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertNotHasAttr(self, obj, name, msg=None):
+ if hasattr(obj, name):
+ if isinstance(obj, types.ModuleType):
+ standardMsg = f'module {obj.__name__!r} has unexpected attribute {name!r}'
+ elif isinstance(obj, type):
+ standardMsg = f'type object {obj.__name__!r} has unexpected attribute {name!r}'
+ else:
+ standardMsg = f'{type(obj).__name__!r} object has unexpected attribute {name!r}'
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertStartsWith(self, s, prefix, msg=None):
+ if s.startswith(prefix):
+ return
+ standardMsg = f"{s!r} doesn't start with {prefix!r}"
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertNotStartsWith(self, s, prefix, msg=None):
+ if not s.startswith(prefix):
+ return
+ self.fail(self._formatMessage(msg, f"{s!r} starts with {prefix!r}"))
+
+ def assertEndsWith(self, s, suffix, msg=None):
+ if s.endswith(suffix):
+ return
+ standardMsg = f"{s!r} doesn't end with {suffix!r}"
+ self.fail(self._formatMessage(msg, standardMsg))
+
+ def assertNotEndsWith(self, s, suffix, msg=None):
+ if not s.endswith(suffix):
+ return
+ self.fail(self._formatMessage(msg, f"{s!r} ends with {suffix!r}"))
+
+
class ExceptionIsLikeMixin:
def assertExceptionIsLike(self, exc, template):
"""
diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index 14bd87eb9c8d84..dd1fa321ecf171 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -15,6 +15,7 @@
from copy import deepcopy
from contextlib import redirect_stdout
from test import support
+from test.support.testcase import ExtraAssertions
try:
import _testcapi
@@ -403,15 +404,7 @@ def test_wrap_lenfunc_bad_cast(self):
self.assertEqual(range(sys.maxsize).__len__(), sys.maxsize)
-class ClassPropertiesAndMethods(unittest.TestCase):
-
- def assertHasAttr(self, obj, name):
- self.assertTrue(hasattr(obj, name),
- '%r has no attribute %r' % (obj, name))
-
- def assertNotHasAttr(self, obj, name):
- self.assertFalse(hasattr(obj, name),
- '%r has unexpected attribute %r' % (obj, name))
+class ClassPropertiesAndMethods(unittest.TestCase, ExtraAssertions):
def test_python_dicts(self):
# Testing Python subclass of dict...
diff --git a/Lib/test/test_gdb/util.py b/Lib/test/test_gdb/util.py
index 8fe9cfc543395e..54c6b2de7cc99d 100644
--- a/Lib/test/test_gdb/util.py
+++ b/Lib/test/test_gdb/util.py
@@ -7,6 +7,7 @@
import sysconfig
import unittest
from test import support
+from test.support.testcase import ExtraAssertions
GDB_PROGRAM = shutil.which('gdb') or 'gdb'
@@ -152,7 +153,7 @@ def setup_module():
print()
-class DebuggerTests(unittest.TestCase):
+class DebuggerTests(unittest.TestCase, ExtraAssertions):
"""Test that the debugger can debug Python."""
@@ -280,11 +281,6 @@ def get_stack_trace(self, source=None, script=None,
return out
- def assertEndsWith(self, actual, exp_end):
- '''Ensure that the given "actual" string ends with "exp_end"'''
- self.assertTrue(actual.endswith(exp_end),
- msg='%r did not end with %r' % (actual, exp_end))
-
def assertMultilineMatches(self, actual, pattern):
m = re.match(pattern, actual, re.DOTALL)
if not m:
diff --git a/Lib/test/test_importlib/resources/test_functional.py b/Lib/test/test_importlib/resources/test_functional.py
index 4317abf3162c52..3fc1ade35bef5a 100644
--- a/Lib/test/test_importlib/resources/test_functional.py
+++ b/Lib/test/test_importlib/resources/test_functional.py
@@ -3,6 +3,7 @@
import importlib
from test.support import warnings_helper
+from test.support.testcase import ExtraAssertions
from importlib import resources
@@ -28,7 +29,7 @@ def anchor02(self):
return importlib.import_module('data02')
-class FunctionalAPIBase(util.DiskSetup):
+class FunctionalAPIBase(util.DiskSetup, ExtraAssertions):
def setUp(self):
super().setUp()
self.load_fixture('data02')
@@ -43,12 +44,6 @@ def _gen_resourcetxt_path_parts(self):
with self.subTest(path_parts=path_parts):
yield path_parts
- def assertEndsWith(self, string, suffix):
- """Assert that `string` ends with `suffix`.
-
- Used to ignore an architecture-specific UTF-16 byte-order mark."""
- self.assertEqual(string[-len(suffix) :], suffix)
-
def test_read_text(self):
self.assertEqual(
resources.read_text(self.anchor01, 'utf-8.file'),
diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py
index d409a2d4a312e6..a65705aaf53abc 100644
--- a/Lib/test/test_pyclbr.py
+++ b/Lib/test/test_pyclbr.py
@@ -10,6 +10,7 @@
from unittest import TestCase, main as unittest_main
from test.test_importlib import util as test_importlib_util
import warnings
+from test.support.testcase import ExtraAssertions
StaticMethodType = type(staticmethod(lambda: None))
@@ -22,7 +23,7 @@
# is imperfect (as designed), testModule is called with a set of
# members to ignore.
-class PyclbrTest(TestCase):
+class PyclbrTest(TestCase, ExtraAssertions):
def assertListEq(self, l1, l2, ignore):
''' succeed iff {l1} - {ignore} == {l2} - {ignore} '''
@@ -31,14 +32,6 @@ def assertListEq(self, l1, l2, ignore):
print("l1=%r\nl2=%r\nignore=%r" % (l1, l2, ignore), file=sys.stderr)
self.fail("%r missing" % missing.pop())
- def assertHasattr(self, obj, attr, ignore):
- ''' succeed iff hasattr(obj,attr) or attr in ignore. '''
- if attr in ignore: return
- if not hasattr(obj, attr): print("???", attr)
- self.assertTrue(hasattr(obj, attr),
- 'expected hasattr(%r, %r)' % (obj, attr))
-
-
def assertHaskey(self, obj, key, ignore):
''' succeed iff key in obj or key in ignore. '''
if key in ignore: return
@@ -86,7 +79,7 @@ def ismethod(oclass, obj, name):
for name, value in dict.items():
if name in ignore:
continue
- self.assertHasattr(module, name, ignore)
+ self.assertHasAttr(module, name, ignore)
py_item = getattr(module, name)
if isinstance(value, pyclbr.Function):
self.assertIsInstance(py_item, (FunctionType, BuiltinFunctionType))
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 140ceb74735570..89a32c7a1a0d14 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -46,6 +46,7 @@
import types
from test.support import captured_stderr, cpython_only, infinite_recursion, requires_docstrings, import_helper
+from test.support.testcase import ExtraAssertions
from test.typinganndata import ann_module695, mod_generics_cache, _typed_dict_helper
@@ -54,21 +55,7 @@
CANNOT_SUBCLASS_INSTANCE = 'Cannot subclass an instance of %s'
-class BaseTestCase(TestCase):
-
- def assertIsSubclass(self, cls, class_or_tuple, msg=None):
- if not issubclass(cls, class_or_tuple):
- message = '%r is not a subclass of %r' % (cls, class_or_tuple)
- if msg is not None:
- message += ' : %s' % msg
- raise self.failureException(message)
-
- def assertNotIsSubclass(self, cls, class_or_tuple, msg=None):
- if issubclass(cls, class_or_tuple):
- message = '%r is a subclass of %r' % (cls, class_or_tuple)
- if msg is not None:
- message += ' : %s' % msg
- raise self.failureException(message)
+class BaseTestCase(TestCase, ExtraAssertions):
def clear_caches(self):
for f in typing._cleanups:
@@ -1249,10 +1236,6 @@ class Gen[*Ts]: ...
class TypeVarTupleTests(BaseTestCase):
- def assertEndsWith(self, string, tail):
- if not string.endswith(tail):
- self.fail(f"String {string!r} does not end with {tail!r}")
-
def test_name(self):
Ts = TypeVarTuple('Ts')
self.assertEqual(Ts.__name__, 'Ts')
diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py
index 0b09010c69d4ea..c39c83f9d0a5c3 100644
--- a/Lib/test/test_venv.py
+++ b/Lib/test/test_venv.py
@@ -26,6 +26,7 @@
requires_resource, copy_python_src_ignore)
from test.support.os_helper import (can_symlink, EnvironmentVarGuard, rmtree,
TESTFN, FakePath)
+from test.support.testcase import ExtraAssertions
import unittest
import venv
from unittest.mock import patch, Mock
@@ -64,7 +65,7 @@ def check_output(cmd, encoding=None):
)
return out, err
-class BaseTest(unittest.TestCase):
+class BaseTest(unittest.TestCase, ExtraAssertions):
"""Base class for venv tests."""
maxDiff = 80 * 50
@@ -111,10 +112,6 @@ def get_text_file_contents(self, *args, encoding='utf-8'):
result = f.read()
return result
- def assertEndsWith(self, string, tail):
- if not string.endswith(tail):
- self.fail(f"String {string!r} does not end with {tail!r}")
-
class BasicTest(BaseTest):
"""Test venv module functionality."""
1
0
gh-71339: Improve error report for types in assertHasAttr() and assertNotHasAttr() (GH-128818)
by serhiy-storchaka Jan. 20, 2025
by serhiy-storchaka Jan. 20, 2025
Jan. 20, 2025
https://github.com/python/cpython/commit/da122b5facbbae9197a108e0a3c4b3f059…
commit: da122b5facbbae9197a108e0a3c4b3f0594c5e92
branch: main
author: Serhiy Storchaka <storchaka(a)gmail.com>
committer: serhiy-storchaka <storchaka(a)gmail.com>
date: 2025-01-20T11:17:49+02:00
summary:
gh-71339: Improve error report for types in assertHasAttr() and assertNotHasAttr() (GH-128818)
files:
M Lib/test/test_unittest/test_case.py
M Lib/unittest/case.py
diff --git a/Lib/test/test_unittest/test_case.py b/Lib/test/test_unittest/test_case.py
index cd366496eedca3..df1381451b7ebc 100644
--- a/Lib/test/test_unittest/test_case.py
+++ b/Lib/test/test_unittest/test_case.py
@@ -795,7 +795,15 @@ def testAssertHasAttr(self):
with self.assertRaises(self.failureException) as cm:
self.assertHasAttr(a, 'y')
self.assertEqual(str(cm.exception),
- "List instance has no attribute 'y'")
+ "'List' object has no attribute 'y'")
+ with self.assertRaises(self.failureException) as cm:
+ self.assertHasAttr(List, 'spam')
+ self.assertEqual(str(cm.exception),
+ "type object 'List' has no attribute 'spam'")
+ with self.assertRaises(self.failureException) as cm:
+ self.assertHasAttr(sys, 'spam')
+ self.assertEqual(str(cm.exception),
+ "module 'sys' has no attribute 'spam'")
with self.assertRaises(self.failureException) as cm:
self.assertHasAttr(a, 'y', 'ababahalamaha')
@@ -811,7 +819,15 @@ def testAssertNotHasAttr(self):
with self.assertRaises(self.failureException) as cm:
self.assertNotHasAttr(a, 'x')
self.assertEqual(str(cm.exception),
- "List instance has unexpected attribute 'x'")
+ "'List' object has unexpected attribute 'x'")
+ with self.assertRaises(self.failureException) as cm:
+ self.assertNotHasAttr(List, 'append')
+ self.assertEqual(str(cm.exception),
+ "type object 'List' has unexpected attribute 'append'")
+ with self.assertRaises(self.failureException) as cm:
+ self.assertNotHasAttr(sys, 'modules')
+ self.assertEqual(str(cm.exception),
+ "module 'sys' has unexpected attribute 'modules'")
with self.assertRaises(self.failureException) as cm:
self.assertNotHasAttr(a, 'x', 'ababahalamaha')
diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py
index e9ef551d0b3ded..10c3b7e122371e 100644
--- a/Lib/unittest/case.py
+++ b/Lib/unittest/case.py
@@ -1372,16 +1372,20 @@ def assertHasAttr(self, obj, name, msg=None):
if not hasattr(obj, name):
if isinstance(obj, types.ModuleType):
standardMsg = f'module {obj.__name__!r} has no attribute {name!r}'
+ elif isinstance(obj, type):
+ standardMsg = f'type object {obj.__name__!r} has no attribute {name!r}'
else:
- standardMsg = f'{type(obj).__name__} instance has no attribute {name!r}'
+ standardMsg = f'{type(obj).__name__!r} object has no attribute {name!r}'
self.fail(self._formatMessage(msg, standardMsg))
def assertNotHasAttr(self, obj, name, msg=None):
if hasattr(obj, name):
if isinstance(obj, types.ModuleType):
standardMsg = f'module {obj.__name__!r} has unexpected attribute {name!r}'
+ elif isinstance(obj, type):
+ standardMsg = f'type object {obj.__name__!r} has unexpected attribute {name!r}'
else:
- standardMsg = f'{type(obj).__name__} instance has unexpected attribute {name!r}'
+ standardMsg = f'{type(obj).__name__!r} object has unexpected attribute {name!r}'
self.fail(self._formatMessage(msg, standardMsg))
def assertRaisesRegex(self, expected_exception, expected_regex,
1
0
Jan. 20, 2025
https://github.com/python/cpython/commit/89c401fb9a612b9618bd513508e67deded…
commit: 89c401fb9a612b9618bd513508e67dededb0f031
branch: main
author: Serhiy Storchaka <storchaka(a)gmail.com>
committer: serhiy-storchaka <storchaka(a)gmail.com>
date: 2025-01-20T11:17:04+02:00
summary:
gh-71339: Use new assertion methods in test_functools (GH-128829)
files:
M Lib/test/test_functools.py
diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py
index 32224866082824..4beb4380c3ad6b 100644
--- a/Lib/test/test_functools.py
+++ b/Lib/test/test_functools.py
@@ -645,11 +645,11 @@ def test_bound_method_introspection(self):
def test_unbound_method_retrieval(self):
obj = self.A
- self.assertFalse(hasattr(obj.both, "__self__"))
- self.assertFalse(hasattr(obj.nested, "__self__"))
- self.assertFalse(hasattr(obj.over_partial, "__self__"))
- self.assertFalse(hasattr(obj.static, "__self__"))
- self.assertFalse(hasattr(self.a.static, "__self__"))
+ self.assertNotHasAttr(obj.both, "__self__")
+ self.assertNotHasAttr(obj.nested, "__self__")
+ self.assertNotHasAttr(obj.over_partial, "__self__")
+ self.assertNotHasAttr(obj.static, "__self__")
+ self.assertNotHasAttr(self.a.static, "__self__")
def test_descriptors(self):
for obj in [self.A, self.a]:
@@ -791,7 +791,7 @@ def wrapper():
self.assertNotEqual(wrapper.__qualname__, f.__qualname__)
self.assertEqual(wrapper.__doc__, None)
self.assertEqual(wrapper.__annotations__, {})
- self.assertFalse(hasattr(wrapper, 'attr'))
+ self.assertNotHasAttr(wrapper, 'attr')
def test_selective_update(self):
def f():
@@ -840,7 +840,7 @@ def wrapper():
pass
functools.update_wrapper(wrapper, max)
self.assertEqual(wrapper.__name__, 'max')
- self.assertTrue(wrapper.__doc__.startswith('max('))
+ self.assertStartsWith(wrapper.__doc__, 'max(')
self.assertEqual(wrapper.__annotations__, {})
def test_update_type_wrapper(self):
@@ -910,7 +910,7 @@ def wrapper():
self.assertEqual(wrapper.__name__, 'wrapper')
self.assertNotEqual(wrapper.__qualname__, f.__qualname__)
self.assertEqual(wrapper.__doc__, None)
- self.assertFalse(hasattr(wrapper, 'attr'))
+ self.assertNotHasAttr(wrapper, 'attr')
def test_selective_update(self):
def f():
@@ -2666,15 +2666,15 @@ def _(self, arg):
a.t(0)
self.assertEqual(a.arg, "int")
aa = A()
- self.assertFalse(hasattr(aa, 'arg'))
+ self.assertNotHasAttr(aa, 'arg')
a.t('')
self.assertEqual(a.arg, "str")
aa = A()
- self.assertFalse(hasattr(aa, 'arg'))
+ self.assertNotHasAttr(aa, 'arg')
a.t(0.0)
self.assertEqual(a.arg, "base")
aa = A()
- self.assertFalse(hasattr(aa, 'arg'))
+ self.assertNotHasAttr(aa, 'arg')
def test_staticmethod_register(self):
class A:
@@ -3036,16 +3036,16 @@ def i(arg):
@i.register(42)
def _(arg):
return "I annotated with a non-type"
- self.assertTrue(str(exc.exception).startswith(msg_prefix + "42"))
- self.assertTrue(str(exc.exception).endswith(msg_suffix))
+ self.assertStartsWith(str(exc.exception), msg_prefix + "42")
+ self.assertEndsWith(str(exc.exception), msg_suffix)
with self.assertRaises(TypeError) as exc:
@i.register
def _(arg):
return "I forgot to annotate"
- self.assertTrue(str(exc.exception).startswith(msg_prefix +
+ self.assertStartsWith(str(exc.exception), msg_prefix +
"<function TestSingleDispatch.test_invalid_registrations.<locals>._"
- ))
- self.assertTrue(str(exc.exception).endswith(msg_suffix))
+ )
+ self.assertEndsWith(str(exc.exception), msg_suffix)
with self.assertRaises(TypeError) as exc:
@i.register
@@ -3055,23 +3055,23 @@ def _(arg: typing.Iterable[str]):
# types from `typing`. Instead, annotate with regular types
# or ABCs.
return "I annotated with a generic collection"
- self.assertTrue(str(exc.exception).startswith(
+ self.assertStartsWith(str(exc.exception),
"Invalid annotation for 'arg'."
- ))
- self.assertTrue(str(exc.exception).endswith(
+ )
+ self.assertEndsWith(str(exc.exception),
'typing.Iterable[str] is not a class.'
- ))
+ )
with self.assertRaises(TypeError) as exc:
@i.register
def _(arg: typing.Union[int, typing.Iterable[str]]):
return "Invalid Union"
- self.assertTrue(str(exc.exception).startswith(
+ self.assertStartsWith(str(exc.exception),
"Invalid annotation for 'arg'."
- ))
- self.assertTrue(str(exc.exception).endswith(
+ )
+ self.assertEndsWith(str(exc.exception),
'typing.Union[int, typing.Iterable[str]] not all arguments are classes.'
- ))
+ )
def test_invalid_positional_argument(self):
@functools.singledispatch
1
0
gh-71339: Use assertIsSubclass() and assertNotIsSubclass() in test_collections (GH-128824)
by serhiy-storchaka Jan. 20, 2025
by serhiy-storchaka Jan. 20, 2025
Jan. 20, 2025
https://github.com/python/cpython/commit/887f2bcf48eaa13363f9276fdaa9e6b22d…
commit: 887f2bcf48eaa13363f9276fdaa9e6b22deaeb65
branch: main
author: Serhiy Storchaka <storchaka(a)gmail.com>
committer: serhiy-storchaka <storchaka(a)gmail.com>
date: 2025-01-20T11:16:32+02:00
summary:
gh-71339: Use assertIsSubclass() and assertNotIsSubclass() in test_collections (GH-128824)
files:
M Lib/test/test_collections.py
diff --git a/Lib/test/test_collections.py b/Lib/test/test_collections.py
index a24d3e3ea142b7..1e93530398be79 100644
--- a/Lib/test/test_collections.py
+++ b/Lib/test/test_collections.py
@@ -742,11 +742,11 @@ def validate_isinstance(self, abc, name):
C = type('C', (object,), {'__hash__': None})
setattr(C, name, stub)
self.assertIsInstance(C(), abc)
- self.assertTrue(issubclass(C, abc))
+ self.assertIsSubclass(C, abc)
C = type('C', (object,), {'__hash__': None})
self.assertNotIsInstance(C(), abc)
- self.assertFalse(issubclass(C, abc))
+ self.assertNotIsSubclass(C, abc)
def validate_comparison(self, instance):
ops = ['lt', 'gt', 'le', 'ge', 'ne', 'or', 'and', 'xor', 'sub']
@@ -812,12 +812,12 @@ def __await__(self):
non_samples = [None, int(), gen(), object()]
for x in non_samples:
self.assertNotIsInstance(x, Awaitable)
- self.assertFalse(issubclass(type(x), Awaitable), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Awaitable)
samples = [Bar(), MinimalCoro()]
for x in samples:
self.assertIsInstance(x, Awaitable)
- self.assertTrue(issubclass(type(x), Awaitable))
+ self.assertIsSubclass(type(x), Awaitable)
c = coro()
# Iterable coroutines (generators with CO_ITERABLE_COROUTINE
@@ -831,8 +831,8 @@ def __await__(self):
class CoroLike: pass
Coroutine.register(CoroLike)
- self.assertTrue(isinstance(CoroLike(), Awaitable))
- self.assertTrue(issubclass(CoroLike, Awaitable))
+ self.assertIsInstance(CoroLike(), Awaitable)
+ self.assertIsSubclass(CoroLike, Awaitable)
CoroLike = None
support.gc_collect() # Kill CoroLike to clean-up ABCMeta cache
@@ -864,12 +864,12 @@ def __await__(self):
non_samples = [None, int(), gen(), object(), Bar()]
for x in non_samples:
self.assertNotIsInstance(x, Coroutine)
- self.assertFalse(issubclass(type(x), Coroutine), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Coroutine)
samples = [MinimalCoro()]
for x in samples:
self.assertIsInstance(x, Awaitable)
- self.assertTrue(issubclass(type(x), Awaitable))
+ self.assertIsSubclass(type(x), Awaitable)
c = coro()
# Iterable coroutines (generators with CO_ITERABLE_COROUTINE
@@ -890,8 +890,8 @@ def close(self):
pass
def __await__(self):
pass
- self.assertTrue(isinstance(CoroLike(), Coroutine))
- self.assertTrue(issubclass(CoroLike, Coroutine))
+ self.assertIsInstance(CoroLike(), Coroutine)
+ self.assertIsSubclass(CoroLike, Coroutine)
class CoroLike:
def send(self, value):
@@ -900,15 +900,15 @@ def close(self):
pass
def __await__(self):
pass
- self.assertFalse(isinstance(CoroLike(), Coroutine))
- self.assertFalse(issubclass(CoroLike, Coroutine))
+ self.assertNotIsInstance(CoroLike(), Coroutine)
+ self.assertNotIsSubclass(CoroLike, Coroutine)
def test_Hashable(self):
# Check some non-hashables
non_samples = [bytearray(), list(), set(), dict()]
for x in non_samples:
self.assertNotIsInstance(x, Hashable)
- self.assertFalse(issubclass(type(x), Hashable), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Hashable)
# Check some hashables
samples = [None,
int(), float(), complex(),
@@ -918,14 +918,14 @@ def test_Hashable(self):
]
for x in samples:
self.assertIsInstance(x, Hashable)
- self.assertTrue(issubclass(type(x), Hashable), repr(type(x)))
+ self.assertIsSubclass(type(x), Hashable)
self.assertRaises(TypeError, Hashable)
# Check direct subclassing
class H(Hashable):
def __hash__(self):
return super().__hash__()
self.assertEqual(hash(H()), 0)
- self.assertFalse(issubclass(int, H))
+ self.assertNotIsSubclass(int, H)
self.validate_abstract_methods(Hashable, '__hash__')
self.validate_isinstance(Hashable, '__hash__')
@@ -933,13 +933,13 @@ def test_AsyncIterable(self):
class AI:
def __aiter__(self):
return self
- self.assertTrue(isinstance(AI(), AsyncIterable))
- self.assertTrue(issubclass(AI, AsyncIterable))
+ self.assertIsInstance(AI(), AsyncIterable)
+ self.assertIsSubclass(AI, AsyncIterable)
# Check some non-iterables
non_samples = [None, object, []]
for x in non_samples:
self.assertNotIsInstance(x, AsyncIterable)
- self.assertFalse(issubclass(type(x), AsyncIterable), repr(type(x)))
+ self.assertNotIsSubclass(type(x), AsyncIterable)
self.validate_abstract_methods(AsyncIterable, '__aiter__')
self.validate_isinstance(AsyncIterable, '__aiter__')
@@ -949,13 +949,13 @@ def __aiter__(self):
return self
async def __anext__(self):
raise StopAsyncIteration
- self.assertTrue(isinstance(AI(), AsyncIterator))
- self.assertTrue(issubclass(AI, AsyncIterator))
+ self.assertIsInstance(AI(), AsyncIterator)
+ self.assertIsSubclass(AI, AsyncIterator)
non_samples = [None, object, []]
# Check some non-iterables
for x in non_samples:
self.assertNotIsInstance(x, AsyncIterator)
- self.assertFalse(issubclass(type(x), AsyncIterator), repr(type(x)))
+ self.assertNotIsSubclass(type(x), AsyncIterator)
# Similarly to regular iterators (see issue 10565)
class AnextOnly:
async def __anext__(self):
@@ -968,7 +968,7 @@ def test_Iterable(self):
non_samples = [None, 42, 3.14, 1j]
for x in non_samples:
self.assertNotIsInstance(x, Iterable)
- self.assertFalse(issubclass(type(x), Iterable), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Iterable)
# Check some iterables
samples = [bytes(), str(),
tuple(), list(), set(), frozenset(), dict(),
@@ -978,13 +978,13 @@ def test_Iterable(self):
]
for x in samples:
self.assertIsInstance(x, Iterable)
- self.assertTrue(issubclass(type(x), Iterable), repr(type(x)))
+ self.assertIsSubclass(type(x), Iterable)
# Check direct subclassing
class I(Iterable):
def __iter__(self):
return super().__iter__()
self.assertEqual(list(I()), [])
- self.assertFalse(issubclass(str, I))
+ self.assertNotIsSubclass(str, I)
self.validate_abstract_methods(Iterable, '__iter__')
self.validate_isinstance(Iterable, '__iter__')
# Check None blocking
@@ -992,22 +992,22 @@ class It:
def __iter__(self): return iter([])
class ItBlocked(It):
__iter__ = None
- self.assertTrue(issubclass(It, Iterable))
- self.assertTrue(isinstance(It(), Iterable))
- self.assertFalse(issubclass(ItBlocked, Iterable))
- self.assertFalse(isinstance(ItBlocked(), Iterable))
+ self.assertIsSubclass(It, Iterable)
+ self.assertIsInstance(It(), Iterable)
+ self.assertNotIsSubclass(ItBlocked, Iterable)
+ self.assertNotIsInstance(ItBlocked(), Iterable)
def test_Reversible(self):
# Check some non-reversibles
non_samples = [None, 42, 3.14, 1j, set(), frozenset()]
for x in non_samples:
self.assertNotIsInstance(x, Reversible)
- self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Reversible)
# Check some non-reversible iterables
non_reversibles = [_test_gen(), (x for x in []), iter([]), reversed([])]
for x in non_reversibles:
self.assertNotIsInstance(x, Reversible)
- self.assertFalse(issubclass(type(x), Reversible), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Reversible)
# Check some reversible iterables
samples = [bytes(), str(), tuple(), list(), OrderedDict(),
OrderedDict().keys(), OrderedDict().items(),
@@ -1016,11 +1016,11 @@ def test_Reversible(self):
dict().keys(), dict().items(), dict().values()]
for x in samples:
self.assertIsInstance(x, Reversible)
- self.assertTrue(issubclass(type(x), Reversible), repr(type(x)))
+ self.assertIsSubclass(type(x), Reversible)
# Check also Mapping, MutableMapping, and Sequence
- self.assertTrue(issubclass(Sequence, Reversible), repr(Sequence))
- self.assertFalse(issubclass(Mapping, Reversible), repr(Mapping))
- self.assertFalse(issubclass(MutableMapping, Reversible), repr(MutableMapping))
+ self.assertIsSubclass(Sequence, Reversible)
+ self.assertNotIsSubclass(Mapping, Reversible)
+ self.assertNotIsSubclass(MutableMapping, Reversible)
# Check direct subclassing
class R(Reversible):
def __iter__(self):
@@ -1028,17 +1028,17 @@ def __iter__(self):
def __reversed__(self):
return iter(list())
self.assertEqual(list(reversed(R())), [])
- self.assertFalse(issubclass(float, R))
+ self.assertNotIsSubclass(float, R)
self.validate_abstract_methods(Reversible, '__reversed__', '__iter__')
# Check reversible non-iterable (which is not Reversible)
class RevNoIter:
def __reversed__(self): return reversed([])
class RevPlusIter(RevNoIter):
def __iter__(self): return iter([])
- self.assertFalse(issubclass(RevNoIter, Reversible))
- self.assertFalse(isinstance(RevNoIter(), Reversible))
- self.assertTrue(issubclass(RevPlusIter, Reversible))
- self.assertTrue(isinstance(RevPlusIter(), Reversible))
+ self.assertNotIsSubclass(RevNoIter, Reversible)
+ self.assertNotIsInstance(RevNoIter(), Reversible)
+ self.assertIsSubclass(RevPlusIter, Reversible)
+ self.assertIsInstance(RevPlusIter(), Reversible)
# Check None blocking
class Rev:
def __iter__(self): return iter([])
@@ -1047,39 +1047,38 @@ class RevItBlocked(Rev):
__iter__ = None
class RevRevBlocked(Rev):
__reversed__ = None
- self.assertTrue(issubclass(Rev, Reversible))
- self.assertTrue(isinstance(Rev(), Reversible))
- self.assertFalse(issubclass(RevItBlocked, Reversible))
- self.assertFalse(isinstance(RevItBlocked(), Reversible))
- self.assertFalse(issubclass(RevRevBlocked, Reversible))
- self.assertFalse(isinstance(RevRevBlocked(), Reversible))
+ self.assertIsSubclass(Rev, Reversible)
+ self.assertIsInstance(Rev(), Reversible)
+ self.assertNotIsSubclass(RevItBlocked, Reversible)
+ self.assertNotIsInstance(RevItBlocked(), Reversible)
+ self.assertNotIsSubclass(RevRevBlocked, Reversible)
+ self.assertNotIsInstance(RevRevBlocked(), Reversible)
def test_Collection(self):
# Check some non-collections
non_collections = [None, 42, 3.14, 1j, lambda x: 2*x]
for x in non_collections:
self.assertNotIsInstance(x, Collection)
- self.assertFalse(issubclass(type(x), Collection), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Collection)
# Check some non-collection iterables
non_col_iterables = [_test_gen(), iter(b''), iter(bytearray()),
(x for x in [])]
for x in non_col_iterables:
self.assertNotIsInstance(x, Collection)
- self.assertFalse(issubclass(type(x), Collection), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Collection)
# Check some collections
samples = [set(), frozenset(), dict(), bytes(), str(), tuple(),
list(), dict().keys(), dict().items(), dict().values()]
for x in samples:
self.assertIsInstance(x, Collection)
- self.assertTrue(issubclass(type(x), Collection), repr(type(x)))
+ self.assertIsSubclass(type(x), Collection)
# Check also Mapping, MutableMapping, etc.
- self.assertTrue(issubclass(Sequence, Collection), repr(Sequence))
- self.assertTrue(issubclass(Mapping, Collection), repr(Mapping))
- self.assertTrue(issubclass(MutableMapping, Collection),
- repr(MutableMapping))
- self.assertTrue(issubclass(Set, Collection), repr(Set))
- self.assertTrue(issubclass(MutableSet, Collection), repr(MutableSet))
- self.assertTrue(issubclass(Sequence, Collection), repr(MutableSet))
+ self.assertIsSubclass(Sequence, Collection)
+ self.assertIsSubclass(Mapping, Collection)
+ self.assertIsSubclass(MutableMapping, Collection)
+ self.assertIsSubclass(Set, Collection)
+ self.assertIsSubclass(MutableSet, Collection)
+ self.assertIsSubclass(Sequence, Collection)
# Check direct subclassing
class Col(Collection):
def __iter__(self):
@@ -1090,13 +1089,13 @@ def __contains__(self, item):
return False
class DerCol(Col): pass
self.assertEqual(list(iter(Col())), [])
- self.assertFalse(issubclass(list, Col))
- self.assertFalse(issubclass(set, Col))
- self.assertFalse(issubclass(float, Col))
+ self.assertNotIsSubclass(list, Col)
+ self.assertNotIsSubclass(set, Col)
+ self.assertNotIsSubclass(float, Col)
self.assertEqual(list(iter(DerCol())), [])
- self.assertFalse(issubclass(list, DerCol))
- self.assertFalse(issubclass(set, DerCol))
- self.assertFalse(issubclass(float, DerCol))
+ self.assertNotIsSubclass(list, DerCol)
+ self.assertNotIsSubclass(set, DerCol)
+ self.assertNotIsSubclass(float, DerCol)
self.validate_abstract_methods(Collection, '__len__', '__iter__',
'__contains__')
# Check sized container non-iterable (which is not Collection) etc.
@@ -1109,12 +1108,12 @@ def __contains__(self, item): return False
class ColNoCont:
def __iter__(self): return iter([])
def __len__(self): return 0
- self.assertFalse(issubclass(ColNoIter, Collection))
- self.assertFalse(isinstance(ColNoIter(), Collection))
- self.assertFalse(issubclass(ColNoSize, Collection))
- self.assertFalse(isinstance(ColNoSize(), Collection))
- self.assertFalse(issubclass(ColNoCont, Collection))
- self.assertFalse(isinstance(ColNoCont(), Collection))
+ self.assertNotIsSubclass(ColNoIter, Collection)
+ self.assertNotIsInstance(ColNoIter(), Collection)
+ self.assertNotIsSubclass(ColNoSize, Collection)
+ self.assertNotIsInstance(ColNoSize(), Collection)
+ self.assertNotIsSubclass(ColNoCont, Collection)
+ self.assertNotIsInstance(ColNoCont(), Collection)
# Check None blocking
class SizeBlock:
def __iter__(self): return iter([])
@@ -1124,10 +1123,10 @@ class IterBlock:
def __len__(self): return 0
def __contains__(self): return True
__iter__ = None
- self.assertFalse(issubclass(SizeBlock, Collection))
- self.assertFalse(isinstance(SizeBlock(), Collection))
- self.assertFalse(issubclass(IterBlock, Collection))
- self.assertFalse(isinstance(IterBlock(), Collection))
+ self.assertNotIsSubclass(SizeBlock, Collection)
+ self.assertNotIsInstance(SizeBlock(), Collection)
+ self.assertNotIsSubclass(IterBlock, Collection)
+ self.assertNotIsInstance(IterBlock(), Collection)
# Check None blocking in subclass
class ColImpl:
def __iter__(self):
@@ -1138,15 +1137,15 @@ def __contains__(self, item):
return False
class NonCol(ColImpl):
__contains__ = None
- self.assertFalse(issubclass(NonCol, Collection))
- self.assertFalse(isinstance(NonCol(), Collection))
+ self.assertNotIsSubclass(NonCol, Collection)
+ self.assertNotIsInstance(NonCol(), Collection)
def test_Iterator(self):
non_samples = [None, 42, 3.14, 1j, b"", "", (), [], {}, set()]
for x in non_samples:
self.assertNotIsInstance(x, Iterator)
- self.assertFalse(issubclass(type(x), Iterator), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Iterator)
samples = [iter(bytes()), iter(str()),
iter(tuple()), iter(list()), iter(dict()),
iter(set()), iter(frozenset()),
@@ -1157,7 +1156,7 @@ def test_Iterator(self):
]
for x in samples:
self.assertIsInstance(x, Iterator)
- self.assertTrue(issubclass(type(x), Iterator), repr(type(x)))
+ self.assertIsSubclass(type(x), Iterator)
self.validate_abstract_methods(Iterator, '__next__', '__iter__')
# Issue 10565
@@ -1190,7 +1189,7 @@ def throw(self, typ, val=None, tb=None): pass
iter(()), iter([]), NonGen1(), NonGen2(), NonGen3()]
for x in non_samples:
self.assertNotIsInstance(x, Generator)
- self.assertFalse(issubclass(type(x), Generator), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Generator)
class Gen:
def __iter__(self): return self
@@ -1212,7 +1211,7 @@ def gen():
for x in samples:
self.assertIsInstance(x, Iterator)
self.assertIsInstance(x, Generator)
- self.assertTrue(issubclass(type(x), Generator), repr(type(x)))
+ self.assertIsSubclass(type(x), Generator)
self.validate_abstract_methods(Generator, 'send', 'throw')
# mixin tests
@@ -1261,7 +1260,7 @@ def athrow(self, typ, val=None, tb=None): pass
iter(()), iter([]), NonAGen1(), NonAGen2(), NonAGen3()]
for x in non_samples:
self.assertNotIsInstance(x, AsyncGenerator)
- self.assertFalse(issubclass(type(x), AsyncGenerator), repr(type(x)))
+ self.assertNotIsSubclass(type(x), AsyncGenerator)
class Gen:
def __aiter__(self): return self
@@ -1283,7 +1282,7 @@ async def gen():
for x in samples:
self.assertIsInstance(x, AsyncIterator)
self.assertIsInstance(x, AsyncGenerator)
- self.assertTrue(issubclass(type(x), AsyncGenerator), repr(type(x)))
+ self.assertIsSubclass(type(x), AsyncGenerator)
self.validate_abstract_methods(AsyncGenerator, 'asend', 'athrow')
def run_async(coro):
@@ -1326,14 +1325,14 @@ def test_Sized(self):
]
for x in non_samples:
self.assertNotIsInstance(x, Sized)
- self.assertFalse(issubclass(type(x), Sized), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Sized)
samples = [bytes(), str(),
tuple(), list(), set(), frozenset(), dict(),
dict().keys(), dict().items(), dict().values(),
]
for x in samples:
self.assertIsInstance(x, Sized)
- self.assertTrue(issubclass(type(x), Sized), repr(type(x)))
+ self.assertIsSubclass(type(x), Sized)
self.validate_abstract_methods(Sized, '__len__')
self.validate_isinstance(Sized, '__len__')
@@ -1344,14 +1343,14 @@ def test_Container(self):
]
for x in non_samples:
self.assertNotIsInstance(x, Container)
- self.assertFalse(issubclass(type(x), Container), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Container)
samples = [bytes(), str(),
tuple(), list(), set(), frozenset(), dict(),
dict().keys(), dict().items(),
]
for x in samples:
self.assertIsInstance(x, Container)
- self.assertTrue(issubclass(type(x), Container), repr(type(x)))
+ self.assertIsSubclass(type(x), Container)
self.validate_abstract_methods(Container, '__contains__')
self.validate_isinstance(Container, '__contains__')
@@ -1363,7 +1362,7 @@ def test_Callable(self):
]
for x in non_samples:
self.assertNotIsInstance(x, Callable)
- self.assertFalse(issubclass(type(x), Callable), repr(type(x)))
+ self.assertNotIsSubclass(type(x), Callable)
samples = [lambda: None,
type, int, object,
len,
@@ -1371,7 +1370,7 @@ def test_Callable(self):
]
for x in samples:
self.assertIsInstance(x, Callable)
- self.assertTrue(issubclass(type(x), Callable), repr(type(x)))
+ self.assertIsSubclass(type(x), Callable)
self.validate_abstract_methods(Callable, '__call__')
self.validate_isinstance(Callable, '__call__')
@@ -1379,16 +1378,16 @@ def test_direct_subclassing(self):
for B in Hashable, Iterable, Iterator, Reversible, Sized, Container, Callable:
class C(B):
pass
- self.assertTrue(issubclass(C, B))
- self.assertFalse(issubclass(int, C))
+ self.assertIsSubclass(C, B)
+ self.assertNotIsSubclass(int, C)
def test_registration(self):
for B in Hashable, Iterable, Iterator, Reversible, Sized, Container, Callable:
class C:
__hash__ = None # Make sure it isn't hashable by default
- self.assertFalse(issubclass(C, B), B.__name__)
+ self.assertNotIsSubclass(C, B)
B.register(C)
- self.assertTrue(issubclass(C, B))
+ self.assertIsSubclass(C, B)
class WithSet(MutableSet):
@@ -1419,7 +1418,7 @@ class TestCollectionABCs(ABCTestCase):
def test_Set(self):
for sample in [set, frozenset]:
self.assertIsInstance(sample(), Set)
- self.assertTrue(issubclass(sample, Set))
+ self.assertIsSubclass(sample, Set)
self.validate_abstract_methods(Set, '__contains__', '__iter__', '__len__')
class MySet(Set):
def __contains__(self, x):
@@ -1500,9 +1499,9 @@ def __len__(self):
def test_MutableSet(self):
self.assertIsInstance(set(), MutableSet)
- self.assertTrue(issubclass(set, MutableSet))
+ self.assertIsSubclass(set, MutableSet)
self.assertNotIsInstance(frozenset(), MutableSet)
- self.assertFalse(issubclass(frozenset, MutableSet))
+ self.assertNotIsSubclass(frozenset, MutableSet)
self.validate_abstract_methods(MutableSet, '__contains__', '__iter__', '__len__',
'add', 'discard')
@@ -1841,7 +1840,7 @@ def test_Set_hash_matches_frozenset(self):
def test_Mapping(self):
for sample in [dict]:
self.assertIsInstance(sample(), Mapping)
- self.assertTrue(issubclass(sample, Mapping))
+ self.assertIsSubclass(sample, Mapping)
self.validate_abstract_methods(Mapping, '__contains__', '__iter__', '__len__',
'__getitem__')
class MyMapping(Mapping):
@@ -1857,7 +1856,7 @@ def __iter__(self):
def test_MutableMapping(self):
for sample in [dict]:
self.assertIsInstance(sample(), MutableMapping)
- self.assertTrue(issubclass(sample, MutableMapping))
+ self.assertIsSubclass(sample, MutableMapping)
self.validate_abstract_methods(MutableMapping, '__contains__', '__iter__', '__len__',
'__getitem__', '__setitem__', '__delitem__')
@@ -1891,12 +1890,12 @@ def test_MutableMapping_subclass(self):
def test_Sequence(self):
for sample in [tuple, list, bytes, str]:
self.assertIsInstance(sample(), Sequence)
- self.assertTrue(issubclass(sample, Sequence))
+ self.assertIsSubclass(sample, Sequence)
self.assertIsInstance(range(10), Sequence)
- self.assertTrue(issubclass(range, Sequence))
+ self.assertIsSubclass(range, Sequence)
self.assertIsInstance(memoryview(b""), Sequence)
- self.assertTrue(issubclass(memoryview, Sequence))
- self.assertTrue(issubclass(str, Sequence))
+ self.assertIsSubclass(memoryview, Sequence)
+ self.assertIsSubclass(str, Sequence)
self.validate_abstract_methods(Sequence, '__contains__', '__iter__', '__len__',
'__getitem__')
@@ -1938,21 +1937,21 @@ def assert_index_same(seq1, seq2, index_args):
def test_Buffer(self):
for sample in [bytes, bytearray, memoryview]:
self.assertIsInstance(sample(b"x"), Buffer)
- self.assertTrue(issubclass(sample, Buffer))
+ self.assertIsSubclass(sample, Buffer)
for sample in [str, list, tuple]:
self.assertNotIsInstance(sample(), Buffer)
- self.assertFalse(issubclass(sample, Buffer))
+ self.assertNotIsSubclass(sample, Buffer)
self.validate_abstract_methods(Buffer, '__buffer__')
def test_MutableSequence(self):
for sample in [tuple, str, bytes]:
self.assertNotIsInstance(sample(), MutableSequence)
- self.assertFalse(issubclass(sample, MutableSequence))
+ self.assertNotIsSubclass(sample, MutableSequence)
for sample in [list, bytearray, deque]:
self.assertIsInstance(sample(), MutableSequence)
- self.assertTrue(issubclass(sample, MutableSequence))
- self.assertTrue(issubclass(array.array, MutableSequence))
- self.assertFalse(issubclass(str, MutableSequence))
+ self.assertIsSubclass(sample, MutableSequence)
+ self.assertIsSubclass(array.array, MutableSequence)
+ self.assertNotIsSubclass(str, MutableSequence)
self.validate_abstract_methods(MutableSequence, '__contains__', '__iter__',
'__len__', '__getitem__', '__setitem__', '__delitem__', 'insert')
@@ -2043,8 +2042,8 @@ def test_basics(self):
self.assertEqual(c, Counter(a=3, b=2, c=1))
self.assertIsInstance(c, dict)
self.assertIsInstance(c, Mapping)
- self.assertTrue(issubclass(Counter, dict))
- self.assertTrue(issubclass(Counter, Mapping))
+ self.assertIsSubclass(Counter, dict)
+ self.assertIsSubclass(Counter, Mapping)
self.assertEqual(len(c), 3)
self.assertEqual(sum(c.values()), 6)
self.assertEqual(list(c.values()), [3, 2, 1])
1
0
Jan. 19, 2025
https://github.com/python/cpython/commit/49b2f31a6b94dae1d986083ac5814b5e89…
commit: 49b2f31a6b94dae1d986083ac5814b5e89b2a710
branch: 3.12
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: hugovk <1324225+hugovk(a)users.noreply.github.com>
date: 2025-01-20T09:17:35+02:00
summary:
[3.12] GH-125722: Use long options for Sphinx (GH-129039) (#129042)
GH-125722: Use long options for Sphinx (GH-129039)
(cherry picked from commit 4967fa6a9c0db7ea3ade905d724a04688cfc7a5e)
Co-authored-by: Adam Turner <9087854+AA-Turner(a)users.noreply.github.com>
files:
M .github/workflows/reusable-docs.yml
M Doc/Makefile
M Doc/make.bat
diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml
index 6ac7d40c4aec27..d01c5e30a9b9fa 100644
--- a/.github/workflows/reusable-docs.yml
+++ b/.github/workflows/reusable-docs.yml
@@ -62,8 +62,8 @@ jobs:
continue-on-error: true
run: |
set -Eeuo pipefail
- # Build docs with the '-n' (nit-picky) option; write warnings to file
- make -C Doc/ PYTHON=../python SPHINXOPTS="-q -n -W --keep-going -w sphinx-warnings.txt" html
+ # Build docs with the nit-picky option; write warnings to file
+ make -C Doc/ PYTHON=../python SPHINXOPTS="--quiet --nitpicky --fail-on-warning --keep-going --warning-file sphinx-warnings.txt" html
- name: 'Check warnings'
if: github.event_name == 'pull_request'
run: |
@@ -98,4 +98,4 @@ jobs:
run: make -C Doc/ PYTHON=../python venv
# Use "xvfb-run" since some doctest tests open GUI windows
- name: 'Run documentation doctest'
- run: xvfb-run make -C Doc/ PYTHON=../python SPHINXERRORHANDLING="-W --keep-going" doctest
+ run: xvfb-run make -C Doc/ PYTHON=../python SPHINXERRORHANDLING="--fail-on-warning --keep-going" doctest
diff --git a/Doc/Makefile b/Doc/Makefile
index 4a704ad58b33d3..1a66642a4a03ed 100644
--- a/Doc/Makefile
+++ b/Doc/Makefile
@@ -14,15 +14,15 @@ PAPER =
SOURCES =
DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py)
REQUIREMENTS = requirements.txt
-SPHINXERRORHANDLING = -W
+SPHINXERRORHANDLING = --fail-on-warning
# Internal variables.
-PAPEROPT_a4 = -D latex_elements.papersize=a4paper
-PAPEROPT_letter = -D latex_elements.papersize=letterpaper
+PAPEROPT_a4 = --define latex_elements.papersize=a4paper
+PAPEROPT_letter = --define latex_elements.papersize=letterpaper
-ALLSPHINXOPTS = -b $(BUILDER) \
- -d build/doctrees \
- -j $(JOBS) \
+ALLSPHINXOPTS = --builder $(BUILDER) \
+ --doctree-dir build/doctrees \
+ --jobs $(JOBS) \
$(PAPEROPT_$(PAPER)) \
$(SPHINXOPTS) $(SPHINXERRORHANDLING) \
. build/$(BUILDER) $(SOURCES)
@@ -144,7 +144,7 @@ pydoc-topics: build
.PHONY: gettext
gettext: BUILDER = gettext
-gettext: override SPHINXOPTS := -d build/doctrees-gettext $(SPHINXOPTS)
+gettext: override SPHINXOPTS := --doctree-dir build/doctrees-gettext $(SPHINXOPTS)
gettext: build
.PHONY: htmlview
@@ -300,20 +300,20 @@ serve:
# By default, Sphinx only rebuilds pages where the page content has changed.
# This means it doesn't always pick up changes to preferred link targets, etc
# To ensure such changes are picked up, we build the published docs with
-# `-E` (to ignore the cached environment) and `-a` (to ignore already existing
-# output files)
+# ``--fresh-env`` (to ignore the cached environment) and ``--write-all``
+# (to ignore already existing output files)
# for development releases: always build
.PHONY: autobuild-dev
autobuild-dev: DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py --short)
autobuild-dev:
- $(MAKE) dist-no-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' DISTVERSION=$(DISTVERSION)
+ $(MAKE) dist-no-html SPHINXOPTS='$(SPHINXOPTS) --fresh-env --write-all --html-define daily=1' DISTVERSION=$(DISTVERSION)
# for HTML-only rebuilds
.PHONY: autobuild-dev-html
autobuild-dev-html: DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py --short)
autobuild-dev-html:
- $(MAKE) dist-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' DISTVERSION=$(DISTVERSION)
+ $(MAKE) dist-html SPHINXOPTS='$(SPHINXOPTS) --fresh-env --write-all --html-define daily=1' DISTVERSION=$(DISTVERSION)
# for stable releases: only build if not in pre-release stage (alpha, beta)
# release candidate downloads are okay, since the stable tree can be in that stage
diff --git a/Doc/make.bat b/Doc/make.bat
index 87d8359ef112bb..ede793ed3c6d70 100644
--- a/Doc/make.bat
+++ b/Doc/make.bat
@@ -144,12 +144,12 @@ if exist ..\Misc\NEWS (
)
if defined PAPER (
- set SPHINXOPTS=-D latex_elements.papersize=%PAPER% %SPHINXOPTS%
+ set SPHINXOPTS=--define latex_elements.papersize=%PAPER% %SPHINXOPTS%
)
if "%1" EQU "htmlhelp" (
- set SPHINXOPTS=-D html_theme_options.body_max_width=none %SPHINXOPTS%
+ set SPHINXOPTS=--define html_theme_options.body_max_width=none %SPHINXOPTS%
)
-cmd /S /C "%SPHINXBUILD% %SPHINXOPTS% -b%1 -dbuild\doctrees . "%BUILDDIR%\%1" %2 %3 %4 %5 %6 %7 %8 %9"
+cmd /S /C "%SPHINXBUILD% %SPHINXOPTS% --builder %1 --doctree-dir build\doctrees . "%BUILDDIR%\%1" %2 %3 %4 %5 %6 %7 %8 %9"
if "%1" EQU "htmlhelp" (
"%HTMLHELP%" "%BUILDDIR%\htmlhelp\python%DISTVERSION:.=%.hhp"
1
0
Jan. 19, 2025
https://github.com/python/cpython/commit/59b919b082bcb96a66eea2b64b6572ceb1…
commit: 59b919b082bcb96a66eea2b64b6572ceb1ce6210
branch: 3.13
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: hugovk <1324225+hugovk(a)users.noreply.github.com>
date: 2025-01-20T09:17:29+02:00
summary:
[3.13] GH-125722: Use long options for Sphinx (GH-129039) (#129041)
GH-125722: Use long options for Sphinx (GH-129039)
(cherry picked from commit 4967fa6a9c0db7ea3ade905d724a04688cfc7a5e)
Co-authored-by: Adam Turner <9087854+AA-Turner(a)users.noreply.github.com>
files:
M .github/workflows/reusable-docs.yml
M Doc/Makefile
M Doc/make.bat
diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml
index 84675fa62e1516..6738acc98c6565 100644
--- a/.github/workflows/reusable-docs.yml
+++ b/.github/workflows/reusable-docs.yml
@@ -65,8 +65,8 @@ jobs:
continue-on-error: true
run: |
set -Eeuo pipefail
- # Build docs with the '-n' (nit-picky) option; write warnings to file
- make -C Doc/ PYTHON=../python SPHINXOPTS="-q -n -W --keep-going -w sphinx-warnings.txt" html
+ # Build docs with the nit-picky option; write warnings to file
+ make -C Doc/ PYTHON=../python SPHINXOPTS="--quiet --nitpicky --fail-on-warning --keep-going --warning-file sphinx-warnings.txt" html
- name: 'Check warnings'
if: github.event_name == 'pull_request'
run: |
@@ -101,4 +101,4 @@ jobs:
run: make -C Doc/ PYTHON=../python venv
# Use "xvfb-run" since some doctest tests open GUI windows
- name: 'Run documentation doctest'
- run: xvfb-run make -C Doc/ PYTHON=../python SPHINXERRORHANDLING="-W --keep-going" doctest
+ run: xvfb-run make -C Doc/ PYTHON=../python SPHINXERRORHANDLING="--fail-on-warning --keep-going" doctest
diff --git a/Doc/Makefile b/Doc/Makefile
index 4a704ad58b33d3..1a66642a4a03ed 100644
--- a/Doc/Makefile
+++ b/Doc/Makefile
@@ -14,15 +14,15 @@ PAPER =
SOURCES =
DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py)
REQUIREMENTS = requirements.txt
-SPHINXERRORHANDLING = -W
+SPHINXERRORHANDLING = --fail-on-warning
# Internal variables.
-PAPEROPT_a4 = -D latex_elements.papersize=a4paper
-PAPEROPT_letter = -D latex_elements.papersize=letterpaper
+PAPEROPT_a4 = --define latex_elements.papersize=a4paper
+PAPEROPT_letter = --define latex_elements.papersize=letterpaper
-ALLSPHINXOPTS = -b $(BUILDER) \
- -d build/doctrees \
- -j $(JOBS) \
+ALLSPHINXOPTS = --builder $(BUILDER) \
+ --doctree-dir build/doctrees \
+ --jobs $(JOBS) \
$(PAPEROPT_$(PAPER)) \
$(SPHINXOPTS) $(SPHINXERRORHANDLING) \
. build/$(BUILDER) $(SOURCES)
@@ -144,7 +144,7 @@ pydoc-topics: build
.PHONY: gettext
gettext: BUILDER = gettext
-gettext: override SPHINXOPTS := -d build/doctrees-gettext $(SPHINXOPTS)
+gettext: override SPHINXOPTS := --doctree-dir build/doctrees-gettext $(SPHINXOPTS)
gettext: build
.PHONY: htmlview
@@ -300,20 +300,20 @@ serve:
# By default, Sphinx only rebuilds pages where the page content has changed.
# This means it doesn't always pick up changes to preferred link targets, etc
# To ensure such changes are picked up, we build the published docs with
-# `-E` (to ignore the cached environment) and `-a` (to ignore already existing
-# output files)
+# ``--fresh-env`` (to ignore the cached environment) and ``--write-all``
+# (to ignore already existing output files)
# for development releases: always build
.PHONY: autobuild-dev
autobuild-dev: DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py --short)
autobuild-dev:
- $(MAKE) dist-no-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' DISTVERSION=$(DISTVERSION)
+ $(MAKE) dist-no-html SPHINXOPTS='$(SPHINXOPTS) --fresh-env --write-all --html-define daily=1' DISTVERSION=$(DISTVERSION)
# for HTML-only rebuilds
.PHONY: autobuild-dev-html
autobuild-dev-html: DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py --short)
autobuild-dev-html:
- $(MAKE) dist-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' DISTVERSION=$(DISTVERSION)
+ $(MAKE) dist-html SPHINXOPTS='$(SPHINXOPTS) --fresh-env --write-all --html-define daily=1' DISTVERSION=$(DISTVERSION)
# for stable releases: only build if not in pre-release stage (alpha, beta)
# release candidate downloads are okay, since the stable tree can be in that stage
diff --git a/Doc/make.bat b/Doc/make.bat
index 87d8359ef112bb..ede793ed3c6d70 100644
--- a/Doc/make.bat
+++ b/Doc/make.bat
@@ -144,12 +144,12 @@ if exist ..\Misc\NEWS (
)
if defined PAPER (
- set SPHINXOPTS=-D latex_elements.papersize=%PAPER% %SPHINXOPTS%
+ set SPHINXOPTS=--define latex_elements.papersize=%PAPER% %SPHINXOPTS%
)
if "%1" EQU "htmlhelp" (
- set SPHINXOPTS=-D html_theme_options.body_max_width=none %SPHINXOPTS%
+ set SPHINXOPTS=--define html_theme_options.body_max_width=none %SPHINXOPTS%
)
-cmd /S /C "%SPHINXBUILD% %SPHINXOPTS% -b%1 -dbuild\doctrees . "%BUILDDIR%\%1" %2 %3 %4 %5 %6 %7 %8 %9"
+cmd /S /C "%SPHINXBUILD% %SPHINXOPTS% --builder %1 --doctree-dir build\doctrees . "%BUILDDIR%\%1" %2 %3 %4 %5 %6 %7 %8 %9"
if "%1" EQU "htmlhelp" (
"%HTMLHELP%" "%BUILDDIR%\htmlhelp\python%DISTVERSION:.=%.hhp"
1
0
https://github.com/python/cpython/commit/4967fa6a9c0db7ea3ade905d724a04688c…
commit: 4967fa6a9c0db7ea3ade905d724a04688cfc7a5e
branch: main
author: Adam Turner <9087854+AA-Turner(a)users.noreply.github.com>
committer: hugovk <1324225+hugovk(a)users.noreply.github.com>
date: 2025-01-20T08:11:22+02:00
summary:
GH-125722: Use long options for Sphinx (#129039)
files:
M .github/workflows/reusable-docs.yml
M Doc/Makefile
M Doc/make.bat
diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml
index 84675fa62e1516..6738acc98c6565 100644
--- a/.github/workflows/reusable-docs.yml
+++ b/.github/workflows/reusable-docs.yml
@@ -65,8 +65,8 @@ jobs:
continue-on-error: true
run: |
set -Eeuo pipefail
- # Build docs with the '-n' (nit-picky) option; write warnings to file
- make -C Doc/ PYTHON=../python SPHINXOPTS="-q -n -W --keep-going -w sphinx-warnings.txt" html
+ # Build docs with the nit-picky option; write warnings to file
+ make -C Doc/ PYTHON=../python SPHINXOPTS="--quiet --nitpicky --fail-on-warning --keep-going --warning-file sphinx-warnings.txt" html
- name: 'Check warnings'
if: github.event_name == 'pull_request'
run: |
@@ -101,4 +101,4 @@ jobs:
run: make -C Doc/ PYTHON=../python venv
# Use "xvfb-run" since some doctest tests open GUI windows
- name: 'Run documentation doctest'
- run: xvfb-run make -C Doc/ PYTHON=../python SPHINXERRORHANDLING="-W --keep-going" doctest
+ run: xvfb-run make -C Doc/ PYTHON=../python SPHINXERRORHANDLING="--fail-on-warning --keep-going" doctest
diff --git a/Doc/Makefile b/Doc/Makefile
index 4a704ad58b33d3..1a66642a4a03ed 100644
--- a/Doc/Makefile
+++ b/Doc/Makefile
@@ -14,15 +14,15 @@ PAPER =
SOURCES =
DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py)
REQUIREMENTS = requirements.txt
-SPHINXERRORHANDLING = -W
+SPHINXERRORHANDLING = --fail-on-warning
# Internal variables.
-PAPEROPT_a4 = -D latex_elements.papersize=a4paper
-PAPEROPT_letter = -D latex_elements.papersize=letterpaper
+PAPEROPT_a4 = --define latex_elements.papersize=a4paper
+PAPEROPT_letter = --define latex_elements.papersize=letterpaper
-ALLSPHINXOPTS = -b $(BUILDER) \
- -d build/doctrees \
- -j $(JOBS) \
+ALLSPHINXOPTS = --builder $(BUILDER) \
+ --doctree-dir build/doctrees \
+ --jobs $(JOBS) \
$(PAPEROPT_$(PAPER)) \
$(SPHINXOPTS) $(SPHINXERRORHANDLING) \
. build/$(BUILDER) $(SOURCES)
@@ -144,7 +144,7 @@ pydoc-topics: build
.PHONY: gettext
gettext: BUILDER = gettext
-gettext: override SPHINXOPTS := -d build/doctrees-gettext $(SPHINXOPTS)
+gettext: override SPHINXOPTS := --doctree-dir build/doctrees-gettext $(SPHINXOPTS)
gettext: build
.PHONY: htmlview
@@ -300,20 +300,20 @@ serve:
# By default, Sphinx only rebuilds pages where the page content has changed.
# This means it doesn't always pick up changes to preferred link targets, etc
# To ensure such changes are picked up, we build the published docs with
-# `-E` (to ignore the cached environment) and `-a` (to ignore already existing
-# output files)
+# ``--fresh-env`` (to ignore the cached environment) and ``--write-all``
+# (to ignore already existing output files)
# for development releases: always build
.PHONY: autobuild-dev
autobuild-dev: DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py --short)
autobuild-dev:
- $(MAKE) dist-no-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' DISTVERSION=$(DISTVERSION)
+ $(MAKE) dist-no-html SPHINXOPTS='$(SPHINXOPTS) --fresh-env --write-all --html-define daily=1' DISTVERSION=$(DISTVERSION)
# for HTML-only rebuilds
.PHONY: autobuild-dev-html
autobuild-dev-html: DISTVERSION = $(shell $(PYTHON) tools/extensions/patchlevel.py --short)
autobuild-dev-html:
- $(MAKE) dist-html SPHINXOPTS='$(SPHINXOPTS) -Ea -A daily=1' DISTVERSION=$(DISTVERSION)
+ $(MAKE) dist-html SPHINXOPTS='$(SPHINXOPTS) --fresh-env --write-all --html-define daily=1' DISTVERSION=$(DISTVERSION)
# for stable releases: only build if not in pre-release stage (alpha, beta)
# release candidate downloads are okay, since the stable tree can be in that stage
diff --git a/Doc/make.bat b/Doc/make.bat
index 87d8359ef112bb..ede793ed3c6d70 100644
--- a/Doc/make.bat
+++ b/Doc/make.bat
@@ -144,12 +144,12 @@ if exist ..\Misc\NEWS (
)
if defined PAPER (
- set SPHINXOPTS=-D latex_elements.papersize=%PAPER% %SPHINXOPTS%
+ set SPHINXOPTS=--define latex_elements.papersize=%PAPER% %SPHINXOPTS%
)
if "%1" EQU "htmlhelp" (
- set SPHINXOPTS=-D html_theme_options.body_max_width=none %SPHINXOPTS%
+ set SPHINXOPTS=--define html_theme_options.body_max_width=none %SPHINXOPTS%
)
-cmd /S /C "%SPHINXBUILD% %SPHINXOPTS% -b%1 -dbuild\doctrees . "%BUILDDIR%\%1" %2 %3 %4 %5 %6 %7 %8 %9"
+cmd /S /C "%SPHINXBUILD% %SPHINXOPTS% --builder %1 --doctree-dir build\doctrees . "%BUILDDIR%\%1" %2 %3 %4 %5 %6 %7 %8 %9"
if "%1" EQU "htmlhelp" (
"%HTMLHELP%" "%BUILDDIR%\htmlhelp\python%DISTVERSION:.=%.hhp"
1
0
[3.13] GH-125722: Increase minimum supported Sphinx to 8.1.3 (GH-128922) (#129037)
by AA-Turner Jan. 19, 2025
by AA-Turner Jan. 19, 2025
Jan. 19, 2025
https://github.com/python/cpython/commit/5fe76505524c64c16c2ad8401d3de2ce39…
commit: 5fe76505524c64c16c2ad8401d3de2ce39d6fb14
branch: 3.13
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: AA-Turner <9087854+AA-Turner(a)users.noreply.github.com>
date: 2025-01-20T00:52:00Z
summary:
[3.13] GH-125722: Increase minimum supported Sphinx to 8.1.3 (GH-128922) (#129037)
(cherry picked from commit d46b577ec026c2e700a9f920f81cfbf698e53eb6)
Co-authored-by: Adam Turner <9087854+AA-Turner(a)users.noreply.github.com>
files:
A Misc/NEWS.d/next/Documentation/2025-01-16-18-59-11.gh-issue-125722.eHHRga.rst
D Doc/requirements-oldest-sphinx.txt
M .github/workflows/reusable-docs.yml
M Doc/conf.py
M Doc/constraints.txt
M Doc/requirements.txt
M Doc/tools/extensions/c_annotations.py
diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml
index 88da55bf08b8fe..84675fa62e1516 100644
--- a/.github/workflows/reusable-docs.yml
+++ b/.github/workflows/reusable-docs.yml
@@ -76,26 +76,6 @@ jobs:
--fail-if-improved \
--fail-if-new-news-nit
- # This build doesn't use problem matchers or check annotations
- build_doc_oldest_supported_sphinx:
- name: 'Docs (Oldest Sphinx)'
- runs-on: ubuntu-latest
- timeout-minutes: 60
- steps:
- - uses: actions/checkout@v4
- with:
- persist-credentials: false
- - name: 'Set up Python'
- uses: actions/setup-python@v5
- with:
- python-version: '3.13' # known to work with Sphinx 7.2.6
- cache: 'pip'
- cache-dependency-path: 'Doc/requirements-oldest-sphinx.txt'
- - name: 'Install build dependencies'
- run: make -C Doc/ venv REQUIREMENTS="requirements-oldest-sphinx.txt"
- - name: 'Build HTML documentation'
- run: make -C Doc/ SPHINXOPTS="-q" SPHINXERRORHANDLING="-W --keep-going" html
-
# Run "doctest" on HEAD as new syntax doesn't exist in the latest stable release
doctest:
name: 'Doctest'
diff --git a/Doc/conf.py b/Doc/conf.py
index 829124c86c8b19..269a5007e0b6c9 100644
--- a/Doc/conf.py
+++ b/Doc/conf.py
@@ -9,9 +9,6 @@
import importlib
import os
import sys
-import time
-
-import sphinx
# Make our custom extensions available to Sphinx
sys.path.append(os.path.abspath('tools/extensions'))
@@ -67,10 +64,7 @@
# General substitutions.
project = 'Python'
-if sphinx.version_info[:2] >= (8, 1):
- copyright = "2001-%Y, Python Software Foundation"
-else:
- copyright = f"2001-{time.strftime('%Y')}, Python Software Foundation"
+copyright = "2001-%Y, Python Software Foundation"
# We look for the Include/patchlevel.h file in the current Python source tree
# and replace the values accordingly.
@@ -93,7 +87,8 @@
highlight_language = 'python3'
# Minimum version of sphinx required
-needs_sphinx = '7.2.6'
+# Keep this version in sync with ``Doc/requirements.txt``.
+needs_sphinx = '8.1.3'
# Create table of contents entries for domain objects (e.g. functions, classes,
# attributes, etc.). Default is True.
@@ -372,13 +367,7 @@
# This 'Last updated on:' timestamp is inserted at the bottom of every page.
html_last_updated_fmt = '%b %d, %Y (%H:%M UTC)'
-if sphinx.version_info[:2] >= (8, 1):
- html_last_updated_use_utc = True
-else:
- html_time = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
- html_last_updated_fmt = time.strftime(
- html_last_updated_fmt, time.gmtime(html_time)
- )
+html_last_updated_use_utc = True
# Path to find HTML templates to override theme
templates_path = ['tools/templates']
@@ -615,16 +604,6 @@
}
extlinks_detect_hardcoded_links = True
-if sphinx.version_info[:2] < (8, 1):
- # Sphinx 8.1 has in-built CVE and CWE roles.
- extlinks |= {
- "cve": (
- "https://www.cve.org/CVERecord?id=CVE-%s",
- "CVE-%s",
- ),
- "cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
- }
-
# Options for c_annotations extension
# -----------------------------------
diff --git a/Doc/constraints.txt b/Doc/constraints.txt
index 26ac1862dbac0b..29cd4be1d3c8db 100644
--- a/Doc/constraints.txt
+++ b/Doc/constraints.txt
@@ -13,14 +13,12 @@ packaging<25
Pygments<3
requests<3
snowballstemmer<3
-# keep lower-bounds until Sphinx 8.1 is released
-# https://github.com/sphinx-doc/sphinx/pull/12756
-sphinxcontrib-applehelp>=1.0.7,<3
-sphinxcontrib-devhelp>=1.0.6,<3
-sphinxcontrib-htmlhelp>=2.0.6,<3
-sphinxcontrib-jsmath>=1.0.1,<2
-sphinxcontrib-qthelp>=1.0.6,<3
-sphinxcontrib-serializinghtml>=1.1.9,<3
+sphinxcontrib-applehelp<3
+sphinxcontrib-devhelp<3
+sphinxcontrib-htmlhelp<3
+sphinxcontrib-jsmath<2
+sphinxcontrib-qthelp<3
+sphinxcontrib-serializinghtml<3
# Direct dependencies of Jinja2 (Jinja is a dependency of Sphinx, see above)
MarkupSafe<3
diff --git a/Doc/requirements-oldest-sphinx.txt b/Doc/requirements-oldest-sphinx.txt
deleted file mode 100644
index c8027a05706c21..00000000000000
--- a/Doc/requirements-oldest-sphinx.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-# Requirements to build the Python documentation, for the oldest supported
-# Sphinx version.
-#
-# We pin Sphinx and all of its dependencies to ensure a consistent environment.
-
-blurb
-python-docs-theme>=2022.1
-
-# Generated from:
-# pip install "Sphinx~=7.2.6"
-# pip freeze
-#
-# Sphinx 7.2.6 comes from ``needs_sphinx = '7.2.6'`` in ``Doc/conf.py``.
-
-alabaster==0.7.16
-babel==2.16.0
-certifi==2024.12.14
-charset-normalizer==3.4.0
-docutils==0.20.1
-idna==3.10
-imagesize==1.4.1
-Jinja2==3.1.5
-MarkupSafe==3.0.2
-packaging==24.2
-Pygments==2.18.0
-requests==2.32.3
-snowballstemmer==2.2.0
-Sphinx==7.2.6
-sphinxcontrib-applehelp==2.0.0
-sphinxcontrib-devhelp==2.0.0
-sphinxcontrib-htmlhelp==2.1.0
-sphinxcontrib-jsmath==1.0.1
-sphinxcontrib-qthelp==2.0.0
-sphinxcontrib-serializinghtml==2.0.0
-urllib3==2.3.0
diff --git a/Doc/requirements.txt b/Doc/requirements.txt
index 5105786ccf283c..32ff8f74d05bb6 100644
--- a/Doc/requirements.txt
+++ b/Doc/requirements.txt
@@ -3,9 +3,10 @@
# Note that when updating this file, you will likely also have to update
# the Doc/constraints.txt file.
-# Sphinx version is pinned so that new versions that introduce new warnings
+# The Sphinx version is pinned so that new versions that introduce new warnings
# won't suddenly cause build failures. Updating the version is fine as long
# as no warnings are raised by doing so.
+# Keep this version in sync with ``Doc/conf.py``.
sphinx~=8.1.0
blurb
diff --git a/Doc/tools/extensions/c_annotations.py b/Doc/tools/extensions/c_annotations.py
index 50065d34a2c27a..089614a1f6c421 100644
--- a/Doc/tools/extensions/c_annotations.py
+++ b/Doc/tools/extensions/c_annotations.py
@@ -16,7 +16,6 @@
from pathlib import Path
from typing import TYPE_CHECKING
-import sphinx
from docutils import nodes
from docutils.statemachine import StringList
from sphinx import addnodes
@@ -285,16 +284,6 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.connect("builder-inited", init_annotations)
app.connect("doctree-read", add_annotations)
- if sphinx.version_info[:2] < (7, 2):
- from docutils.parsers.rst import directives
- from sphinx.domains.c import CObject
-
- # monkey-patch C object...
- CObject.option_spec |= {
- "no-index-entry": directives.flag,
- "no-contents-entry": directives.flag,
- }
-
return {
"version": "1.0",
"parallel_read_safe": True,
diff --git a/Misc/NEWS.d/next/Documentation/2025-01-16-18-59-11.gh-issue-125722.eHHRga.rst b/Misc/NEWS.d/next/Documentation/2025-01-16-18-59-11.gh-issue-125722.eHHRga.rst
new file mode 100644
index 00000000000000..bf6253eed2eb90
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2025-01-16-18-59-11.gh-issue-125722.eHHRga.rst
@@ -0,0 +1,2 @@
+Require Sphinx 8.1.3 or later to build the Python documentation. Patch by
+Adam Turner.
1
0
[3.12] GH-125722: Increase minimum supported Sphinx to 8.1.3 (GH-128922) (#129038)
by AA-Turner Jan. 19, 2025
by AA-Turner Jan. 19, 2025
Jan. 19, 2025
https://github.com/python/cpython/commit/6f19c6a705d4bdbf01f4e704e42f38d375…
commit: 6f19c6a705d4bdbf01f4e704e42f38d37597b60b
branch: 3.12
author: Miss Islington (bot) <31488909+miss-islington(a)users.noreply.github.com>
committer: AA-Turner <9087854+AA-Turner(a)users.noreply.github.com>
date: 2025-01-20T00:46:05Z
summary:
[3.12] GH-125722: Increase minimum supported Sphinx to 8.1.3 (GH-128922) (#129038)
(cherry picked from commit d46b577ec026c2e700a9f920f81cfbf698e53eb6)
Co-authored-by: Adam Turner <9087854+AA-Turner(a)users.noreply.github.com>
files:
A Misc/NEWS.d/next/Documentation/2025-01-16-18-59-11.gh-issue-125722.eHHRga.rst
D Doc/requirements-oldest-sphinx.txt
M .github/workflows/reusable-docs.yml
M Doc/conf.py
M Doc/constraints.txt
M Doc/requirements.txt
M Doc/tools/extensions/c_annotations.py
diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml
index c5f71831a005fd..6ac7d40c4aec27 100644
--- a/.github/workflows/reusable-docs.yml
+++ b/.github/workflows/reusable-docs.yml
@@ -73,26 +73,6 @@ jobs:
--fail-if-improved \
--fail-if-new-news-nit
- # This build doesn't use problem matchers or check annotations
- build_doc_oldest_supported_sphinx:
- name: 'Docs (Oldest Sphinx)'
- runs-on: ubuntu-latest
- timeout-minutes: 60
- steps:
- - uses: actions/checkout@v4
- with:
- persist-credentials: false
- - name: 'Set up Python'
- uses: actions/setup-python@v5
- with:
- python-version: '3.13' # known to work with Sphinx 7.2.6
- cache: 'pip'
- cache-dependency-path: 'Doc/requirements-oldest-sphinx.txt'
- - name: 'Install build dependencies'
- run: make -C Doc/ venv REQUIREMENTS="requirements-oldest-sphinx.txt"
- - name: 'Build HTML documentation'
- run: make -C Doc/ SPHINXOPTS="-q" SPHINXERRORHANDLING="-W --keep-going" html
-
# Run "doctest" on HEAD as new syntax doesn't exist in the latest stable release
doctest:
name: 'Doctest'
diff --git a/Doc/conf.py b/Doc/conf.py
index 4b18d02a943f62..4c74040976959c 100644
--- a/Doc/conf.py
+++ b/Doc/conf.py
@@ -9,9 +9,6 @@
import importlib
import os
import sys
-import time
-
-import sphinx
# Make our custom extensions available to Sphinx
sys.path.append(os.path.abspath('tools/extensions'))
@@ -61,10 +58,7 @@
# General substitutions.
project = 'Python'
-if sphinx.version_info[:2] >= (8, 1):
- copyright = "2001-%Y, Python Software Foundation"
-else:
- copyright = f"2001-{time.strftime('%Y')}, Python Software Foundation"
+copyright = "2001-%Y, Python Software Foundation"
# We look for the Include/patchlevel.h file in the current Python source tree
# and replace the values accordingly.
@@ -85,7 +79,8 @@
highlight_language = 'python3'
# Minimum version of sphinx required
-needs_sphinx = '7.2.6'
+# Keep this version in sync with ``Doc/requirements.txt``.
+needs_sphinx = '8.1.3'
# Create table of contents entries for domain objects (e.g. functions, classes,
# attributes, etc.). Default is True.
@@ -350,13 +345,7 @@
# This 'Last updated on:' timestamp is inserted at the bottom of every page.
html_last_updated_fmt = '%b %d, %Y (%H:%M UTC)'
-if sphinx.version_info[:2] >= (8, 1):
- html_last_updated_use_utc = True
-else:
- html_time = int(os.environ.get('SOURCE_DATE_EPOCH', time.time()))
- html_last_updated_fmt = time.strftime(
- html_last_updated_fmt, time.gmtime(html_time)
- )
+html_last_updated_use_utc = True
# Path to find HTML templates to override theme
templates_path = ['tools/templates']
@@ -594,16 +583,6 @@
}
extlinks_detect_hardcoded_links = True
-if sphinx.version_info[:2] < (8, 1):
- # Sphinx 8.1 has in-built CVE and CWE roles.
- extlinks |= {
- "cve": (
- "https://www.cve.org/CVERecord?id=CVE-%s",
- "CVE-%s",
- ),
- "cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
- }
-
# Options for c_annotations extension
# -----------------------------------
diff --git a/Doc/constraints.txt b/Doc/constraints.txt
index 26ac1862dbac0b..29cd4be1d3c8db 100644
--- a/Doc/constraints.txt
+++ b/Doc/constraints.txt
@@ -13,14 +13,12 @@ packaging<25
Pygments<3
requests<3
snowballstemmer<3
-# keep lower-bounds until Sphinx 8.1 is released
-# https://github.com/sphinx-doc/sphinx/pull/12756
-sphinxcontrib-applehelp>=1.0.7,<3
-sphinxcontrib-devhelp>=1.0.6,<3
-sphinxcontrib-htmlhelp>=2.0.6,<3
-sphinxcontrib-jsmath>=1.0.1,<2
-sphinxcontrib-qthelp>=1.0.6,<3
-sphinxcontrib-serializinghtml>=1.1.9,<3
+sphinxcontrib-applehelp<3
+sphinxcontrib-devhelp<3
+sphinxcontrib-htmlhelp<3
+sphinxcontrib-jsmath<2
+sphinxcontrib-qthelp<3
+sphinxcontrib-serializinghtml<3
# Direct dependencies of Jinja2 (Jinja is a dependency of Sphinx, see above)
MarkupSafe<3
diff --git a/Doc/requirements-oldest-sphinx.txt b/Doc/requirements-oldest-sphinx.txt
deleted file mode 100644
index c8027a05706c21..00000000000000
--- a/Doc/requirements-oldest-sphinx.txt
+++ /dev/null
@@ -1,35 +0,0 @@
-# Requirements to build the Python documentation, for the oldest supported
-# Sphinx version.
-#
-# We pin Sphinx and all of its dependencies to ensure a consistent environment.
-
-blurb
-python-docs-theme>=2022.1
-
-# Generated from:
-# pip install "Sphinx~=7.2.6"
-# pip freeze
-#
-# Sphinx 7.2.6 comes from ``needs_sphinx = '7.2.6'`` in ``Doc/conf.py``.
-
-alabaster==0.7.16
-babel==2.16.0
-certifi==2024.12.14
-charset-normalizer==3.4.0
-docutils==0.20.1
-idna==3.10
-imagesize==1.4.1
-Jinja2==3.1.5
-MarkupSafe==3.0.2
-packaging==24.2
-Pygments==2.18.0
-requests==2.32.3
-snowballstemmer==2.2.0
-Sphinx==7.2.6
-sphinxcontrib-applehelp==2.0.0
-sphinxcontrib-devhelp==2.0.0
-sphinxcontrib-htmlhelp==2.1.0
-sphinxcontrib-jsmath==1.0.1
-sphinxcontrib-qthelp==2.0.0
-sphinxcontrib-serializinghtml==2.0.0
-urllib3==2.3.0
diff --git a/Doc/requirements.txt b/Doc/requirements.txt
index 5105786ccf283c..32ff8f74d05bb6 100644
--- a/Doc/requirements.txt
+++ b/Doc/requirements.txt
@@ -3,9 +3,10 @@
# Note that when updating this file, you will likely also have to update
# the Doc/constraints.txt file.
-# Sphinx version is pinned so that new versions that introduce new warnings
+# The Sphinx version is pinned so that new versions that introduce new warnings
# won't suddenly cause build failures. Updating the version is fine as long
# as no warnings are raised by doing so.
+# Keep this version in sync with ``Doc/conf.py``.
sphinx~=8.1.0
blurb
diff --git a/Doc/tools/extensions/c_annotations.py b/Doc/tools/extensions/c_annotations.py
index 50065d34a2c27a..089614a1f6c421 100644
--- a/Doc/tools/extensions/c_annotations.py
+++ b/Doc/tools/extensions/c_annotations.py
@@ -16,7 +16,6 @@
from pathlib import Path
from typing import TYPE_CHECKING
-import sphinx
from docutils import nodes
from docutils.statemachine import StringList
from sphinx import addnodes
@@ -285,16 +284,6 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.connect("builder-inited", init_annotations)
app.connect("doctree-read", add_annotations)
- if sphinx.version_info[:2] < (7, 2):
- from docutils.parsers.rst import directives
- from sphinx.domains.c import CObject
-
- # monkey-patch C object...
- CObject.option_spec |= {
- "no-index-entry": directives.flag,
- "no-contents-entry": directives.flag,
- }
-
return {
"version": "1.0",
"parallel_read_safe": True,
diff --git a/Misc/NEWS.d/next/Documentation/2025-01-16-18-59-11.gh-issue-125722.eHHRga.rst b/Misc/NEWS.d/next/Documentation/2025-01-16-18-59-11.gh-issue-125722.eHHRga.rst
new file mode 100644
index 00000000000000..bf6253eed2eb90
--- /dev/null
+++ b/Misc/NEWS.d/next/Documentation/2025-01-16-18-59-11.gh-issue-125722.eHHRga.rst
@@ -0,0 +1,2 @@
+Require Sphinx 8.1.3 or later to build the Python documentation. Patch by
+Adam Turner.
1
0