diff --git a/Doc/c-api/module.rst b/Doc/c-api/module.rst index 20fd66e35a0d4d6..9f68abba66bc5d6 100644 --- a/Doc/c-api/module.rst +++ b/Doc/c-api/module.rst @@ -247,6 +247,15 @@ Feature slots If ``Py_mod_multiple_interpreters`` is not specified, the import machinery defaults to ``Py_MOD_MULTIPLE_INTERPRETERS_SUPPORTED``. + For historical reasons, the values are declared as pointers (``void *``). + When using :c:type:`PySlot` arrays, use :c:macro:`PySlot_DATA` for + :c:macro:`!Py_mod_multiple_interpreters`: + + .. code-block:: c + + PySlot_DATA(Py_mod_multiple_interpreters, + Py_MOD_PER_INTERPRETER_GIL_SUPPORTED) + .. versionadded:: 3.12 .. c:macro:: Py_mod_gil @@ -272,6 +281,14 @@ Feature slots If ``Py_mod_gil`` is not specified, the import machinery defaults to ``Py_MOD_GIL_USED``. + For historical reasons, the values are declared as pointers (``void *``). + When using :c:type:`PySlot` arrays, use :c:macro:`PySlot_DATA` for + :c:macro:`!Py_mod_gil`: + + .. code-block:: c + + PySlot_DATA(Py_mod_gil, Py_MOD_GIL_NOT_USED) + .. versionadded:: 3.13 diff --git a/Doc/c-api/weakref.rst b/Doc/c-api/weakref.rst index db6ae0a9d4ea3d7..8762a003c5218d3 100644 --- a/Doc/c-api/weakref.rst +++ b/Doc/c-api/weakref.rst @@ -43,7 +43,11 @@ as much as it can. should accept a single parameter, which will be the weak reference object itself. *callback* may also be ``None`` or ``NULL``. If *ob* is not a weakly referenceable object, or if *callback* is not callable, ``None``, or - ``NULL``, this will return ``NULL`` and raise :exc:`TypeError`. + ``NULL``, this will raise :exc:`TypeError` and return ``NULL``. + + .. versionchanged:: next + Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or + ``NULL``. .. seealso:: :c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly @@ -59,7 +63,11 @@ as much as it can. collected; it should accept a single parameter, which will be the weak reference object itself. *callback* may also be ``None`` or ``NULL``. If *ob* is not a weakly referenceable object, or if *callback* is not callable, - ``None``, or ``NULL``, this will return ``NULL`` and raise :exc:`TypeError`. + ``NULL``, this will raise :exc:`TypeError` and return ``NULL``. + + .. versionchanged:: next + Raise :exc:`!TypeError` if *callback* is not callable, ``None``, or + ``NULL``. .. seealso:: :c:func:`PyType_SUPPORTS_WEAKREFS` for checking if *ob* is weakly diff --git a/Doc/library/weakref.rst b/Doc/library/weakref.rst index fcb9e0199fad69e..952609c0e700e5e 100644 --- a/Doc/library/weakref.rst +++ b/Doc/library/weakref.rst @@ -132,6 +132,9 @@ See :ref:`__slots__ documentation ` for details. .. versionchanged:: 3.4 Added the :attr:`__callback__` attribute. + .. versionchanged:: next + Raise :exc:`!TypeError` if *callback* is not callable or ``None``. + .. function:: proxy(object[, callback]) @@ -151,6 +154,9 @@ See :ref:`__slots__ documentation ` for details. Extended the operator support on proxy objects to include the matrix multiplication operators ``@`` and ``@=``. + .. versionchanged:: next + Raise :exc:`!TypeError` if *callback* is not callable or ``None``. + .. function:: getweakrefcount(object) diff --git a/Lib/_pyrepl/utils.py b/Lib/_pyrepl/utils.py index b50426c31ead53a..f2837a1b8eb95e7 100644 --- a/Lib/_pyrepl/utils.py +++ b/Lib/_pyrepl/utils.py @@ -274,7 +274,7 @@ def is_soft_keyword_used(*tokens: TI | None) -> bool: None | TI(T.NEWLINE) | TI(T.INDENT) | TI(T.DEDENT) | TI(string=":"), TI(string="case"), TI(T.NUMBER | T.STRING | T.FSTRING_START | T.TSTRING_START) - | TI(T.OP, string="(" | "*" | "-" | "[" | "{") + | TI(T.OP, string="(" | "*" | "-" | "+" | "[" | "{") ): return True case ( diff --git a/Lib/pydoc.py b/Lib/pydoc.py index 497cc7d90a42456..ca4eb1001981681 100644 --- a/Lib/pydoc.py +++ b/Lib/pydoc.py @@ -1240,6 +1240,17 @@ def indent(self, text, prefix=' '): lines = [(prefix + line).rstrip() for line in text.split('\n')] return '\n'.join(lines) + def _format_doc(self, text, width=68): + """Wraps the single-line summary if it is too long.""" + if not text: return '' + lines = text.split('\n', 2) + if len(lines) > 1 and lines[1]: + return text + lines[:1] = textwrap.wrap(lines[0], width, + break_long_words=False, + break_on_hyphens=False) + return '\n'.join(lines) + def section(self, title, contents): """Format a section with a given heading.""" clean_contents = self.indent(contents).rstrip() @@ -1390,6 +1401,7 @@ def makename(c, m=object.__module__): doc = getdoc(object) if doc: + doc = self._format_doc(doc) push(doc + '\n') # List the mro, if non-trivial. @@ -1590,6 +1602,7 @@ def docroutine(self, object, name=None, mod=None, cl=None, homecls=None): return decl + '\n' else: doc = getdoc(object) or '' + doc = self._format_doc(doc) return decl + '\n' + (doc and self.indent(doc).rstrip() + '\n') def docdata(self, object, name=None, mod=None, cl=None, *ignored): @@ -1602,6 +1615,7 @@ def docdata(self, object, name=None, mod=None, cl=None, *ignored): push('\n') doc = getdoc(object) or '' if doc: + doc = self._format_doc(doc) push(self.indent(doc)) push('\n') return ''.join(results) @@ -1620,7 +1634,8 @@ def docother(self, object, name=None, mod=None, parent=None, *ignored, if not doc: doc = getdoc(object) if doc: - line += '\n' + self.indent(str(doc)) + '\n' + doc = self._format_doc(str(doc)) + line += '\n' + self.indent(doc) + '\n' return line class _PlainTextDoc(TextDoc): diff --git a/Lib/test/test_capi/test_weakref.py b/Lib/test/test_capi/test_weakref.py index 86ebe92da8d95db..7c9e97f571979dc 100644 --- a/Lib/test/test_capi/test_weakref.py +++ b/Lib/test/test_capi/test_weakref.py @@ -97,6 +97,7 @@ def test_pyweakref_newref(self): # PyWeakref_NewRef() handles None callback as NULL callback wr = newref(obj, None) self.assertIs(type(wr), weakref.ReferenceType) + self.assertRaises(TypeError, newref, obj, 42) log = [] wr = newref(obj, log.append) self.assertIs(type(wr), weakref.ReferenceType) @@ -116,6 +117,7 @@ def test_pyweakref_newproxy(self): # PyWeakref_NewProxy() handles None callback as NULL callback wp = newproxy(obj, None) self.assertIs(type(wp), weakref.ProxyType) + self.assertRaises(TypeError, newproxy, obj, 42) log = [] wp = newproxy(obj, log.append) self.assertIs(type(wp), weakref.ProxyType) diff --git a/Lib/test/test_dtrace.py b/Lib/test/test_dtrace.py index 61320a472f3e02c..6286b6d21b572e3 100644 --- a/Lib/test/test_dtrace.py +++ b/Lib/test/test_dtrace.py @@ -378,11 +378,14 @@ def setUpClass(cls): def get_readelf_version(): try: cmd = ["readelf", "--version"] + # Force the C locale to disable localization. + env = dict(os.environ, LC_ALL="C") proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, + env=env, ) with proc: version, stderr = proc.communicate() @@ -405,12 +408,36 @@ def get_readelf_version(): return int(match.group(1)), int(match.group(2)) def get_readelf_output(self): - command = ["readelf", "-n", sys.executable] + binary = sys.executable + if sysconfig.get_config_var("Py_ENABLE_SHARED"): + lib_dir = sysconfig.get_config_var("LIBDIR") + if not lib_dir or sysconfig.is_python_build(): + lib_dir = os.path.abspath(os.path.dirname(sys.executable)) + + lib_names = [] + for name in ( + sysconfig.get_config_var("INSTSONAME"), + sysconfig.get_config_var("LDLIBRARY"), + ): + if name and name not in lib_names: + lib_names.append(name) + + if lib_dir: + for name in lib_names: + libpython_path = os.path.join(lib_dir, name) + if os.path.exists(libpython_path): + binary = libpython_path + break + + command = ["readelf", "-n", binary] + # Force the C locale to disable localization. + env = dict(os.environ, LC_ALL="C") stdout, _ = subprocess.Popen( command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, + env=env, ).communicate() return stdout diff --git a/Lib/test/test_pydoc/longsummary.py b/Lib/test/test_pydoc/longsummary.py new file mode 100644 index 000000000000000..0e566d33454119b --- /dev/null +++ b/Lib/test/test_pydoc/longsummary.py @@ -0,0 +1,29 @@ +class C: + """This is a class summary that consists of a very long single line, exceeding the recommended PEP 8 limit. + + The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. + """ + def meth(self): + """This is a method summary that consists of a very long single line, exceeding the recommended PEP 8 limit. + + The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. + """ + + @property + def prop(self): + """This is a property summary that consists of a very long single line, exceeding the recommended PEP 8 limit. + + The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. + """ + +def func(self): + """This is a function summary that consists of a very long single line, exceeding the recommended PEP 8 limit. + + The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. + """ + +data = C() +data.__doc__ = """This is a data summary that consists of a very long single line, exceeding the recommended PEP 8 limit. + +The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. +""" diff --git a/Lib/test/test_pydoc/test_pydoc.py b/Lib/test/test_pydoc/test_pydoc.py index 5cd26923f75c311..5543c664528e6c8 100644 --- a/Lib/test/test_pydoc/test_pydoc.py +++ b/Lib/test/test_pydoc/test_pydoc.py @@ -1245,6 +1245,71 @@ def function_with_really_long_name_so_annotations_can_be_rather_small( lambda very_long_parameter_name_that_should_not_fit_into_a_single_line, second_very_long_parameter_name ''' % __name__) + @requires_docstrings + def test_long_summaries(self): + from . import longsummary + doc = pydoc.render_doc(longsummary) + doc = clean_text(doc) + self.assertEqual(doc, '''Python Library Documentation: module test.test_pydoc.longsummary in test.test_pydoc + +NAME + test.test_pydoc.longsummary + +CLASSES + builtins.object + C + + class C(builtins.object) + | This is a class summary that consists of a very long single line, + | exceeding the recommended PEP 8 limit. + | + | The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. + | + | Methods defined here: + | + | meth(self) + | This is a method summary that consists of a very long single line, + | exceeding the recommended PEP 8 limit. + | + | The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. + | + | ---------------------------------------------------------------------- + | Readonly properties defined here: + | + | prop + | This is a property summary that consists of a very long single line, + | exceeding the recommended PEP 8 limit. + | + | The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. + | + | ---------------------------------------------------------------------- + | Data descriptors defined here: + | + | __dict__ + | dictionary for instance variables + | + | __weakref__ + | list of weak references to the object + +FUNCTIONS + func(self) + This is a function summary that consists of a very long single line, + exceeding the recommended PEP 8 limit. + + The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. + +DATA + data = + This is a data summary that consists of a very long single line, + exceeding the recommended PEP 8 limit. + + The rest of the docstring body, separated from the summary by a blank line, can also contain very long lines. + +FILE + %s + +''' % inspect.getabsfile(longsummary)) + def test__future__imports(self): # __future__ features are excluded from module help, # except when it's the __future__ module itself diff --git a/Lib/test/test_pyrepl/test_utils.py b/Lib/test/test_pyrepl/test_utils.py index 3c55b6bdaeee9e8..0b873d32b62b5d2 100644 --- a/Lib/test/test_pyrepl/test_utils.py +++ b/Lib/test/test_pyrepl/test_utils.py @@ -125,6 +125,14 @@ def test_gen_colors_keyword_highlighting(self): ("import", "keyword"), ], ), + ( + "case +1", + [ + ("case", "soft_keyword"), + ("+", "op"), + ("1", "number"), + ], + ), ] for code, expected_highlights in cases: with self.subTest(code=code): diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index 7dc3364561d8a11..2c6324a14a8e2fc 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -206,7 +206,7 @@ def test_recursion_error_during_traceback(self): sys.setrecursionlimit(15) def f(): - ref(lambda: 0, []) + ref(lambda: 0, ord) f() try: diff --git a/Lib/test/test_venv.py b/Lib/test/test_venv.py index 1ff5d7cf0c51dd8..0c6cd1b196ac812 100644 --- a/Lib/test/test_venv.py +++ b/Lib/test/test_venv.py @@ -593,6 +593,51 @@ def test_unicode_in_batch_file(self): ) self.assertEqual(out.strip(), '0') + @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') + def test_activate_bat_respects_disable_prompt(self): + rmtree(self.env_dir) + env_dir = os.path.join(os.path.realpath(self.env_dir), 'venv') + builder = venv.EnvBuilder(clear=True) + builder.create(env_dir) + activate = os.path.join(env_dir, self.bindir, 'activate.bat') + test_batch = os.path.join(self.env_dir, 'test_disable_prompt.bat') + with open(test_batch, "w") as f: + f.write('@echo off\n' + 'set "PROMPT=base$G"\n' + 'set "VIRTUAL_ENV_DISABLE_PROMPT=1"\n' + f'call "{activate}"\n' + 'echo ACTIVE_PROMPT:%PROMPT%\n' + 'echo VIRTUAL_ENV:%VIRTUAL_ENV%\n' + 'set "PROMPT=changed$G"\n' + 'call deactivate\n' + 'echo FINAL_PROMPT:%PROMPT%\n') + out, err = check_output([test_batch]) + lines = out.splitlines() + self.assertEqual(lines[0], b'ACTIVE_PROMPT:base$G') + self.assertEndsWith(lines[1], os.fsencode(env_dir)) + self.assertEqual(lines[2], b'FINAL_PROMPT:changed$G') + + @unittest.skipUnless(os.name == 'nt', 'only relevant on Windows') + def test_activate_bat_prefixes_prompt_by_default(self): + rmtree(self.env_dir) + env_dir = os.path.join(os.path.realpath(self.env_dir), 'venv') + builder = venv.EnvBuilder(clear=True) + builder.create(env_dir) + activate = os.path.join(env_dir, self.bindir, 'activate.bat') + test_batch = os.path.join(self.env_dir, 'test_enable_prompt.bat') + with open(test_batch, "w") as f: + f.write('@echo off\n' + 'set "PROMPT=base) $G"\n' + 'set "VIRTUAL_ENV_DISABLE_PROMPT="\n' + f'call "{activate}"\n' + 'echo ACTIVE_PROMPT:%PROMPT%\n' + 'call deactivate\n' + 'echo FINAL_PROMPT:%PROMPT%\n') + out, err = check_output([test_batch]) + lines = out.splitlines() + self.assertEqual(lines[0], b'ACTIVE_PROMPT:(venv) base) $G') + self.assertEqual(lines[1], b'FINAL_PROMPT:base) $G') + @unittest.skipUnless(os.name == 'nt' and can_symlink(), 'symlinks on Windows') def test_failed_symlink(self): diff --git a/Lib/test/test_weakref.py b/Lib/test/test_weakref.py index b187643e84521cc..0c4717521f16f67 100644 --- a/Lib/test/test_weakref.py +++ b/Lib/test/test_weakref.py @@ -167,6 +167,11 @@ def test_basic_callback(self): self.check_basic_callback(create_function) self.check_basic_callback(create_bound_method) + def test_non_callable_callback(self): + c = C() + self.assertRaises(TypeError, weakref.ref, c, 42) + self.assertRaises(TypeError, weakref.proxy, c, 42) + @support.cpython_only def test_cfunction(self): _testcapi = import_helper.import_module("_testcapi") diff --git a/Lib/venv/scripts/nt/activate.bat b/Lib/venv/scripts/nt/activate.bat index 06f4753d73b7aed..4a3e791abb86bd2 100644 --- a/Lib/venv/scripts/nt/activate.bat +++ b/Lib/venv/scripts/nt/activate.bat @@ -13,8 +13,8 @@ @if defined _OLD_VIRTUAL_PROMPT @set PROMPT=%_OLD_VIRTUAL_PROMPT% @if defined _OLD_VIRTUAL_PYTHONHOME @set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME% -@set "_OLD_VIRTUAL_PROMPT=%PROMPT%" -@set "PROMPT=(__VENV_PROMPT__) %PROMPT%" +@if not defined VIRTUAL_ENV_DISABLE_PROMPT @set "_OLD_VIRTUAL_PROMPT=%PROMPT%" +@if not defined VIRTUAL_ENV_DISABLE_PROMPT @set "PROMPT=(__VENV_PROMPT__) %PROMPT%" @if defined PYTHONHOME @set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME% @set PYTHONHOME= diff --git a/Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst b/Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst new file mode 100644 index 000000000000000..c4f5e8f3f9bbc2d --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-06-09-14-50-35.gh-issue-80384.UEGyvB.rst @@ -0,0 +1,3 @@ +:c:func:`PyWeakref_NewRef` and :c:func:`PyWeakref_NewProxy` now raise +:exc:`TypeError` if the *callback* argument is not callable, ``None``, or +``NULL``. diff --git a/Misc/NEWS.d/next/Library/2026-06-08-15-04-35.gh-issue-150285.-Tj94n.rst b/Misc/NEWS.d/next/Library/2026-06-08-15-04-35.gh-issue-150285.-Tj94n.rst new file mode 100644 index 000000000000000..60b1f3e0a71f5bb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-08-15-04-35.gh-issue-150285.-Tj94n.rst @@ -0,0 +1 @@ +:mod:`pydoc` now wraps long single-line summary in text output. diff --git a/Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst b/Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst new file mode 100644 index 000000000000000..53d4d513ba757de --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-09-14-49-17.gh-issue-80384.ttRAja.rst @@ -0,0 +1,2 @@ +:func:`weakref.ref` and :func:`weakref.proxy` now raise :exc:`TypeError` if +the *callback* argument is not callable or ``None``. diff --git a/Misc/NEWS.d/next/Library/2026-06-10-00-00-02.gh-issue-109940.Cx1099.rst b/Misc/NEWS.d/next/Library/2026-06-10-00-00-02.gh-issue-109940.Cx1099.rst new file mode 100644 index 000000000000000..130dc780b612864 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-10-00-00-02.gh-issue-109940.Cx1099.rst @@ -0,0 +1,2 @@ +Fix Windows :mod:`venv` activation in ``cmd.exe`` to respect +``VIRTUAL_ENV_DISABLE_PROMPT``. diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index 8446a2dbcf75593..daeba5368b21660 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -416,8 +416,15 @@ get_or_create_weakref(PyTypeObject *type, PyObject *obj, PyObject *callback) Py_TYPE(obj)->tp_name); return NULL; } - if (callback == Py_None) + if (callback == Py_None) { callback = NULL; + } + if (callback != NULL && !PyCallable_Check(callback)) { + PyErr_Format(PyExc_TypeError, + "callback must be callable or None, not '%T'", + callback); + return NULL; + } PyWeakReference **list = GET_WEAKREFS_LISTPTR(obj); if ((type == &_PyWeakref_RefType) ||