Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2398,6 +2398,14 @@ expression support in the :mod:`re` module).
Return a copy of the string with all occurrences of substring *old* replaced by
*new*. If *count* is given, only the first *count* occurrences are replaced.
If *count* is not specified or ``-1``, then all occurrences are replaced.
For example:

.. doctest::

>>> 'spam, spam, spam'.replace('spam', 'eggs')
'eggs, eggs, eggs'
>>> 'spam, spam, spam'.replace('spam', 'eggs', 1)
'eggs, spam, spam'

.. versionchanged:: 3.13
*count* is now supported as a keyword argument.
Expand Down
5 changes: 2 additions & 3 deletions Lib/test/test_capi/test_float.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import math
import random
import platform
import sys
import unittest
import warnings
Expand Down Expand Up @@ -215,8 +214,8 @@ def test_pack_unpack_roundtrip_for_nans(self):
# PyFloat_Pack/Unpack*() API. See also gh-130317 and
# e.g. https://developercommunity.visualstudio.com/t/155064
signaling = 0
if platform.machine().startswith('parisc'):
# HP PA RISC uses 0 for quiet, see:
if _testcapi.nan_msb_is_signaling:
# HP PA RISC and some MIPS CPUs use 0 for quiet, see:
# https://en.wikipedia.org/wiki/NaN#Encoding
signaling = 1
i = make_nan(size, sign, not signaling)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -705,10 +705,6 @@ def test_filter_prompt_displayed(self):
self.assertTrue(self.display.contains_text("myfilter"))


if __name__ == "__main__":
unittest.main()


class TestLiveCollectorThreadNavigation(unittest.TestCase):
"""Tests for thread navigation functionality."""

Expand Down
6 changes: 3 additions & 3 deletions Lib/test/test_struct.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import math
import operator
import unittest
import platform
import struct
import sys
import weakref
Expand Down Expand Up @@ -891,6 +890,7 @@ def test_module_func(self):
self.assertRaises(StopIteration, next, it)

def test_half_float(self):
_testcapi = import_helper.import_module('_testcapi')
# Little-endian examples from:
# http://en.wikipedia.org/wiki/Half_precision_floating-point_format
format_bits_float__cleanRoundtrip_list = [
Expand Down Expand Up @@ -935,8 +935,8 @@ def test_half_float(self):

# Check that packing produces a bit pattern representing a quiet NaN:
# all exponent bits and the msb of the fraction should all be 1.
if platform.machine().startswith('parisc'):
# HP PA RISC uses 0 for quiet, see:
if _testcapi.nan_msb_is_signaling:
# HP PA RISC and some MIPS CPUs use 0 for quiet, see:
# https://en.wikipedia.org/wiki/NaN#Encoding
expected = 0x7c
else:
Expand Down
6 changes: 5 additions & 1 deletion Modules/_testcapi/float.c
Original file line number Diff line number Diff line change
Expand Up @@ -171,5 +171,9 @@ _PyTestCapi_Init_Float(PyObject *mod)
return -1;
}

return 0;
#if (defined(__mips__) && !defined(__mips_nan2008)) || defined(__hppa__)
return PyModule_Add(mod, "nan_msb_is_signaling", PyBool_FromLong(1));
#else
return PyModule_Add(mod, "nan_msb_is_signaling", PyBool_FromLong(0));
#endif
}
57 changes: 57 additions & 0 deletions Tools/check-c-api-docs/ignored_c_api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ Py_LL
Py_SAFE_DOWNCAST
Py_ULL
Py_VA_COPY
PYLONG_BITS_IN_DIGIT
PY_DWORD_MAX
PY_FORMAT_SIZE_T
PY_INT32_T
PY_INT64_T
PY_LITTLE_ENDIAN
PY_LLONG_MAX
PY_LLONG_MIN
PY_LONG_LONG
PY_SIZE_MAX
PY_UINT32_T
PY_UINT64_T
PY_ULLONG_MAX
# patchlevel.h
PYTHON_ABI_STRING
PYTHON_API_STRING
PY_RELEASE_LEVEL_ALPHA
PY_RELEASE_LEVEL_BETA
PY_RELEASE_LEVEL_FINAL
PY_RELEASE_LEVEL_GAMMA
PY_VERSION
# unicodeobject.h
Py_UNICODE_SIZE
# cpython/methodobject.h
Expand Down Expand Up @@ -91,3 +112,39 @@ Py_FrozenMain
# cpython/unicodeobject.h
PyUnicode_IS_COMPACT
PyUnicode_IS_COMPACT_ASCII
# pythonrun.h
PyErr_Display
# cpython/objimpl.h
PyObject_GET_WEAKREFS_LISTPTR
# cpython/pythonrun.h
PyOS_Readline
# cpython/warnings.h
PyErr_Warn
# fileobject.h
PY_STDIOTEXTMODE
# structmember.h
PY_WRITE_RESTRICTED
# pythread.h
PY_TIMEOUT_T
PY_TIMEOUT_MAX
# cpython/pyctype.h
PY_CTF_ALNUM
PY_CTF_ALPHA
PY_CTF_DIGIT
PY_CTF_LOWER
PY_CTF_SPACE
PY_CTF_UPPER
PY_CTF_XDIGIT
# cpython/code.h
PY_DEF_EVENT
PY_FOREACH_CODE_EVENT
# cpython/funcobject.h
PY_DEF_EVENT
PY_FOREACH_FUNC_EVENT
# cpython/monitoring.h
PY_MONITORING_EVENT_BRANCH
# cpython/dictobject.h
PY_DEF_EVENT
PY_FOREACH_DICT_EVENT
# cpython/pystats.h
PYSTATS_MAX_UOP_ID
43 changes: 19 additions & 24 deletions Tools/check-c-api-docs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
SIMPLE_MACRO_REGEX = re.compile(r"# *define *(\w+)(\(.+\))? ")
SIMPLE_INLINE_REGEX = re.compile(r"static inline .+( |\n)(\w+)")
SIMPLE_DATA_REGEX = re.compile(r"PyAPI_DATA\(.+\) (\w+)")
API_NAME_REGEX = re.compile(r'\bP[yY][a-zA-Z0-9_]+')

CPYTHON = Path(__file__).parent.parent.parent
INCLUDE = CPYTHON / "Include"
Expand Down Expand Up @@ -72,24 +73,10 @@ def found_ignored_documented(singular: bool) -> str:
)


def is_documented(name: str) -> bool:
"""
Is a name present in the C API documentation?
"""
for path in C_API_DOCS.iterdir():
if path.is_dir():
continue
if path.suffix != ".rst":
continue

text = path.read_text(encoding="utf-8")
if name in text:
return True

return False


def scan_file_for_docs(filename: str, text: str) -> tuple[list[str], list[str]]:
def scan_file_for_docs(
filename: str,
text: str,
names: set[str]) -> tuple[list[str], list[str]]:
"""
Scan a header file for C API functions.
"""
Expand All @@ -98,22 +85,22 @@ def scan_file_for_docs(filename: str, text: str) -> tuple[list[str], list[str]]:
colors = _colorize.get_colors()

def check_for_name(name: str) -> None:
documented = is_documented(name)
documented = name in names
if documented and (name in IGNORED):
documented_ignored.append(name)
elif not documented and (name not in IGNORED):
undocumented.append(name)

for function in SIMPLE_FUNCTION_REGEX.finditer(text):
name = function.group(2)
if not name.startswith("Py"):
if not API_NAME_REGEX.fullmatch(name):
continue

check_for_name(name)

for macro in SIMPLE_MACRO_REGEX.finditer(text):
name = macro.group(1)
if not name.startswith("Py"):
if not API_NAME_REGEX.fullmatch(name):
continue

if "(" in name:
Expand All @@ -123,14 +110,14 @@ def check_for_name(name: str) -> None:

for inline in SIMPLE_INLINE_REGEX.finditer(text):
name = inline.group(2)
if not name.startswith("Py"):
if not API_NAME_REGEX.fullmatch(name):
continue

check_for_name(name)

for data in SIMPLE_DATA_REGEX.finditer(text):
name = data.group(1)
if not name.startswith("Py"):
if not API_NAME_REGEX.fullmatch(name):
continue

check_for_name(name)
Expand All @@ -152,6 +139,14 @@ def check_for_name(name: str) -> None:


def main() -> None:
print("Gathering C API names from docs...")
names = set()
for path in C_API_DOCS.glob('**/*.rst'):
text = path.read_text(encoding="utf-8")
for name in API_NAME_REGEX.findall(text):
names.add(name)
print(f"Got {len(names)} names!")

print("Scanning for undocumented C API functions...")
files = [*INCLUDE.iterdir(), *(INCLUDE / "cpython").iterdir()]
all_missing: list[str] = []
Expand All @@ -162,7 +157,7 @@ def main() -> None:
continue
assert file.exists()
text = file.read_text(encoding="utf-8")
missing, ignored = scan_file_for_docs(str(file.relative_to(INCLUDE)), text)
missing, ignored = scan_file_for_docs(str(file.relative_to(INCLUDE)), text, names)
all_found_ignored += ignored
all_missing += missing

Expand Down
Loading