diff --git a/Doc/library/constants.rst b/Doc/builtins/constants.rst
similarity index 100%
rename from Doc/library/constants.rst
rename to Doc/builtins/constants.rst
diff --git a/Doc/library/exceptions.rst b/Doc/builtins/exceptions.rst
similarity index 100%
rename from Doc/library/exceptions.rst
rename to Doc/builtins/exceptions.rst
diff --git a/Doc/library/functions.rst b/Doc/builtins/functions.rst
similarity index 100%
rename from Doc/library/functions.rst
rename to Doc/builtins/functions.rst
diff --git a/Doc/builtins/index.rst b/Doc/builtins/index.rst
new file mode 100644
index 000000000000000..17aab32200d976e
--- /dev/null
+++ b/Doc/builtins/index.rst
@@ -0,0 +1,35 @@
+.. _builtins-index:
+
+##############################
+ Python built-ins reference
+##############################
+
+Python comes with a number of built-in functions and classes.
+
+The built-in classes include data types that would normally be considered part
+of the "core" of a language, such as numbers and lists. For these types, the
+Python language core defines the form of literals and places some constraints
+on their semantics, but does not fully define the semantics.
+
+The built-ins also include functions and exceptions --- objects that can
+be used by all Python code without the need of an :keyword:`import` statement.
+Some of these are defined by the core language, but many are not essential for
+the core semantics and are only described here.
+
+.. seealso::
+
+ In addition to the built-ins, Python provides an extensive importable
+ standard library, see :ref:`library-index`.
+
+.. We don't use :numbered: option for the TOC below as it enforces
+ numbered sections for the entire builtin docs. If desired,
+ :numbered: can be enabled on a per-page basis.
+.. toctree::
+ :maxdepth: 2
+
+ stdtypes.rst
+ constants.rst
+ functions.rst
+ exceptions.rst
+ threadsafety.rst
+ time-complexity.rst
diff --git a/Doc/library/stdtypes.rst b/Doc/builtins/stdtypes.rst
similarity index 100%
rename from Doc/library/stdtypes.rst
rename to Doc/builtins/stdtypes.rst
diff --git a/Doc/library/threadsafety.rst b/Doc/builtins/threadsafety.rst
similarity index 100%
rename from Doc/library/threadsafety.rst
rename to Doc/builtins/threadsafety.rst
diff --git a/Doc/library/time-complexity.rst b/Doc/builtins/time-complexity.rst
similarity index 100%
rename from Doc/library/time-complexity.rst
rename to Doc/builtins/time-complexity.rst
diff --git a/Doc/conf.py b/Doc/conf.py
index c768e6fd676a5a3..f803fb1ff44bef9 100644
--- a/Doc/conf.py
+++ b/Doc/conf.py
@@ -44,6 +44,7 @@
'sphinx_linklint.ext',
'notfound.extension',
'sphinxext.opengraph',
+ 'sphinxext.rediraffe',
'sphinxcontrib.rsvgconverter',
)
for optional_ext in _OPTIONAL_EXTENSIONS:
@@ -359,7 +360,13 @@
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, document class [howto/manual]).
latex_documents = [
- ('c-api/index', 'c-api.tex', 'The Python/C API', _doc_authors, 'manual'),
+ (
+ 'c-api/index',
+ 'c-api.tex',
+ 'The Python/C API',
+ _doc_authors,
+ 'manual',
+ ),
(
'extending/index',
'extending.tex',
@@ -374,6 +381,13 @@
_doc_authors,
'manual',
),
+ (
+ 'builtins/index',
+ 'builtins.tex',
+ 'Python Built-ins Reference',
+ _doc_authors,
+ 'manual',
+ ),
(
'library/index',
'library.tex',
@@ -606,3 +620,16 @@
'',
'',
)
+
+# Options for sphinxext-rediraffe
+# -------------------------------
+
+rediraffe_redirects = {
+ # Splitting builtins from library
+ "library/functions.rst": "builtins/functions.rst",
+ "library/stdtypes.rst": "builtins/stdtypes.rst",
+ "library/constants.rst": "builtins/constants.rst",
+ "library/exceptions.rst": "builtins/exceptions.rst",
+ "library/threadsafety.rst": "builtins/threadsafety.rst",
+ "library/time-complexity.rst": "builtins/time-complexity.rst",
+}
diff --git a/Doc/contents.rst b/Doc/contents.rst
index b57f4b09a5dcb6a..852be4a6d5b6ba7 100644
--- a/Doc/contents.rst
+++ b/Doc/contents.rst
@@ -8,6 +8,7 @@
tutorial/index.rst
using/index.rst
reference/index.rst
+ builtins/index.rst
library/index.rst
extending/index.rst
c-api/index.rst
diff --git a/Doc/extending/index.rst b/Doc/extending/index.rst
index c0c494c3059d99c..0f0686ea40e75be 100644
--- a/Doc/extending/index.rst
+++ b/Doc/extending/index.rst
@@ -16,9 +16,10 @@ underlying operating system supports this feature.
This document assumes basic knowledge about C and Python. For an informal
introduction to Python, see :ref:`tutorial-index`. :ref:`reference-index`
-gives a more formal definition of the language. :ref:`library-index` documents
-the existing object types, functions and modules (both built-in and written in
-Python) that give the language its wide application range.
+gives a more formal definition of the language. :ref:`builtins-index` documents
+the built-in functions and object types, and :ref:`library-index` documents the
+modules (both built-in and written in Python) that give the language its wide
+application range.
For a detailed description of the whole Python/C API, see the separate
:ref:`c-api-index`.
diff --git a/Doc/library/index.rst b/Doc/library/index.rst
index f28c03e2fae092f..79437d533512b1a 100644
--- a/Doc/library/index.rst
+++ b/Doc/library/index.rst
@@ -1,16 +1,18 @@
.. _library-index:
###############################
- The Python Standard Library
+ The Python standard library
###############################
-While :ref:`reference-index` describes the exact syntax and
-semantics of the Python language, this library reference manual
-describes the standard library that is distributed with Python. It also
-describes some of the optional components that are commonly included
-in Python distributions.
+This library reference manual describes the standard library
+distributed with Python. It also describes some of the optional
+components that are commonly included in Python distributions.
-Python's standard library is very extensive, offering a wide range of
+Elsewhere, :ref:`reference-index` describes the exact syntax and
+semantics of the Python language, and :ref:`builtins-index` describes
+the built-in functions.
+
+Python's standard library is extensive, offering a wide range of
facilities as indicated by the long table of contents listed below. The
library contains built-in modules (written in C) that provide access to
system functionality such as file I/O that would otherwise be
@@ -39,12 +41,6 @@ the `Python Package Index `_.
:maxdepth: 2
intro.rst
- functions.rst
- constants.rst
- stdtypes.rst
- exceptions.rst
- threadsafety.rst
- time-complexity.rst
text.rst
binary.rst
diff --git a/Doc/library/intro.rst b/Doc/library/intro.rst
index 8f76044be488cda..fcd2175dbccc01b 100644
--- a/Doc/library/intro.rst
+++ b/Doc/library/intro.rst
@@ -4,48 +4,34 @@
Introduction
************
-The "Python library" contains several different kinds of components.
-
-It contains data types that would normally be considered part of the "core" of a
-language, such as numbers and lists. For these types, the Python language core
-defines the form of literals and places some constraints on their semantics, but
-does not fully define the semantics. (On the other hand, the language core does
-define syntactic properties like the spelling and priorities of operators.)
-
-The library also contains built-in functions and exceptions --- objects that can
-be used by all Python code without the need of an :keyword:`import` statement.
-Some of these are defined by the core language, but many are not essential for
-the core semantics and are only described here.
-
-The bulk of the library, however, consists of a collection of modules. There are
-many ways to dissect this collection. Some modules are written in C and built
-in to the Python interpreter; others are written in Python and imported in
-source form. Some modules provide interfaces that are highly specific to
+The Python standard library consists of a collection of modules. There are
+many ways to dissect this collection. Most modules are written in Python,
+but some are written in C. All can be imported into your program to add
+functionality. Some modules provide interfaces that are highly specific to
Python, like printing a stack trace; some provide interfaces that are specific
to particular operating systems, such as access to specific hardware; others
provide interfaces that are specific to a particular application domain, like
-the World Wide Web. Some modules are available in all versions and ports of
+web development. Some modules are available in all versions and ports of
Python; others are only available when the underlying system supports or
requires them; yet others are available only when a particular configuration
option was chosen at the time when Python was compiled and installed.
-This manual is organized "from the inside out:" it first describes the built-in
-functions, data types and exceptions, and finally the modules, grouped in
-chapters of related modules.
-
-This means that if you start reading this manual from the start, and skip to the
+If you start reading this manual from the start, and skip to the
next chapter when you get bored, you will get a reasonable overview of the
available modules and application areas that are supported by the Python
library. Of course, you don't *have* to read it like a novel --- you can also
browse the table of contents (in front of the manual), or look for a specific
function, module or term in the index (in the back). And finally, if you enjoy
-learning about random subjects, you choose a random page number (see module
-:mod:`random`) and read a section or two. Regardless of the order in which you
-read the sections of this manual, it helps to start with chapter
-:ref:`built-in-funcs`, as the remainder of the manual assumes familiarity with
-this material.
+learning about random subjects, you choose a random page
+and read a section or two. Regardless of the order in which you
+read the sections of this manual, it helps to first read
+:ref:`built-in-funcs`, as the remainder of this section
+assumes familiarity with this material.
+
+.. seealso::
-Let the show begin!
+ The built-in functions and classes (which can be used without an
+ :keyword:`import` statement) are described in :ref:`builtins-index`.
.. _availability:
diff --git a/Doc/library/xml.dom.minidom.rst b/Doc/library/xml.dom.minidom.rst
index efc81f31e36a5be..3e1f7a7e12a94e7 100644
--- a/Doc/library/xml.dom.minidom.rst
+++ b/Doc/library/xml.dom.minidom.rst
@@ -187,6 +187,12 @@ module documentation. This section lists the differences between the API and
The *standalone* argument behaves exactly as in :meth:`writexml`.
+ No indentation is added inside an element
+ which is marked with ``xml:space="preserve"``,
+ which is declared in the DTD as not having element content,
+ or, in absence of such declaration, which contains text,
+ because this would change its content.
+
.. versionchanged:: 3.8
The :meth:`toprettyxml` method now preserves the attribute order specified
by the user.
@@ -194,6 +200,10 @@ module documentation. This section lists the differences between the API and
.. versionchanged:: 3.9
The *standalone* parameter was added.
+ .. versionchanged:: next
+ Whitespace is no longer added inside an element with mixed content
+ or marked with ``xml:space="preserve"``.
+
.. _dom-example:
DOM Example
@@ -274,6 +284,10 @@ rules apply:
and produced an invalid document,
but removing an absent attribute raised :exc:`~xml.dom.NotFoundErr`.
+ .. versionchanged:: next
+ Namespaces are now validated in the factory methods and when setting
+ :attr:`~xml.dom.Node.prefix` of an attribute.
+
The following interfaces have no implementation in :mod:`!xml.dom.minidom`:
* :class:`DOMTimeStamp`
diff --git a/Doc/library/xml.dom.rst b/Doc/library/xml.dom.rst
index b916e5ad9bd782a..b1c9e27a55274b1 100644
--- a/Doc/library/xml.dom.rst
+++ b/Doc/library/xml.dom.rst
@@ -687,6 +687,10 @@ inherits properties from :class:`Node`.
:meth:`~Node.insertBefore` or :meth:`~Node.appendChild`.
Raise :exc:`InvalidCharacterErr` if the name is not a valid XML name.
+ Raise :exc:`NamespaceErr` if the qualified name is malformed,
+ if it has a prefix and the namespace URI is empty,
+ or if the prefix is ``'xml'``
+ and the namespace URI is not the XML namespace.
.. method:: Document.createTextNode(data)
@@ -740,6 +744,11 @@ inherits properties from :class:`Node`.
:class:`Element` object to use the newly created attribute instance.
Raise :exc:`InvalidCharacterErr` if the name is not a valid XML name.
+ Raise :exc:`NamespaceErr` if the qualified name is malformed,
+ if it has a prefix and the namespace URI is empty,
+ if the prefix is ``'xml'`` and the namespace URI is not the XML namespace,
+ or if the name or the prefix is ``'xmlns'``
+ and the namespace URI is not the XMLNS namespace, or vice versa.
.. method:: Document.getElementById(id)
@@ -905,6 +914,11 @@ of that class.
Note that a qname is the whole attribute name. This is different than above.
Raise :exc:`InvalidCharacterErr` if the name is not a valid XML name.
+ Raise :exc:`NamespaceErr` if the qualified name is malformed,
+ if it has a prefix and the namespace URI is empty,
+ if the prefix is ``'xml'`` and the namespace URI is not the XML namespace,
+ or if the name or the prefix is ``'xmlns'``
+ and the namespace URI is not the XMLNS namespace, or vice versa.
.. _dom-attr-objects:
diff --git a/Doc/library/xml.etree.elementtree.rst b/Doc/library/xml.etree.elementtree.rst
index 7948b2ed78f4d0d..a61fb05bf99d873 100644
--- a/Doc/library/xml.etree.elementtree.rst
+++ b/Doc/library/xml.etree.elementtree.rst
@@ -160,8 +160,37 @@ some storage device. In such cases, blocking reads are unacceptable.
Because it's so flexible, :class:`XMLPullParser` can be inconvenient to use for
simpler use-cases. If you don't mind your application blocking on reading XML
data but would still like to have incremental parsing capabilities, take a look
-at :func:`iterparse`. It can be useful when you're reading a large XML document
-and don't want to hold it wholly in memory.
+at :func:`iterparse`.
+
+Note that both parsers build the tree incrementally: it is not freed
+incrementally, so every parsed element is kept until the whole document is
+read. To keep the memory usage low, get rid of the data which is not needed
+any more.
+
+If the processed elements are large, it is enough to clear them.
+This works wherever they are in the tree,
+but the emptied elements are left in it::
+
+ for event, elem in ET.iterparse(source):
+ if elem.tag == 'record':
+ process(elem)
+ elem.clear()
+
+If an element has a large number of children,
+remove the processed children from it::
+
+ for event, elem in ET.iterparse(source, events=('start', 'end')):
+ if event == 'start' and elem.tag == 'parent':
+ parent = elem
+ elif event == 'end' and elem.tag == 'child':
+ process(elem)
+ parent.remove(elem)
+
+These examples are not universal,
+they only give an idea for two common cases.
+If you do not need a tree at all,
+parse with :class:`XMLParser` and a custom target instead;
+it is not built then, and nothing has to be removed.
Where *immediate* feedback through events is wanted, calling method
:meth:`XMLPullParser.flush` can help reduce delay;
@@ -602,8 +631,16 @@ Functions
characters by default. For indenting partial subtrees inside of an
already indented tree, pass the initial indentation level as *level*.
+ No whitespace is added inside an element
+ which is marked with ``xml:space="preserve"``
+ or which contains text, because this would change its content.
+
.. versionadded:: 3.9
+ .. versionchanged:: next
+ Whitespace is no longer added inside an element with mixed content
+ or marked with ``xml:space="preserve"``.
+
.. function:: iselement(element)
@@ -611,10 +648,11 @@ Functions
element instance. Return ``True`` if this is an element object.
-.. function:: iterparse(source, events=None, parser=None)
+.. function:: iterparse(source, events=None, parser=None, *, target=None)
- Parses an XML section into an element tree incrementally, and reports what's
- going on to the user. *source* is a filename or :term:`file object`
+ Parses an XML section incrementally, and reports what's going on to the
+ user. Unless a custom target is used, an element tree is built.
+ *source* is a filename or :term:`file object`
containing XML data. *events* is a sequence of events to report back. The
supported events are the strings ``"start"``, ``"end"``, ``"comment"``,
``"pi"``, ``"start-ns"`` and ``"end-ns"``
@@ -622,11 +660,18 @@ Functions
information). If *events* is omitted, only ``"end"`` events are reported.
*parser* is an optional parser instance.
If not given, the standard :class:`XMLParser` parser is used.
- *parser* must be an instance of :class:`XMLParser` or its subclass
- and can only use the default :class:`TreeBuilder` as a target.
- Returns an :term:`iterator` providing ``(event, elem)`` pairs;
+ *parser* must be an instance of :class:`XMLParser` or its subclass.
+ *target* is the target of the standard parser,
+ as for :class:`XMLPullParser`;
+ it cannot be used together with *parser*.
+ Returns an :term:`iterator` providing ``(event, obj)`` pairs,
+ as described for :meth:`XMLPullParser.read_events`;
it has a ``root`` attribute that references the root element of the
- resulting XML tree once *source* is fully read.
+ resulting XML tree, or the value returned by the ``close()`` method
+ of a custom target, once *source* is fully read.
+ If a custom target is used, it is set to the value returned
+ by the :meth:`!close` method of the target.
+
The iterator has the :meth:`!close` method that closes the internal
file object if *source* is a filename.
@@ -635,6 +680,10 @@ Functions
for applications where blocking reads can't be made. For fully non-blocking
parsing, see :class:`XMLPullParser`.
+ The tree is only built incrementally, it is not freed incrementally:
+ every parsed element is kept until the whole document is read.
+ See :ref:`elementtree-pull-parsing` for how to keep the memory usage low.
+
.. note::
:func:`iterparse` only guarantees that it has seen the ">" character of a
@@ -658,6 +707,9 @@ Functions
A :exc:`ResourceWarning` is now emitted if the iterator opened a file
and is not explicitly closed.
+ .. versionchanged:: next
+ Added the *target* parameter.
+
.. function:: parse(source, parser=None)
@@ -1491,7 +1543,7 @@ XMLParser Objects
XMLPullParser Objects
^^^^^^^^^^^^^^^^^^^^^
-.. class:: XMLPullParser(events=None)
+.. class:: XMLPullParser(events=None, *, target=None)
A pull parser suitable for non-blocking applications. Its input-side API is
similar to that of :class:`XMLParser`, but instead of pushing calls to a
@@ -1502,6 +1554,20 @@ XMLPullParser Objects
are used to get detailed namespace information). If *events* is omitted,
only ``"end"`` events are reported.
+ *target* is the target object of the underlying :class:`XMLParser`.
+ If omitted, the standard :class:`TreeBuilder` is used,
+ and the reported objects are :class:`Element` instances.
+ With other targets the reported object is the value returned
+ by the corresponding method of the target,
+ so no tree is built if the target does not build one.
+ The target must implement the methods for all requested events,
+ except :meth:`!start_ns` and :meth:`!end_ns`:
+ if they are not implemented, a ``(prefix, uri)`` tuple and ``None``
+ are reported for the ``"start-ns"`` and ``"end-ns"`` events.
+
+ .. versionchanged:: next
+ Added the *target* parameter.
+
.. method:: feed(data)
Feed the given data to the parser. *data* is a string
@@ -1534,9 +1600,10 @@ XMLPullParser Objects
Return an iterator over the events which have been encountered in the
data fed to the
- parser. The iterator yields ``(event, elem)`` pairs, where *event* is a
- string representing the type of event (e.g. ``"end"``) and *elem* is the
- encountered :class:`Element` object, or other context value as follows.
+ parser. The iterator yields ``(event, obj)`` pairs, where *event* is a
+ string representing the type of event (e.g. ``"end"``) and *obj* is the
+ object returned by the corresponding method of the target.
+ With the standard :class:`TreeBuilder` it is as follows.
* ``start``, ``end``: the current Element.
* ``comment``, ``pi``: the current comment / processing instruction
diff --git a/Doc/pylock.toml b/Doc/pylock.toml
index 94b7d9d48d646e5..3ad79b3cc6a8735 100644
--- a/Doc/pylock.toml
+++ b/Doc/pylock.toml
@@ -238,6 +238,12 @@ version = "0.13.0"
sdist = { url = "https://files.pythonhosted.org/packages/f6/c0/eb6838e3bae624ce6c8b90b245d17e84252863150e95efdb88f92c8aa3fb/sphinxext_opengraph-0.13.0.tar.gz", upload-time = 2025-08-29T12:20:31Z, size = 1026875, hashes = { sha256 = "103335d08567ad8468faf1425f575e3b698e9621f9323949a6c8b96d9793e80b" } }
wheels = [{ url = "https://files.pythonhosted.org/packages/bf/a4/66c1fd4f8fab88faf71cee04a945f9806ba0fef753f2cfc8be6353f64508/sphinxext_opengraph-0.13.0-py3-none-any.whl", upload-time = 2025-08-29T12:20:29Z, size = 1004152, hashes = { sha256 = "936c07828edc9ad9a7b07908b29596dc84ed0b3ceaa77acdf51282d232d4d80e" } }]
+[[packages]]
+name = "sphinxext-rediraffe"
+version = "0.3.0"
+sdist = { url = "https://files.pythonhosted.org/packages/e3/a9/ab13d156049eea633f992424f3e92cb40e3f1b606bb6d01d40a27457d38a/sphinxext_rediraffe-0.3.0.tar.gz", upload-time = 2025-09-28T15:31:53Z, size = 22114, hashes = { sha256 = "f319b3ccb7c3c3b6f63ffa6fd3eeb171b6d272df55075a9e84364394f391f507" } }
+wheels = [{ url = "https://files.pythonhosted.org/packages/87/55/ab40a0d1378ee5c859590a633052cf1d0a1f8435af87558a9f7cd576601a/sphinxext_rediraffe-0.3.0-py3-none-any.whl", upload-time = 2025-09-28T15:31:52Z, size = 7194, hashes = { sha256 = "f4220beafa99c99177488276b8e4fcf61fbeeec4253c1e4aae841a18c475330c" } }]
+
[[packages]]
name = "urllib3"
version = "2.7.0"
diff --git a/Doc/reference/index.rst b/Doc/reference/index.rst
index a66673b17246d7b..9a5b2e631204a55 100644
--- a/Doc/reference/index.rst
+++ b/Doc/reference/index.rst
@@ -4,10 +4,13 @@
The Python Language Reference
#################################
-This reference manual describes the syntax and "core semantics" of the
-language. It is terse, but attempts to be exact and complete. The semantics of
-non-essential built-in object types and of the built-in functions and modules
-are described in :ref:`library-index`. For an informal introduction to the
+This reference manual describes the syntax and core semantics of the
+language. It is terse, but attempts to be exact and complete.
+
+Elsewhere, the built-in object types and functions are described in
+:ref:`builtins-index`. Standard library modules are described in :ref:`library-index`.
+
+For an informal introduction to the
language, see :ref:`tutorial-index`. For C or C++ programmers, two additional
manuals exist: :ref:`extending-index` describes the high-level picture of how to
write a Python extension module, and the :ref:`c-api-index` describes the
diff --git a/Doc/requirements.txt b/Doc/requirements.txt
index b9072b4af542225..2fa952aa291f1fc 100644
--- a/Doc/requirements.txt
+++ b/Doc/requirements.txt
@@ -16,6 +16,7 @@ blurb
sphinx-linklint
sphinx-notfound-page~=1.0.0
sphinxext-opengraph~=0.13.0
+sphinxext-rediraffe
# The theme used by the documentation is stored separately, so we need
# to install that as well.
diff --git a/Doc/tools/check-html-ids.py b/Doc/tools/check-html-ids.py
index 3ea0a99d1dd4f68..12bda7666073d45 100644
--- a/Doc/tools/check-html-ids.py
+++ b/Doc/tools/check-html-ids.py
@@ -19,18 +19,33 @@
)
+class Redirect(Exception): # noqa: N818 Exception should be named with an Error suffix
+ def __init__(self, redirect_to):
+ self.redirect_to = redirect_to
+
+
class IDGatherer(html.parser.HTMLParser):
def __init__(self, ids):
super().__init__()
self.__ids = ids
def handle_starttag(self, tag, attrs):
+ if tag == "meta":
+ # Redirects are done with a meta tag:
+ #
+ dattr = dict(attrs)
+ if dattr.get("http-equiv") == "refresh":
+ content = dattr.get("content", "")
+ if content.startswith("0; url="):
+ redirect_to = content[7:]
+ raise Redirect(redirect_to)
for name, value in attrs:
if name == 'id':
if not IGNORED_ID_RE.fullmatch(value):
self.__ids.add(value)
+@functools.cache
def get_ids_from_file(path):
ids = set()
gatherer = IDGatherer(ids)
@@ -40,6 +55,18 @@ def get_ids_from_file(path):
return ids
+def get_ids_including_redirects(path):
+ # Only try 6 redirects, to avoid accidental endless loops
+ for _ in range(6):
+ try:
+ return get_ids_from_file(path)
+ except Redirect as r:
+ path = (path.parent / r.redirect_to).resolve()
+ continue
+ else:
+ raise RuntimeError("Apparent infinite redirects")
+
+
def gather_ids(htmldir, *, verbose_print):
if not htmldir.joinpath('objects.inv').exists():
raise ValueError(f'{htmldir!r} is not a Sphinx HTML output directory')
@@ -55,7 +82,9 @@ def gather_ids(htmldir, *, verbose_print):
continue
if 'whatsnew' in relative_path.parts:
continue
- tasks[relative_path] = pool.submit(get_ids_from_file, path=path)
+ tasks[relative_path] = pool.submit(
+ get_ids_including_redirects, path=path
+ )
ids_by_page = {}
for relative_path, future in tasks.items():
diff --git a/Doc/tools/templates/indexcontent.html b/Doc/tools/templates/indexcontent.html
index 4366da69d1b2d09..59a693c00003c45 100644
--- a/Doc/tools/templates/indexcontent.html
+++ b/Doc/tools/templates/indexcontent.html
@@ -56,16 +56,18 @@ {{ docstitle|e }}
{% trans whatsnew_index=pathto("whatsnew/index") %}Or all "What's new" documents since Python 2.0{% endtrans %}
{% trans %}Tutorial{% endtrans %}
{% trans %}Start here: a tour of Python's syntax and features{% endtrans %}
+ {% trans %}Built-ins reference{% endtrans %}
+ {% trans %}Built-in functions and classes{% endtrans %}
{% trans %}Library reference{% endtrans %}
- {% trans %}Standard library and builtins{% endtrans %}
+ {% trans %}Standard library modules{% endtrans %}
{% trans %}Language reference{% endtrans %}
{% trans %}Syntax and language elements{% endtrans %}
{% trans %}Python setup and usage{% endtrans %}
{% trans %}How to install, configure, and use Python{% endtrans %}
- {% trans %}Python HOWTOs{% endtrans %}
- {% trans %}In-depth topic manuals{% endtrans %}
+ - {% trans %}Python HOWTOs{% endtrans %}
+ {% trans %}In-depth topic manuals{% endtrans %}
- {% trans %}Installing Python modules{% endtrans %}
{% trans %}Third-party modules and PyPI.org{% endtrans %}
- {% trans %}Extending and embedding{% endtrans %}
diff --git a/Doc/tutorial/index.rst b/Doc/tutorial/index.rst
index 20fe161be4acc26..c3ae5eefdfb6693 100644
--- a/Doc/tutorial/index.rst
+++ b/Doc/tutorial/index.rst
@@ -30,9 +30,9 @@ have a basic understanding of programming in general. It helps to have a Python
interpreter handy for hands-on experience, but all examples are self-contained,
so the tutorial can be read off-line as well.
-For a description of standard objects and modules, see :ref:`library-index`.
-:ref:`reference-index` gives a more formal definition of the language. To write
-extensions in C or C++, read :ref:`extending-index` and
+For a description of standard objects and modules, see :ref:`builtins-index` and
+:ref:`library-index`. :ref:`reference-index` gives a more formal definition of
+the language. To write extensions in C or C++, read :ref:`extending-index` and
:ref:`c-api-index`. There are also several books covering Python in depth.
This tutorial does not attempt to be comprehensive and cover every single
diff --git a/Doc/tutorial/whatnow.rst b/Doc/tutorial/whatnow.rst
index aae8f29b0077627..6f4d1329682be3f 100644
--- a/Doc/tutorial/whatnow.rst
+++ b/Doc/tutorial/whatnow.rst
@@ -11,9 +11,10 @@ should you go to learn more?
This tutorial is part of Python's documentation set. Some other documents in
the set are:
-* :ref:`library-index`:
+* :ref:`builtins-index`: gives details about Python's built-in types and
+ functions.
- You should browse through this manual, which gives complete (though terse)
+* :ref:`library-index`: gives complete (though terse)
reference material about types, functions, and the modules in the standard
library. The standard Python distribution includes a *lot* of additional code.
There are modules to read Unix mailboxes, retrieve documents via HTTP, generate
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 1098b152e51eb41..aa875362c9b6281 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -702,12 +702,36 @@ xml
and :meth:`!Document.createEntityReference`.
(Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.)
+* :mod:`xml.dom.minidom` now validates namespaces in the factory methods
+ :meth:`~xml.dom.Document.createElementNS`,
+ :meth:`~xml.dom.Document.createAttributeNS`
+ and :meth:`~xml.dom.Element.setAttributeNS`.
+ :exc:`~xml.dom.NamespaceErr` is now raised for a malformed qualified name,
+ for a prefix with an empty namespace, and for illegal use
+ of the ``xml`` and ``xmlns`` prefixes.
+ (Contributed by Serhiy Storchaka in :gh:`156665`.)
+
+* :meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom`
+ and :func:`~xml.etree.ElementTree.indent` in :mod:`xml.etree.ElementTree`
+ no longer add whitespace inside an element
+ which is marked with ``xml:space="preserve"`` or which contains text.
+ :meth:`!toprettyxml` also takes into account
+ the content model declared in the DTD.
+ (Contributed by Serhiy Storchaka in :gh:`81623`.)
+
* Add :meth:`!GetSpecifiedAttributeCount` method
to the :mod:`XML parser ` objects.
It tells how many of the reported attributes were given in the start tag
rather than defaulted from the DTD.
(Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.)
+* :class:`~xml.etree.ElementTree.XMLPullParser` and
+ :func:`~xml.etree.ElementTree.iterparse` now support the *target* parameter.
+ The reported object is the value returned by the corresponding method of
+ the target, so a large document can be parsed incrementally without
+ building a tree for it.
+ (Contributed by Serhiy Storchaka in :gh:`63102`.)
+
zipfile
-------
@@ -940,6 +964,21 @@ that may require changes to your code.
Attributes defaulted in the DTD are no longer omitted when parsing.
(Contributed by Jason Orendorff and Serhiy Storchaka in :gh:`44871`.)
+* :mod:`xml.dom.minidom` now raises :exc:`~xml.dom.NamespaceErr`
+ for a malformed qualified name, for a prefix with an empty namespace,
+ and for illegal use of the ``xml`` and ``xmlns`` prefixes.
+ Such operations formerly succeeded and produced an invalid document.
+ (Contributed by Serhiy Storchaka in :gh:`156665`.)
+
+* :meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom`
+ and :func:`~xml.etree.ElementTree.indent` in :mod:`xml.etree.ElementTree`
+ no longer add whitespace inside an element
+ which is marked with ``xml:space="preserve"`` or which contains text,
+ because this changed the content of the element.
+ :meth:`!toprettyxml` also takes into account
+ the content model declared in the DTD.
+ (Contributed by Serhiy Storchaka in :gh:`81623`.)
+
* On Windows, seeking a pipe now fails instead of silently appearing to
succeed: :func:`os.lseek` and :meth:`~io.IOBase.seek` raise :exc:`OSError`,
and :meth:`~io.IOBase.seekable` returns ``False``. As a consequence,
diff --git a/InternalDocs/code_objects.md b/InternalDocs/code_objects.md
index 98fa22d66a923c2..9129fadbb0a567c 100644
--- a/InternalDocs/code_objects.md
+++ b/InternalDocs/code_objects.md
@@ -10,7 +10,7 @@ the source code location, which is useful for debuggers and other tools.
Since 3.11, the final field of the `PyCodeObject` C struct is an array
of indeterminate length containing the bytecode, `code->co_code_adaptive`.
(In older versions the code object was a
-[`bytes`](https://docs.python.org/dev/library/stdtypes.html#bytes)
+[`bytes`](https://docs.python.org/dev/builtins/stdtypes.html#bytes)
object, `code->co_code`; this was changed to save an allocation and to
allow it to be mutated.)
diff --git a/InternalDocs/parser.md b/InternalDocs/parser.md
index a6de8d456b6f71c..ff6426c4879f660 100644
--- a/InternalDocs/parser.md
+++ b/InternalDocs/parser.md
@@ -80,7 +80,7 @@ Key ideas
using memoization.
- If parsing fails completely (no rule succeeds in parsing all the input text), the
PEG parser doesn't have a concept of "where the
- [`SyntaxError`](https://docs.python.org/3/library/exceptions.html#SyntaxError) is".
+ [`SyntaxError`](https://docs.python.org/3/builtins/exceptions.html#SyntaxError) is".
> [!IMPORTANT]
@@ -654,7 +654,7 @@ is, and it will unwind the stack and report the exception. This means that if a
[rule action](#grammar-actions) raises an exception, all parsing will
stop at that exact point. This is done to allow to correctly propagate any
exception set by calling Python's C API functions. This also includes
-[`SyntaxError`](https://docs.python.org/3/library/exceptions.html#SyntaxError)
+[`SyntaxError`](https://docs.python.org/3/builtins/exceptions.html#SyntaxError)
exceptions and it is the main mechanism the parser uses to report custom syntax
error messages.
@@ -715,7 +715,7 @@ acts in two phases:
> When defining invalid rules:
>
> - Make sure all custom invalid rules raise
-> [`SyntaxError`](https://docs.python.org/3/library/exceptions.html#SyntaxError)
+> [`SyntaxError`](https://docs.python.org/3/builtins/exceptions.html#SyntaxError)
> exceptions (or a subclass of it).
> - Make sure **all** invalid rules start with the `invalid_` prefix to not
> impact performance of parsing correct Python code.
@@ -823,7 +823,7 @@ $ python -m pegen python
> Python's grammar (the `Grammar/python.gram` file) is written for the
> C backend. To experiment, you will need to write a grammar
> without C-specific parts like actions and the trailer.
-> See [#133560](https://github.com/python/cpython/issues/133560)
+> See [#133560](https://github.com/python/cpython/issues/133560)
> and [#96424](https://github.com/python/cpython/issues/96424) for more information.
This will generate a file called `parse.py` in the same directory that you
diff --git a/InternalDocs/structure.md b/InternalDocs/structure.md
index 75c8476aa0ad989..364773d68126903 100644
--- a/InternalDocs/structure.md
+++ b/InternalDocs/structure.md
@@ -23,13 +23,13 @@ For builtin types, the typical layout is:
* `Objects/object.c`
* `Lib/test/test_.py`
-* [`Doc/library/stdtypes.rst`](../Doc/library/stdtypes.rst)
+* [`Doc/builtins/stdtypes.rst`](../Doc/builtins/stdtypes.rst)
For builtin functions, the typical layout is:
* [`Python/bltinmodule.c`](../Python/bltinmodule.c)
* [`Lib/test/test_builtin.py`](../Lib/test/test_builtin.py)
-* [`Doc/library/functions.rst`](../Doc/library/functions.rst)
+* [`Doc/builtins/functions.rst`](../Doc/builtins/functions.rst)
Some exceptions to these layouts are:
diff --git a/Lib/nturl2path.py b/Lib/nturl2path.py
index 57c7858dff0b818..47c49ca2020c5c8 100644
--- a/Lib/nturl2path.py
+++ b/Lib/nturl2path.py
@@ -22,7 +22,10 @@ def url2pathname(url):
# ///C:/foo/bar/spam.foo
# become
# C:\foo\bar\spam.foo
+ import sys
import urllib.parse
+ encoding = sys.getfilesystemencoding()
+ errors = sys.getfilesystemencodeerrors()
if url[:3] == '///':
# URL has an empty authority section, so the path begins on the third
# character.
@@ -40,7 +43,8 @@ def url2pathname(url):
if url[1:2] == '|':
# Older URLs use a pipe after a drive letter
url = url[:1] + ':' + url[2:]
- return urllib.parse.unquote(url.replace('/', '\\'))
+ return urllib.parse.unquote(url.replace('/', '\\'),
+ encoding=encoding, errors=errors)
def pathname2url(p):
"""OS-specific conversion from a file system path to a relative URL
@@ -50,7 +54,10 @@ def pathname2url(p):
# becomes
# ///C:/foo/bar/spam.foo
import ntpath
+ import sys
import urllib.parse
+ encoding = sys.getfilesystemencoding()
+ errors = sys.getfilesystemencodeerrors()
# First, clean up some special forms. We are going to sacrifice
# the additional information anyway
p = p.replace('\\', '/')
@@ -65,10 +72,11 @@ def pathname2url(p):
# an authority section with a zero-length authority, and a path
# section starting with a single slash.
drive = f'///{drive}'
- drive = urllib.parse.quote(drive, safe='/:')
+ drive = urllib.parse.quote(drive, encoding=encoding, errors=errors,
+ safe='/:')
elif root:
# Add explicitly empty authority to path beginning with one slash.
root = f'//{root}'
- tail = urllib.parse.quote(tail)
+ tail = urllib.parse.quote(tail, encoding=encoding, errors=errors)
return drive + root + tail
diff --git a/Lib/statistics.py b/Lib/statistics.py
index 758b5b58848fb98..eee3e6eb94e990b 100644
--- a/Lib/statistics.py
+++ b/Lib/statistics.py
@@ -138,14 +138,15 @@
from decimal import Decimal
from itertools import compress, count, groupby, repeat
from bisect import bisect_left, bisect_right
-from math import hypot, sqrt, fabs, exp, erfc, tau, log, fsum, sumprod
-from math import isfinite, isinf, pi, cos, sin, tan, cosh, asin, atan, acos
+from math import hypot, sqrt, fabs, exp, erfc, log, fsum, sumprod
+from math import isfinite, isinf, pi, sin, cosh
+from math import sinpi, cospi, tanpi, asinpi, acospi, atanpi
from functools import reduce
from operator import itemgetter
from collections import Counter, namedtuple, defaultdict
_SQRT2 = sqrt(2.0)
-_SQRT2PI = sqrt(tau)
+_SQRT2PI = float.fromhex('0x1.40d931ff62706p+1') # Correctly rounded sqrt(2*pi)
_random = random
## Exceptions ##############################################################
@@ -820,8 +821,8 @@ def deco(builder):
@register('normal', 'gauss')
def normal_kernel():
- sqrt2pi = sqrt(2 * pi)
- neg_sqrt2 = -sqrt(2)
+ sqrt2pi = _SQRT2PI
+ neg_sqrt2 = -_SQRT2
pdf = lambda t: exp(-1/2 * t * t) / sqrt2pi
cdf = lambda t: 1/2 * erfc(t / neg_sqrt2)
invcdf = lambda t: _normal_dist_inv_cdf(t, 0.0, 1.0)
@@ -840,12 +841,10 @@ def logistic_kernel():
@register('sigmoid')
def sigmoid_kernel():
# (2/pi) / (exp(t) + exp(-t))
- c1 = 1 / pi
- c2 = 2 / pi
- c3 = pi / 2
- pdf = lambda t: c1 / cosh(t)
- cdf = lambda t: c2 * atan(exp(t))
- invcdf = lambda p: log(tan(p * c3))
+ recip_pi = 1 / pi
+ pdf = lambda t: recip_pi / cosh(t)
+ cdf = lambda t: 2.0 * atanpi(exp(t))
+ invcdf = lambda p: log(tanpi(p * 0.5))
support = None
return pdf, cdf, invcdf, support
@@ -869,7 +868,7 @@ def triangular_kernel():
def parabolic_kernel():
pdf = lambda t: 3/4 * (1.0 - t * t)
cdf = lambda t: sumprod((-1/4, 3/4, 1/2), (t**3, t, 1.0))
- invcdf = lambda p: 2.0 * cos((acos(2.0*p - 1.0) + pi) / 3.0)
+ invcdf = lambda p: 2.0 * cospi((acospi(2.0 * p - 1.0) + 1.0) / 3.0)
support = 1.0
return pdf, cdf, invcdf, support
@@ -906,7 +905,7 @@ def _triweight_invcdf_estimate(p):
sign, p = (1.0, p) if p <= 1/2 else (-1.0, 1.0 - p)
x = (2.0 * p) ** 0.3400218741872791 - 1.0
if 0.00001 < p < 0.499:
- x -= 0.033 * sin(1.07 * tau * (p - 0.035))
+ x -= 0.033 * sinpi(2.14 * (p - 0.035))
return x * sign
@register('triweight')
@@ -921,10 +920,9 @@ def triweight_kernel():
@register('cosine')
def cosine_kernel():
c1 = pi / 4
- c2 = pi / 2
- pdf = lambda t: c1 * cos(c2 * t)
- cdf = lambda t: 1/2 * sin(c2 * t) + 1/2
- invcdf = lambda p: 2.0 * asin(2.0 * p - 1.0) / pi
+ pdf = lambda t: c1 * cospi(0.5 * t)
+ cdf = lambda t: 1/2 * sinpi(0.5 * t) + 1/2
+ invcdf = lambda p: 2.0 * asinpi(2.0 * p - 1.0)
support = 1.0
return pdf, cdf, invcdf, support
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 210982fae236d5f..625898e6734aaa5 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -1373,7 +1373,11 @@ def internal(*args, **kwargs):
import_module('_testcapi')
return test(*args, **kwargs)
- return cpython_only(internal)
+ use_tsan = check_sanitizer(thread=True)
+ reason ='not working with thread sanitizer (gh-157415)'
+ skip_if_tsan = unittest.skipIf(use_tsan, reason)
+
+ return cpython_only(skip_if_tsan(internal))
def bigaddrspacetest(f):
"""Decorator for tests that fill the address space."""
diff --git a/Lib/test/test_capi/test_marshal.py b/Lib/test/test_capi/test_marshal.py
new file mode 100644
index 000000000000000..82a20c44fac4243
--- /dev/null
+++ b/Lib/test/test_capi/test_marshal.py
@@ -0,0 +1,127 @@
+import marshal
+import os.path
+import unittest
+
+from test import support
+from test.support import import_helper
+from test.support import os_helper
+from test.test_marshal import HelperMixin, omit_last_byte
+
+
+# Skip this test if _testcapi is are not available.
+_testcapi = import_helper.import_module('_testcapi')
+
+
+@support.cpython_only
+class CAPI_TestCase(unittest.TestCase, HelperMixin):
+
+ def test_read_from_file_error(self):
+ # A read error is reported as OSError, not EOFError.
+ # A directory cannot be read (on some platforms it cannot even
+ # be opened, which is reported as OSError as well).
+ os.mkdir(os_helper.TESTFN)
+ self.addCleanup(os_helper.rmdir, os_helper.TESTFN)
+ for func in (_testcapi.pymarshal_read_short_from_file,
+ _testcapi.pymarshal_read_long_from_file,
+ _testcapi.pymarshal_read_object_from_file,
+ _testcapi.pymarshal_read_last_object_from_file):
+ with self.subTest(func=func.__name__):
+ self.assertRaises(OSError, func, os_helper.TESTFN)
+
+ @unittest.skipUnless(os.path.exists('/dev/full'), 'requires /dev/full')
+ def test_write_to_file_error(self):
+ # A write error is reported as OSError.
+ # The data is large enough to not fit in the stdio buffer, so that
+ # the error is detected before the file is closed.
+ obj = b'x' * 100000
+ with self.assertRaises(OSError):
+ _testcapi.pymarshal_write_object_to_file(obj, '/dev/full',
+ marshal.version)
+
+ def test_write_unmarshallable_to_file(self):
+ self.addCleanup(os_helper.unlink, os_helper.TESTFN)
+ with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
+ _testcapi.pymarshal_write_object_to_file(object(), os_helper.TESTFN,
+ marshal.version)
+
+ def test_write_long_to_file(self):
+ for v in range(marshal.version + 1):
+ _testcapi.pymarshal_write_long_to_file(0x12345678, os_helper.TESTFN, v)
+ with open(os_helper.TESTFN, 'rb') as f:
+ data = f.read()
+ os_helper.unlink(os_helper.TESTFN)
+ self.assertEqual(data, b'\x78\x56\x34\x12')
+
+ def test_write_object_to_file(self):
+ obj = ('\u20ac', b'abc', 123, 45.6, 7+8j, 'long line '*1000)
+ for v in range(marshal.version + 1):
+ _testcapi.pymarshal_write_object_to_file(obj, os_helper.TESTFN, v)
+ with open(os_helper.TESTFN, 'rb') as f:
+ data = f.read()
+ os_helper.unlink(os_helper.TESTFN)
+ self.assertEqual(marshal.loads(data), obj)
+
+ def test_read_short_from_file(self):
+ with open(os_helper.TESTFN, 'wb') as f:
+ f.write(b'\x34\x12xxxx')
+ r, p = _testcapi.pymarshal_read_short_from_file(os_helper.TESTFN)
+ os_helper.unlink(os_helper.TESTFN)
+ self.assertEqual(r, 0x1234)
+ self.assertEqual(p, 2)
+
+ with open(os_helper.TESTFN, 'wb') as f:
+ f.write(b'\x12')
+ with self.assertRaises(EOFError):
+ _testcapi.pymarshal_read_short_from_file(os_helper.TESTFN)
+ os_helper.unlink(os_helper.TESTFN)
+
+ def test_read_long_from_file(self):
+ with open(os_helper.TESTFN, 'wb') as f:
+ f.write(b'\x78\x56\x34\x12xxxx')
+ r, p = _testcapi.pymarshal_read_long_from_file(os_helper.TESTFN)
+ os_helper.unlink(os_helper.TESTFN)
+ self.assertEqual(r, 0x12345678)
+ self.assertEqual(p, 4)
+
+ with open(os_helper.TESTFN, 'wb') as f:
+ f.write(b'\x56\x34\x12')
+ with self.assertRaises(EOFError):
+ _testcapi.pymarshal_read_long_from_file(os_helper.TESTFN)
+ os_helper.unlink(os_helper.TESTFN)
+
+ def test_read_last_object_from_file(self):
+ obj = ('\u20ac', b'abc', 123, 45.6, 7+8j)
+ for v in range(marshal.version + 1):
+ data = marshal.dumps(obj, v)
+ with open(os_helper.TESTFN, 'wb') as f:
+ f.write(data + b'xxxx')
+ r, p = _testcapi.pymarshal_read_last_object_from_file(os_helper.TESTFN)
+ os_helper.unlink(os_helper.TESTFN)
+ self.assertEqual(r, obj)
+
+ with open(os_helper.TESTFN, 'wb') as f:
+ f.write(omit_last_byte(data))
+ with self.assertRaises(EOFError):
+ _testcapi.pymarshal_read_last_object_from_file(os_helper.TESTFN)
+ os_helper.unlink(os_helper.TESTFN)
+
+ def test_read_object_from_file(self):
+ obj = ('\u20ac', b'abc', 123, 45.6, 7+8j)
+ for v in range(marshal.version + 1):
+ data = marshal.dumps(obj, v)
+ with open(os_helper.TESTFN, 'wb') as f:
+ f.write(data + b'xxxx')
+ r, p = _testcapi.pymarshal_read_object_from_file(os_helper.TESTFN)
+ os_helper.unlink(os_helper.TESTFN)
+ self.assertEqual(r, obj)
+ self.assertEqual(p, len(data))
+
+ with open(os_helper.TESTFN, 'wb') as f:
+ f.write(omit_last_byte(data))
+ with self.assertRaises(EOFError):
+ _testcapi.pymarshal_read_object_from_file(os_helper.TESTFN)
+ os_helper.unlink(os_helper.TESTFN)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index 9145ab1ee26e6f8..20da30f445d28c7 100644
--- a/Lib/test/test_clinic.py
+++ b/Lib/test/test_clinic.py
@@ -15,6 +15,7 @@
import re
import sys
import unittest
+import warnings
test_tools.skip_if_missing('clinic')
with test_tools.imports_under_tool('clinic'):
@@ -2305,6 +2306,128 @@ def test_depr_slash_duplicate2(self):
err = "Function 'bar': '/ [from 3.14]' must precede '/ [from 3.15]'"
self.expect_failure(block, err, lineno=5)
+ def test_alias(self):
+ function = self.parse_function("""
+ module foo
+ foo.bar
+ a: int
+ *
+ b as a: int = 0
+ Docstring.
+ """)
+ _, a, b = function.parameters.values()
+ self.assertIsNone(a.converter.alias_of)
+ self.assertIs(b.converter.alias_of, a)
+ self.assertEqual(function.docstring.splitlines()[0],
+ "bar($module, /, a)")
+
+ def test_alias_must_be_keyword_only(self):
+ block = """
+ module foo
+ foo.bar
+ a: int
+ b as a: int = 0
+ Docstring.
+ """
+ err = "Alias 'b' of the parameter 'a' must be keyword-only."
+ self.expect_failure(block, err, lineno=3)
+
+ def test_alias_must_have_default(self):
+ block = """
+ module foo
+ foo.bar
+ a: int
+ *
+ b as a: int
+ Docstring.
+ """
+ err = "Alias 'b' of the parameter 'a' must have a default value."
+ self.expect_failure(block, err, lineno=4)
+
+ def test_alias_deprecated(self):
+ function = self.parse_function("""
+ module foo
+ foo.bar
+ a: int
+ *
+ [until 3.14] b as a: int = 0
+ Docstring.
+ """)
+ _, a, b = function.parameters.values()
+ self.assertIsNone(a.deprecated_until)
+ self.assertEqual(b.deprecated_until, (3, 14))
+
+ def test_deprecated_last_positional_only_parameters(self):
+ function = self.parse_function("""
+ module foo
+ foo.bar
+ a: int = 0
+ [until 3.14] b: int = 0
+ [until 3.14] c: int = 0
+ /
+ d: int = 0
+ Docstring.
+ """)
+ _, a, b, c, d = function.parameters.values()
+ self.assertIsNone(a.deprecated_until)
+ self.assertEqual(b.deprecated_until, (3, 14))
+ self.assertEqual(c.deprecated_until, (3, 14))
+ self.assertIsNone(d.deprecated_until)
+
+ def test_deprecated_non_last_positional_only_parameter(self):
+ block = """
+ module foo
+ foo.bar
+ [until 3.14] a: int = 0
+ b: int = 0
+ /
+ Docstring.
+ """
+ err = ("Parameter 'b' cannot follow the deprecated parameter 'a': "
+ "only the last positional-only parameters can be deprecated.")
+ self.expect_failure(block, err, lineno=4)
+
+ def test_deprecated_non_positional_only_parameters(self):
+ # The following parameters can still be passed by keyword.
+ function = self.parse_function("""
+ module foo
+ foo.bar
+ [until 3.14] a: int = 0
+ b: int = 0
+ *
+ [until 3.14] c: int = 0
+ d: int = 0
+ Docstring.
+ """)
+ _, a, b, c, d = function.parameters.values()
+ self.assertEqual(a.deprecated_until, (3, 14))
+ self.assertIsNone(b.deprecated_until)
+ self.assertEqual(c.deprecated_until, (3, 14))
+ self.assertIsNone(d.deprecated_until)
+
+ def test_deprecated_parameter_without_default(self):
+ block = """
+ module foo
+ foo.bar
+ [until 3.14] a: int
+ Docstring.
+ """
+ err = "Deprecated parameter 'a' must have a default value."
+ self.expect_failure(block, err, lineno=2)
+
+ def test_deprecated_invalid_format(self):
+ block = """
+ module foo
+ foo.bar
+ [until 3] a: int = 0
+ Docstring.
+ """
+ err = (
+ "Function 'bar': expected format '[until major.minor]' "
+ "where 'major' and 'minor' are integers; got '3'"
+ )
+ self.expect_failure(block, err, lineno=2)
+
def test_single_slash(self):
block = """
module foo
@@ -5072,6 +5195,58 @@ def test_depr_multi(self):
check("a", b="b", c="c", d="d", e="e", f="f", g="g")
self.assertRaises(TypeError, fn, a="a", b="b", c="c", d="d", e="e", f="f", g="g")
+ def test_alias_pos(self):
+ fn = ac_tester.alias_pos
+ self.assertIsNone(fn())
+ self.assertEqual(fn(1), 1)
+ self.assertEqual(fn(a=1), 1)
+ self.assertEqual(fn(b=1), 1)
+ self.assertEqual(fn.__text_signature__, "($module, /, a=None)")
+ errmsg = re.escape(
+ "argument for alias_pos() given by name ('b') and position (1)")
+ self.assertRaisesRegex(TypeError, errmsg, fn, 1, b=2)
+ errmsg = re.escape(
+ "argument for alias_pos() given by name ('b') and name ('a')")
+ self.assertRaisesRegex(TypeError, errmsg, fn, a=1, b=2)
+
+ def test_alias_kwonly(self):
+ fn = ac_tester.alias_kwonly
+ self.assertIsNone(fn())
+ self.assertEqual(fn(a=1), 1)
+ self.assertEqual(fn(b=1), 1)
+ self.assertEqual(fn.__text_signature__, "($module, /, *, a=None)")
+ self.assertRaises(TypeError, fn, 1)
+ errmsg = re.escape(
+ "argument for alias_kwonly() given by name ('b') and name ('a')")
+ self.assertRaisesRegex(TypeError, errmsg, fn, a=1, b=2)
+
+ def test_depr_alias(self):
+ fn = ac_tester.depr_alias
+ self.assertEqual(fn(1), 1)
+ self.assertEqual(fn(a=1), 1)
+ errmsg = ("Passing the argument 'b' to depr_alias() is deprecated. "
+ "Use 'a' instead. It will be removed in Python 3.14.")
+ self.check_depr(re.escape(errmsg), fn, b=1)
+
+ def test_depr_param(self):
+ fn = ac_tester.depr_param
+ self.assertEqual(fn(), (None, None, None, None))
+ self.assertEqual(fn(1), (1, None, None, None))
+ def errmsg(name):
+ return re.escape(f"Passing the argument {name!r} to depr_param() "
+ f"is deprecated. "
+ f"It will be removed in Python 3.14.")
+ self.check_depr(errmsg('b'), fn, 1, 2)
+ self.check_depr(errmsg('d'), fn, 1, d=4)
+ # Each deprecated parameter is reported on its own.
+ with warnings.catch_warnings(record=True) as caught:
+ warnings.simplefilter("always")
+ self.assertEqual(fn(1, 2, 3), (1, 2, 3, None))
+ self.assertEqual(len(caught), 2)
+ for warning, name in zip(caught, 'bc'):
+ self.assertIs(warning.category, DeprecationWarning)
+ self.assertRegex(str(warning.message), errmsg(name))
+
def test_lone_kwds(self):
with self.assertRaises(TypeError):
ac_tester.lone_kwds(1, 2)
@@ -5266,6 +5441,26 @@ def test_limited_capi_double(self):
self.assertIn("double f;", generated)
self.assertIn("f = PyFloat_AsDouble", generated)
+ def test_limited_capi_alias(self):
+ block = self.wrap_clinic_input("""
+ func
+ a: object = None
+ *
+ b as a: object = None
+ """)
+ err = ("Parameter 'b' cannot be an alias: "
+ "the arguments are not parsed one by one.")
+ _expect_failure(self, self.clinic.parse, block, err)
+
+ def test_limited_capi_deprecated(self):
+ block = self.wrap_clinic_input("""
+ func
+ [until 3.14] a: object = None
+ """)
+ err = ("Parameter 'a' cannot be deprecated: "
+ "the arguments are not parsed one by one.")
+ _expect_failure(self, self.clinic.parse, block, err)
+
try:
import _testclinic_limited
diff --git a/Lib/test/test_io/test_memoryio.py b/Lib/test/test_io/test_memoryio.py
index 423b99779bc6e78..59e0dc4435d1f3b 100644
--- a/Lib/test/test_io/test_memoryio.py
+++ b/Lib/test/test_io/test_memoryio.py
@@ -754,6 +754,7 @@ def __buffer__(self, flags):
self.assertEqual(memio.getvalue(), b"01AAA56789")
self.assertEqual(memio.tell(), 5)
+ @support.nomemtest
def test_memory_error(self):
# gh-157242: io.BytesIO() must not close the file on MemoryError
_testcapi = import_helper.import_module('_testcapi')
diff --git a/Lib/test/test_marshal.py b/Lib/test/test_marshal.py
index c595e8cf14f1e15..d7db3d480ff1e2e 100644
--- a/Lib/test/test_marshal.py
+++ b/Lib/test/test_marshal.py
@@ -116,6 +116,14 @@ def test_bytes(self):
for s in [b"", b"Andr\xe8 Previn", b"abc", b" "*10000]:
self.helper(s)
+ @support.cpython_only
+ def test_bytes_singleton(self):
+ for version in range(marshal.version + 1):
+ for sample in [b"", b"x"]:
+ new = marshal.loads(marshal.dumps(sample, version))
+ self.assertIs(new, sample)
+
+
class ExceptionTestCase(unittest.TestCase):
def test_exceptions(self):
new = marshal.loads(marshal.dumps(StopIteration))
@@ -789,117 +797,6 @@ def test_slice(self):
with self.assertRaises(ValueError):
marshal.dumps(obj, version)
-@support.cpython_only
-@unittest.skipUnless(_testcapi, 'requires _testcapi')
-class CAPI_TestCase(unittest.TestCase, HelperMixin):
-
- def test_read_from_file_error(self):
- # A read error is reported as OSError, not EOFError.
- # A directory cannot be read (on some platforms it cannot even
- # be opened, which is reported as OSError as well).
- os.mkdir(os_helper.TESTFN)
- self.addCleanup(os_helper.rmdir, os_helper.TESTFN)
- for func in (_testcapi.pymarshal_read_short_from_file,
- _testcapi.pymarshal_read_long_from_file,
- _testcapi.pymarshal_read_object_from_file,
- _testcapi.pymarshal_read_last_object_from_file):
- with self.subTest(func=func.__name__):
- self.assertRaises(OSError, func, os_helper.TESTFN)
-
- @unittest.skipUnless(os.path.exists('/dev/full'), 'requires /dev/full')
- def test_write_to_file_error(self):
- # A write error is reported as OSError.
- # The data is large enough to not fit in the stdio buffer, so that
- # the error is detected before the file is closed.
- obj = b'x' * 100000
- with self.assertRaises(OSError):
- _testcapi.pymarshal_write_object_to_file(obj, '/dev/full',
- marshal.version)
-
- def test_write_unmarshallable_to_file(self):
- self.addCleanup(os_helper.unlink, os_helper.TESTFN)
- with self.assertRaisesRegex(ValueError, 'unmarshallable object'):
- _testcapi.pymarshal_write_object_to_file(object(), os_helper.TESTFN,
- marshal.version)
-
- def test_write_long_to_file(self):
- for v in range(marshal.version + 1):
- _testcapi.pymarshal_write_long_to_file(0x12345678, os_helper.TESTFN, v)
- with open(os_helper.TESTFN, 'rb') as f:
- data = f.read()
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(data, b'\x78\x56\x34\x12')
-
- def test_write_object_to_file(self):
- obj = ('\u20ac', b'abc', 123, 45.6, 7+8j, 'long line '*1000)
- for v in range(marshal.version + 1):
- _testcapi.pymarshal_write_object_to_file(obj, os_helper.TESTFN, v)
- with open(os_helper.TESTFN, 'rb') as f:
- data = f.read()
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(marshal.loads(data), obj)
-
- def test_read_short_from_file(self):
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(b'\x34\x12xxxx')
- r, p = _testcapi.pymarshal_read_short_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(r, 0x1234)
- self.assertEqual(p, 2)
-
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(b'\x12')
- with self.assertRaises(EOFError):
- _testcapi.pymarshal_read_short_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
-
- def test_read_long_from_file(self):
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(b'\x78\x56\x34\x12xxxx')
- r, p = _testcapi.pymarshal_read_long_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(r, 0x12345678)
- self.assertEqual(p, 4)
-
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(b'\x56\x34\x12')
- with self.assertRaises(EOFError):
- _testcapi.pymarshal_read_long_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
-
- def test_read_last_object_from_file(self):
- obj = ('\u20ac', b'abc', 123, 45.6, 7+8j)
- for v in range(marshal.version + 1):
- data = marshal.dumps(obj, v)
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(data + b'xxxx')
- r, p = _testcapi.pymarshal_read_last_object_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(r, obj)
-
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(omit_last_byte(data))
- with self.assertRaises(EOFError):
- _testcapi.pymarshal_read_last_object_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
-
- def test_read_object_from_file(self):
- obj = ('\u20ac', b'abc', 123, 45.6, 7+8j)
- for v in range(marshal.version + 1):
- data = marshal.dumps(obj, v)
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(data + b'xxxx')
- r, p = _testcapi.pymarshal_read_object_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
- self.assertEqual(r, obj)
- self.assertEqual(p, len(data))
-
- with open(os_helper.TESTFN, 'wb') as f:
- f.write(omit_last_byte(data))
- with self.assertRaises(EOFError):
- _testcapi.pymarshal_read_object_from_file(os_helper.TESTFN)
- os_helper.unlink(os_helper.TESTFN)
-
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/test/test_minidom.py b/Lib/test/test_minidom.py
index e204bdc7dc672db..3cc99e36e8898f3 100644
--- a/Lib/test/test_minidom.py
+++ b/Lib/test/test_minidom.py
@@ -365,7 +365,7 @@ def testRemoveAttrNS(self):
dom = Document()
child = dom.appendChild(
dom.createElementNS("http://www.python.org", "python:abc"))
- child.setAttributeNS("http://www.w3.org", "xmlns:python",
+ child.setAttributeNS(xml.dom.XMLNS_NAMESPACE, "xmlns:python",
"http://www.python.org")
child.setAttributeNS("http://www.python.org", "python:abcattr", "foo")
# removing an absent attribute has no effect
@@ -472,17 +472,27 @@ def testGetAttributeNS(self):
dom = Document()
child = dom.appendChild(
dom.createElementNS("http://www.python.org", "python:abc"))
- child.setAttributeNS("http://www.w3.org", "xmlns:python",
+ child.setAttributeNS(xml.dom.XMLNS_NAMESPACE, "xmlns:python",
"http://www.python.org")
- self.assertEqual(child.getAttributeNS("http://www.w3.org", "python"),
+ self.assertEqual(
+ child.getAttributeNS(xml.dom.XMLNS_NAMESPACE, "python"),
'http://www.python.org')
- self.assertEqual(child.getAttributeNS("http://www.w3.org", "other"),
+ self.assertEqual(
+ child.getAttributeNS(xml.dom.XMLNS_NAMESPACE, "other"),
'')
child2 = child.appendChild(dom.createElement('abc'))
self.assertEqual(child2.getAttributeNS("http://www.python.org", "missing"),
'')
- def testGetAttributeNode(self): pass
+ def testGetAttributeNode(self):
+ dom = parseString("")
+ elem = dom.documentElement
+ attr = elem.getAttributeNode("a")
+ self.assertEqual(attr.name, "a")
+ self.assertEqual(attr.value, "1")
+ self.assertIs(attr.ownerElement, elem)
+ self.assertIsNone(elem.getAttributeNode("b"))
+ dom.unlink()
def testGetElementsByTagNameNS(self):
d="""
@@ -609,32 +619,76 @@ def testAltNewline(self):
self.assertEqual(domstr, str.replace("\n", "\r\n"))
def test_toprettyxml_with_text_nodes(self):
- # see issue #4147, text nodes are not indented
+ # see gh-48397 and gh-81623,
+ # the content of an element with text is not changed
decl = '\n'
self.assertEqual(parseString('A').toprettyxml(),
decl + 'A\n')
self.assertEqual(parseString('AA').toprettyxml(),
- decl + '\n\tA\n\tA\n\n')
+ decl + 'AA\n')
self.assertEqual(parseString('AA').toprettyxml(),
- decl + '\n\tA\n\tA\n\n')
+ decl + 'AA\n')
self.assertEqual(parseString('AA').toprettyxml(),
decl + '\n\tA\n\tA\n\n')
self.assertEqual(parseString('AAA').toprettyxml(),
- decl + '\n\tA\n\tA\n\tA\n\n')
+ decl + 'AAA\n')
+ # toprettyxml treats whitespace between elements as insignificant
+ self.assertEqual(parseString(' A ').toprettyxml(),
+ decl + '\n\t \n\tA\n\t \n\n')
def test_toprettyxml_with_adjacent_text_nodes(self):
- # see issue #4147, adjacent text nodes are indented normally
+ # see gh-81623, adjacent text nodes are not separated
dom = Document()
elem = dom.createElement('elem')
elem.appendChild(dom.createTextNode('TEXT'))
elem.appendChild(dom.createTextNode('TEXT'))
dom.appendChild(elem)
decl = '\n'
- self.assertEqual(dom.toprettyxml(),
- decl + '\n\tTEXT\n\tTEXT\n\n')
+ self.assertEqual(dom.toprettyxml(), decl + 'TEXTTEXT\n')
+
+ def test_toprettyxml_preserve(self):
+ decl = '\n'
+ # xml:space="preserve" applies to the whole subtree
+ self.assertEqual(
+ parseString('AA'
+ ).toprettyxml(),
+ decl + 'AA\n')
+ self.assertEqual(
+ parseString(''
+ ).toprettyxml(),
+ decl + '\n')
+ # other values do not preserve whitespace
+ self.assertEqual(
+ parseString('A').toprettyxml(),
+ decl + '\n\tA\n\n')
+
+ def test_toprettyxml_with_non_xml_whitespace(self):
+ # only " \t\r\n" are whitespace in XML (see XML 1.0, 2.3)
+ decl = '\n'
+ self.assertEqual(parseString('\xa0A').toprettyxml(),
+ decl + '\xa0A\n')
+
+ def test_toprettyxml_with_dtd(self):
+ decl = '\n'
+ # only whitespace in element content is ignorable
+ doctype = (''
+ ']>')
+ self.assertEqual(
+ parseString(doctype + 'AA').toprettyxml(),
+ decl + doctype + '\nAA\n')
+ doctype = ']>'
+ self.assertEqual(
+ parseString(doctype + 'AA').toprettyxml(),
+ decl + doctype + '\n\n\tA\n\tA\n\n')
+
+ def test_toprettyxml_with_cdata_section(self):
+ decl = '\n'
+ self.assertEqual(
+ parseString('A').toprettyxml(),
+ decl + 'A\n')
def test_toprettyxml_preserves_content_of_text_node(self):
- # see issue #4147
+ # see gh-48397
for str in ('A', 'C'):
dom = parseString(str)
dom2 = parseString(dom.toprettyxml())
@@ -695,9 +749,34 @@ def testTextRepr(self):
self.assertEqual(str(el), repr(el))
self.assertEqual('', str(el))
- def testWriteText(self): pass
+ def testWriteText(self):
+ dom = parseString("text<&>")
+ elem = dom.documentElement
+ writer = io.StringIO()
+ elem.writexml(writer)
+ self.assertEqual(writer.getvalue(),
+ "text<&>")
+ writer = io.StringIO()
+ elem.writexml(writer, indent=" ", addindent=" ", newl="\n")
+ self.assertEqual(writer.getvalue(),
+ " \n"
+ " text\n"
+ " <&>\n"
+ " \n")
+ dom.unlink()
+
+ def testDocumentElement(self):
+ dom = parseString("")
+ elem = dom.documentElement
+ self.assertEqual(elem.tagName, "doc")
+ self.assertIs(elem, dom.childNodes[1])
+ dom.unlink()
- def testDocumentElement(self): pass
+ dom = Document()
+ self.assertIsNone(dom.documentElement)
+ elem = dom.appendChild(dom.createElement("doc"))
+ self.assertIs(dom.documentElement, elem)
+ dom.unlink()
def testTooManyDocumentElements(self):
doc = parseString("")
@@ -707,25 +786,126 @@ def testTooManyDocumentElements(self):
elem.unlink()
doc.unlink()
- def testCreateElementNS(self): pass
+ def testCreateElementNS(self):
+ dom = Document()
+ elem = dom.createElementNS("http://xml.python.org/ns", "p:elem")
+ self.assertEqual(elem.nodeType, Node.ELEMENT_NODE)
+ self.assertEqual(elem.tagName, "p:elem")
+ self.assertEqual(elem.nodeName, "p:elem")
+ self.assertEqual(elem.namespaceURI, "http://xml.python.org/ns")
+ self.assertEqual(elem.prefix, "p")
+ self.assertEqual(elem.localName, "elem")
+ self.assertIs(elem.ownerDocument, dom)
+ self.assertIsNone(elem.parentNode)
+
+ elem = dom.createElementNS("http://xml.python.org/ns", "elem")
+ self.assertEqual(elem.tagName, "elem")
+ self.assertIsNone(elem.prefix)
+ self.assertEqual(elem.localName, "elem")
+ dom.unlink()
+
+ def testCreateAttributeNS(self):
+ dom = Document()
+ attr = dom.createAttributeNS("http://xml.python.org/ns", "p:attr")
+ self.assertEqual(attr.nodeType, Node.ATTRIBUTE_NODE)
+ self.assertEqual(attr.name, "p:attr")
+ self.assertEqual(attr.nodeName, "p:attr")
+ self.assertEqual(attr.namespaceURI, "http://xml.python.org/ns")
+ self.assertEqual(attr.prefix, "p")
+ self.assertEqual(attr.localName, "attr")
+ self.assertEqual(attr.value, "")
+ self.assertIs(attr.ownerDocument, dom)
+ self.assertIsNone(attr.ownerElement)
+
+ elem = dom.appendChild(dom.createElement("doc"))
+ elem.setAttributeNode(attr)
+ self.assertIs(attr.ownerElement, elem)
+ self.assertIs(elem.getAttributeNodeNS("http://xml.python.org/ns",
+ "attr"), attr)
+ dom.unlink()
+
+ def testParse(self):
+ # parsing from a file object is tested in testParseFromBinaryFile
+ # and testParseFromTextFile
+ dom = parse(tstfile)
+ self.assertEqual(dom.nodeType, Node.DOCUMENT_NODE)
+ self.assertEqual(dom.documentElement.tagName, "HTML")
+ dom.unlink()
- def testCreateAttributeNS(self): pass
+ self.assertRaises(ExpatError, parseString, "")
+
+ def testParseString(self):
+ dom = parseString("text")
+ self.assertEqual(dom.nodeType, Node.DOCUMENT_NODE)
+ self.assertEqual(dom.documentElement.tagName, "doc")
+ self.assertEqual(dom.documentElement.firstChild.data, "text")
+ dom.unlink()
- def testParse(self): pass
+ dom = parseString(b""
+ b"\xc3\xa9")
+ self.assertEqual(dom.documentElement.firstChild.data, "\xe9")
+ dom.unlink()
- def testParseString(self): pass
+ def testComment(self):
+ dom = Document()
+ comment = dom.createComment("comment")
+ self.assertEqual(comment.nodeType, Node.COMMENT_NODE)
+ self.assertEqual(comment.nodeName, "#comment")
+ self.assertEqual(comment.data, "comment")
+ self.assertEqual(comment.nodeValue, "comment")
+ self.assertIsNone(comment.attributes)
+ dom.appendChild(comment)
+ self.assertEqual(dom.toxml(),
+ '')
+ dom.unlink()
- def testComment(self): pass
+ dom = parseString("")
+ comment = dom.documentElement.firstChild
+ self.assertEqual(comment.nodeType, Node.COMMENT_NODE)
+ self.assertEqual(comment.data, "comment")
+ dom.unlink()
- def testAttrListItem(self): pass
+ def testAttrListItem(self):
+ dom = parseString("")
+ attrs = dom.documentElement.attributes
+ self.assertEqual(attrs.item(0).name, "a")
+ self.assertEqual(attrs.item(1).name, "b")
+ self.assertIsNone(attrs.item(2))
+ dom.unlink()
- def testAttrListItems(self): pass
+ def testAttrListItems(self):
+ dom = parseString("")
+ attrs = dom.documentElement.attributes
+ self.assertEqual(attrs.items(), [("a", "1"), ("b", "2")])
+ dom.unlink()
- def testAttrListItemNS(self): pass
+ def testAttrListItemNS(self):
+ dom = parseString("")
+ attrs = dom.documentElement.attributes
+ self.assertEqual(attrs.itemsNS(), [
+ ((xml.dom.XMLNS_NAMESPACE, "p"), "http://xml.python.org/ns"),
+ (("http://xml.python.org/ns", "a"), "1"),
+ ((None, "b"), "2"),
+ ])
+ dom.unlink()
- def testAttrListKeys(self): pass
+ def testAttrListKeys(self):
+ dom = parseString("")
+ attrs = dom.documentElement.attributes
+ self.assertEqual(list(attrs.keys()), ["a", "b"])
+ dom.unlink()
- def testAttrListKeysNS(self): pass
+ def testAttrListKeysNS(self):
+ dom = parseString("")
+ attrs = dom.documentElement.attributes
+ self.assertEqual(list(attrs.keysNS()), [
+ (xml.dom.XMLNS_NAMESPACE, "p"),
+ ("http://xml.python.org/ns", "a"),
+ (None, "b"),
+ ])
+ dom.unlink()
def testRemoveNamedItem(self):
doc = parseString("")
@@ -746,29 +926,162 @@ def testRemoveNamedItemNS(self):
self.assertRaises(xml.dom.NotFoundErr, attrs.removeNamedItemNS,
"http://xml.python.org/", "b")
- def testAttrListValues(self): pass
+ def testAttrListValues(self):
+ dom = parseString("")
+ attrs = dom.documentElement.attributes
+ self.assertEqual([attr.name for attr in attrs.values()], ["a", "b"])
+ self.assertEqual([attr.value for attr in attrs.values()], ["1", "2"])
+ dom.unlink()
+
+ def testAttrListLength(self):
+ dom = parseString("")
+ attrs = dom.documentElement.attributes
+ self.assertEqual(attrs.length, 2)
+ self.assertEqual(len(attrs), 2)
+ dom.unlink()
+
+ dom = parseString("")
+ self.assertEqual(dom.documentElement.attributes.length, 0)
+ dom.unlink()
+
+ def testAttrList__getitem__(self):
+ dom = parseString("")
+ attrs = dom.documentElement.attributes
+ self.assertEqual(attrs["b"].value, "2")
+ self.assertEqual(attrs[("http://xml.python.org/ns", "a")].value, "1")
+ self.assertRaises(KeyError, attrs.__getitem__, "missing")
+ self.assertRaises(KeyError, attrs.__getitem__, (None, "missing"))
+ dom.unlink()
- def testAttrListLength(self): pass
+ def testAttrList__setitem__(self):
+ dom = parseString("")
+ elem = dom.documentElement
+ attrs = elem.attributes
+ attrs["a"] = "2"
+ self.assertEqual(elem.getAttribute("a"), "2")
+ attrs["b"] = "3"
+ self.assertEqual(elem.getAttribute("b"), "3")
+ self.assertEqual(attrs.length, 2)
+
+ attr = dom.createAttribute("c")
+ attr.value = "4"
+ attrs["c"] = attr
+ self.assertIs(elem.getAttributeNode("c"), attr)
+ self.assertEqual(elem.getAttribute("c"), "4")
+ dom.unlink()
- def testAttrList__getitem__(self): pass
+ def testSetAttrValueandNodeValue(self):
+ dom = parseString("")
+ attr = dom.documentElement.getAttributeNode("a")
+ self.assertEqual(attr.value, "1")
+ self.assertEqual(attr.nodeValue, "1")
+ attr.value = "2"
+ self.assertEqual(attr.nodeValue, "2")
+ attr.nodeValue = "3"
+ self.assertEqual(attr.value, "3")
+ self.assertEqual(dom.documentElement.getAttribute("a"), "3")
+ dom.unlink()
- def testAttrList__setitem__(self): pass
+ def testParseElement(self):
+ dom = parseString("")
+ elem = dom.documentElement
+ self.assertEqual(elem.nodeType, Node.ELEMENT_NODE)
+ self.assertEqual(elem.tagName, "doc")
+ self.assertIsNone(elem.namespaceURI)
+ self.assertIs(elem.parentNode, dom)
+ self.assertIs(elem.ownerDocument, dom)
+ self.assertEqual([child.tagName for child in elem.childNodes],
+ ["child", "child"])
+ dom.unlink()
- def testSetAttrValueandNodeValue(self): pass
+ def testParseAttributes(self):
+ dom = parseString("")
+ elem = dom.documentElement
+ self.assertEqual(elem.getAttribute("a"), "1")
+ self.assertEqual(elem.getAttribute("b"), "&")
+ self.assertEqual(elem.getAttribute("missing"), "")
+ self.assertTrue(elem.hasAttribute("a"))
+ self.assertFalse(elem.hasAttribute("missing"))
+ attr = elem.getAttributeNode("a")
+ self.assertTrue(attr.specified)
+ self.assertIsNone(attr.namespaceURI)
+ dom.unlink()
- def testParseElement(self): pass
+ def testParseElementNamespaces(self):
+ dom = parseString(""
+ "")
+ elem = dom.documentElement
+ self.assertEqual(elem.tagName, "p:doc")
+ self.assertEqual(elem.namespaceURI, "http://xml.python.org/ns")
+ self.assertEqual(elem.prefix, "p")
+ self.assertEqual(elem.localName, "doc")
+ child = elem.getElementsByTagName("child")[0]
+ self.assertEqual(child.namespaceURI, "http://xml.python.org/default")
+ self.assertIsNone(child.prefix)
+ self.assertEqual(child.localName, "child")
+ dom.unlink()
- def testParseAttributes(self): pass
+ def testParseAttributeNamespaces(self):
+ dom = parseString("")
+ elem = dom.documentElement
+ self.assertEqual(elem.getAttributeNS("http://xml.python.org/ns", "a"),
+ "1")
+ self.assertEqual(elem.getAttributeNS(None, "b"), "2")
+ attr = elem.getAttributeNodeNS("http://xml.python.org/ns", "a")
+ self.assertEqual(attr.name, "p:a")
+ self.assertEqual(attr.prefix, "p")
+ self.assertEqual(attr.localName, "a")
+ declaration = elem.getAttributeNode("xmlns:p")
+ self.assertEqual(declaration.namespaceURI, xml.dom.XMLNS_NAMESPACE)
+ self.assertEqual(declaration.value, "http://xml.python.org/ns")
+ dom.unlink()
- def testParseElementNamespaces(self): pass
+ def testParseProcessingInstructions(self):
+ # the content of a processing instruction is tested
+ # in testProcessingInstruction
+ dom = parseString("")
+ pi = dom.childNodes[0]
+ self.assertEqual(pi.nodeType, Node.PROCESSING_INSTRUCTION_NODE)
+ self.assertEqual(pi.target, "before")
+ self.assertEqual(pi.data, "data")
+ self.assertIs(pi.parentNode, dom)
+ pi = dom.childNodes[2]
+ self.assertEqual(pi.target, "after")
+ self.assertEqual(pi.data, "")
+ dom.unlink()
- def testParseAttributeNamespaces(self): pass
+ def testChildNodes(self):
+ dom = parseString("text")
+ children = dom.documentElement.childNodes
+ self.assertEqual(len(children), 3)
+ self.assertEqual([child.nodeType for child in children],
+ [Node.TEXT_NODE, Node.ELEMENT_NODE, Node.COMMENT_NODE])
+ for child in children:
+ self.assertIs(child.parentNode, dom.documentElement)
+ dom.unlink()
- def testParseProcessingInstructions(self): pass
+ dom = parseString("")
+ self.assertEqual(len(dom.documentElement.childNodes), 0)
+ dom.unlink()
- def testChildNodes(self): pass
+ def testFirstChild(self):
+ dom = parseString("")
+ elem = dom.documentElement
+ self.assertEqual(elem.firstChild.tagName, "a")
+ self.assertEqual(elem.lastChild.tagName, "b")
+ self.assertIs(elem.firstChild, elem.childNodes[0])
+ self.assertIs(elem.lastChild, elem.childNodes[-1])
+ self.assertIsNone(elem.firstChild.previousSibling)
+ self.assertIs(elem.firstChild.nextSibling, elem.lastChild)
+ dom.unlink()
- def testFirstChild(self): pass
+ dom = parseString("")
+ self.assertIsNone(dom.documentElement.firstChild)
+ self.assertIsNone(dom.documentElement.lastChild)
+ dom.unlink()
def testHasChildNodes(self):
dom = parseString("")
@@ -1810,6 +2123,69 @@ def test_cdata_parsing(self):
dom2 = parseString(dom1.toprettyxml())
self.checkWholeText(dom2.getElementsByTagName('node')[0].firstChild, '')
+ def testNamespaceErr(self):
+ doc = parseString("")
+ elem = doc.documentElement
+ XML_NS = xml.dom.XML_NAMESPACE
+ XMLNS_NS = xml.dom.XMLNS_NAMESPACE
+ for namespaceURI, qname in [
+ (None, "p:e"), # a prefix without a namespace
+ ("", "p:e"),
+ ("http://xml.python.org/ns", "p:p:e"), # malformed
+ ("http://xml.python.org/ns", "p:"),
+ ("http://xml.python.org/ns", "p:1e"),
+ ("http://xml.python.org/ns", "xml:e"), # the xml prefix
+ ]:
+ with self.subTest(namespaceURI=namespaceURI, qname=qname):
+ self.assertRaises(xml.dom.NamespaceErr,
+ doc.createElementNS, namespaceURI, qname)
+ self.assertRaises(xml.dom.NamespaceErr,
+ doc.createAttributeNS, namespaceURI, qname)
+ self.assertRaises(xml.dom.NamespaceErr,
+ elem.setAttributeNS, namespaceURI, qname, "v")
+
+ # the xmlns name and prefix are only allowed in the XMLNS namespace
+ for namespaceURI, qname in [
+ ("http://xml.python.org/ns", "xmlns"),
+ ("http://xml.python.org/ns", "xmlns:p"),
+ (None, "xmlns:p"),
+ (XMLNS_NS, "p:a"), # and it allows nothing else
+ (XMLNS_NS, "a"),
+ ]:
+ with self.subTest(namespaceURI=namespaceURI, qname=qname):
+ self.assertRaises(xml.dom.NamespaceErr,
+ doc.createAttributeNS, namespaceURI, qname)
+ self.assertRaises(xml.dom.NamespaceErr,
+ elem.setAttributeNS, namespaceURI, qname, "v")
+
+ # valid combinations
+ doc.createElementNS(None, "e")
+ doc.createElementNS("http://xml.python.org/ns", "p:e")
+ doc.createElementNS(XML_NS, "xml:e")
+ doc.createAttributeNS(None, "a")
+ doc.createAttributeNS(XML_NS, "xml:lang")
+ doc.createAttributeNS(XMLNS_NS, "xmlns")
+ doc.createAttributeNS(XMLNS_NS, "xmlns:p")
+ elem.setAttributeNS("http://xml.python.org/ns", "p:a", "v")
+ doc.unlink()
+
+ def testAttrPrefix(self):
+ doc = parseString("")
+ attr = doc.createAttributeNS("http://xml.python.org/ns", "p:a")
+ self.assertRaises(xml.dom.InvalidCharacterErr,
+ setattr, attr, "prefix", "q:r")
+ self.assertRaises(xml.dom.InvalidCharacterErr,
+ setattr, attr, "prefix", "1q")
+ self.assertRaises(xml.dom.NamespaceErr,
+ setattr, attr, "prefix", "xml")
+ self.assertRaises(xml.dom.NamespaceErr,
+ setattr, attr, "prefix", "xmlns")
+ attr.prefix = "q"
+ self.assertEqual(attr.name, "q:a")
+ attr.prefix = None
+ self.assertEqual(attr.name, "a")
+ doc.unlink()
+
def testInvalidCharacterErr(self):
doc = parseString("")
impl = getDOMImplementation()
diff --git a/Lib/test/test_nturl2path.py b/Lib/test/test_nturl2path.py
index a6a3422a0f75b2e..b4532137968d6e3 100644
--- a/Lib/test/test_nturl2path.py
+++ b/Lib/test/test_nturl2path.py
@@ -1,4 +1,6 @@
+import sys
import unittest
+import urllib.parse
from test.support import warnings_helper
@@ -58,6 +60,15 @@ def test_pathname2url(self):
for url in urls:
self.assertEqual(fn(nturl2path.url2pathname(url)), url)
+ def test_pathname2url_surrogates(self):
+ # gh-156713: the filesystem encoding and error handler are used,
+ # so that paths containing surrogate characters can be converted.
+ encoding = sys.getfilesystemencoding()
+ errors = sys.getfilesystemencodeerrors()
+ tail = urllib.parse.quote('a\udcff', encoding=encoding, errors=errors)
+ self.assertEqual(nturl2path.pathname2url('C:\\a\udcff'),
+ '///C:/' + tail)
+
def test_url2pathname(self):
fn = nturl2path.url2pathname
self.assertEqual(fn('/'), '\\')
@@ -103,5 +114,15 @@ def test_url2pathname(self):
self.assertEqual(fn(nturl2path.pathname2url(path)), path)
+ def test_url2pathname_surrogates(self):
+ # gh-156713: the filesystem encoding and error handler are used, so
+ # that URLs containing percent-encoded surrogates can be converted.
+ encoding = sys.getfilesystemencoding()
+ errors = sys.getfilesystemencodeerrors()
+ url = urllib.parse.quote('a\udcff', encoding=encoding, errors=errors)
+ self.assertEqual(nturl2path.url2pathname('///C:/' + url),
+ 'C:\\a\udcff')
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 8e4c28562a6cbe5..d5b00a3bb628cb1 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -3904,7 +3904,7 @@ def f():
def test_dont_swallow_cause_or_context_of_falsey_exception(self):
# see gh-132308: Ensure that __cause__ or __context__ attributes of exceptions
# that evaluate as falsey are included in the output. For falsey term,
- # see https://docs.python.org/3/library/stdtypes.html#truth-value-testing.
+ # see https://docs.python.org/3/builtins/stdtypes.html#truth-value-testing.
try:
raise FalseyException from KeyError
@@ -4123,7 +4123,7 @@ def test_comparison(self):
def test_dont_swallow_subexceptions_of_falsey_exceptiongroup(self):
# see gh-132308: Ensure that subexceptions of exception groups
# that evaluate as falsey are displayed in the output. For falsey term,
- # see https://docs.python.org/3/library/stdtypes.html#truth-value-testing.
+ # see https://docs.python.org/3/builtins/stdtypes.html#truth-value-testing.
try:
raise FalseyExceptionGroup("Gih", (KeyError(), NameError()))
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index 90e556ec95308bc..899947c1f8e0d74 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -773,9 +773,10 @@ def test_indent(self):
ET.indent(elem)
self.assertEqual(ET.tostring(elem), b'\n text\n')
+ # an element with mixed content is not indented
elem = ET.XML("texttail")
ET.indent(elem)
- self.assertEqual(ET.tostring(elem), b'\n texttail')
+ self.assertEqual(ET.tostring(elem), b'texttail')
elem = ET.XML("par
\ntext
\t
")
ET.indent(elem)
@@ -851,9 +852,39 @@ def test_indent_non_xml_whitespace(self):
ET.indent(elem)
self.assertEqual(
ET.tostring(elem),
- b' \n text
\n'
+ b' text
'
)
+ def test_indent_preserve(self):
+ # xml:space="preserve" applies to the whole subtree
+ elem = ET.XML(' text
')
+ ET.indent(elem)
+ self.assertEqual(
+ ET.tostring(elem),
+ b' text
'
+ )
+ # other values do not preserve whitespace
+ elem = ET.XML('text
')
+ ET.indent(elem)
+ self.assertEqual(
+ ET.tostring(elem),
+ b'\n'
+ b' \n'
+ b' text
\n'
+ b' \n'
+ b''
+ )
+
+ def test_indent_mixed_content(self):
+ # whitespace in an element which contains text is significant
+ elem = ET.XML('hello x y
')
+ ET.indent(elem)
+ self.assertEqual(ET.tostring(elem), b'hello x y
')
+ # the subtree of such element is not indented either
+ elem = ET.XML('hello y
')
+ ET.indent(elem)
+ self.assertEqual(ET.tostring(elem), b'hello y
')
+
def test_indent_level(self):
elem = ET.XML("pre
post
text
")
with self.assertRaises(ValueError):
@@ -910,6 +941,70 @@ def test_tostring_default_namespace_original_no_namespace(self):
with self.assertRaisesRegex(ValueError, EXPECTED_MSG):
ET.tostring(elem, encoding='unicode', default_namespace='foobar')
+ def test_tostring_default_namespace_attributes(self):
+ # gh-61290: the default namespace does not apply to attribute names
+ elem = ET.XML(''
+ '')
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode',
+ default_namespace='http://effbot.org/ns'),
+ ''
+ ''
+ )
+
+ def test_tostring_default_namespace_qualified_attributes(self):
+ # a qualified attribute name always needs a prefix, even if it is
+ # in the default namespace
+ elem = ET.Element('{http://effbot.org/ns}body',
+ {'{http://effbot.org/ns}attr': 'value'})
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode',
+ default_namespace='http://effbot.org/ns'),
+ ''
+ )
+ # an attribute in another namespace uses the prefix of that namespace
+ elem = ET.Element('{http://effbot.org/ns}body',
+ {'{foobar}attr': 'value', 'plain': 'value'})
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode',
+ default_namespace='http://effbot.org/ns'),
+ ''
+ )
+
+ def test_tostring_default_namespace_attributes_round_trip(self):
+ xml = ('')
+ elem = ET.XML(xml)
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode',
+ default_namespace='http://effbot.org/ns'),
+ xml
+ )
+ self.assertEqual(
+ [sorted(e.attrib.items()) for e in ET.XML(xml).iter()],
+ [sorted(e.attrib.items()) for e in elem.iter()]
+ )
+
+ def test_tostring_default_namespace_registered_empty_prefix(self):
+ # gh-118416: the empty prefix is registered for other namespace,
+ # so it cannot be used for the default namespace
+ nsmap = ET.register_namespace._namespace_map
+ self.addCleanup(nsmap.pop, 'default', None)
+ ET.register_namespace('', 'default')
+ elem = ET.Element('{default}elem')
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode',
+ default_namespace='otherdefault'),
+ ''
+ )
+ # without the option the registered prefix is used
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode'),
+ ''
+ )
+
def test_tostring_no_xml_declaration(self):
elem = ET.XML('')
self.assertEqual(
@@ -979,6 +1074,14 @@ def test_tostring_xml_declaration_cases(self):
expected_retval
)
+ def test_tostring_default_namespace_attributes_html(self):
+ elem = ET.XML('')
+ self.assertEqual(
+ ET.tostring(elem, encoding='unicode', method='html',
+ default_namespace='http://effbot.org/ns'),
+ ''
+ )
+
def test_tostring_standalone(self):
elem = ET.XML('')
self.assertEqual(
@@ -1656,6 +1759,43 @@ def test_unknown_events(self):
del cm
gc_collect()
+ class Target:
+ # a target which does not build a tree
+ def start(self, tag, attrib):
+ return tag
+ def end(self, tag):
+ return tag
+ def data(self, data):
+ pass
+
+ def test_target(self):
+ # gh-63102: a custom target reports its own objects
+ with open(SIMPLE_XMLFILE, 'rb') as f:
+ it = ET.iterparse(f, events=('start', 'end'), target=self.Target())
+ self.assertEqual(list(it), [
+ ('start', 'root'),
+ ('start', 'element'),
+ ('end', 'element'),
+ ('start', 'element'),
+ ('end', 'element'),
+ ('start', 'empty-element'),
+ ('end', 'empty-element'),
+ ('end', 'root'),
+ ])
+ self.assertIsNone(it.root)
+
+ def test_parser_with_target(self):
+ with open(SIMPLE_XMLFILE, 'rb') as f:
+ parser = ET.XMLParser(target=self.Target())
+ it = ET.iterparse(f, events=('start',), parser=parser)
+ self.assertEqual(next(it), ('start', 'root'))
+
+ def test_target_and_parser(self):
+ with self.assertRaisesRegex(ValueError,
+ "can't specify both parser and target"):
+ ET.iterparse(SIMPLE_XMLFILE, parser=ET.XMLParser(),
+ target=self.Target())
+
def test_non_utf8(self):
source = io.BytesIO(
b"\n"
@@ -2067,6 +2207,76 @@ def __next__(self):
self._feed(parser, "bar")
self.assert_event_tags(parser, [('start', 'foo'), ('end', 'foo')])
+ # gh-63102: the pull parser reports events from any target
+ class SimpleTarget:
+ def start(self, tag, attrib):
+ return ('start', tag)
+ def end(self, tag):
+ return ('end', tag)
+ def data(self, data):
+ pass
+ def comment(self, text):
+ return ('comment', text)
+ def pi(self, target, data=None):
+ return ('pi', target)
+ def close(self):
+ return 'closed'
+
+ def test_custom_target(self):
+ parser = ET.XMLPullParser(events=('start', 'end'),
+ target=self.SimpleTarget())
+ self._feed(parser, "")
+ self.assert_event_tuples(parser, [
+ ('start', ('start', 'root')),
+ ('start', ('start', 'element')),
+ ('end', ('end', 'element')),
+ ('end', ('end', 'root')),
+ ])
+
+ def test_custom_target_comment_pi(self):
+ parser = ET.XMLPullParser(events=('comment', 'pi'),
+ target=self.SimpleTarget())
+ self._feed(parser, "")
+ self.assert_event_tuples(parser, [
+ ('comment', ('comment', ' text ')),
+ ('pi', ('pi', 'pitarget')),
+ ])
+
+ def test_custom_target_without_method(self):
+ class Target:
+ def close(self):
+ pass
+ for event in ('start', 'end', 'comment', 'pi'):
+ with self.subTest(event=event):
+ with self.assertRaisesRegex(TypeError,
+ "the target does not support %r events" % event):
+ ET.XMLPullParser(events=(event,), target=Target())
+ # the namespace events do not need methods of the target
+ parser = ET.XMLPullParser(events=('start-ns', 'end-ns'),
+ target=Target())
+ self._feed(parser, "")
+ self.assert_event_tuples(parser, [
+ ('start-ns', ('', 'namespace')),
+ ('end-ns', None),
+ ])
+
+ def test_custom_target_ns_events(self):
+ # the target does not implement start_ns()/end_ns(),
+ # so the prefix and the uri are reported
+ parser = ET.XMLPullParser(events=('start-ns', 'end-ns'),
+ target=self.SimpleTarget())
+ self._feed(parser, "")
+ self.assert_event_tuples(parser, [
+ ('start-ns', ('', 'namespace')),
+ ('end-ns', None),
+ ])
+
+ def test_custom_target_close(self):
+ parser = ET.XMLPullParser(events=('end',), target=self.SimpleTarget())
+ self._feed(parser, "")
+ parser.close()
+ self.assert_event_tuples(parser, [('end', ('end', 'root'))])
+
def test_unknown_event(self):
with self.assertRaises(ValueError):
ET.XMLPullParser(events=('start', 'end', 'bogus'))
diff --git a/Lib/xml/dom/minidom.py b/Lib/xml/dom/minidom.py
index 7639fa14c5050fb..2edd3f438e686da 100644
--- a/Lib/xml/dom/minidom.py
+++ b/Lib/xml/dom/minidom.py
@@ -19,7 +19,8 @@
import xml
import xml.dom
-from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE, domreg
+from xml.dom import (EMPTY_NAMESPACE, EMPTY_PREFIX, XML_NAMESPACE,
+ XMLNS_NAMESPACE, domreg)
from xml.dom.minicompat import *
from xml.dom.xmlbuilder import DOMImplementationLS, DocumentLS
@@ -305,6 +306,35 @@ def _check_name(name):
"%r is not a valid XML name" % (name,))
+def _check_prefix(prefix, namespaceURI, attribute=False):
+ if not xml.is_valid_name(prefix) or ':' in prefix:
+ raise xml.dom.InvalidCharacterErr(
+ "%r is not a valid namespace prefix" % (prefix,))
+ if not namespaceURI:
+ raise xml.dom.NamespaceErr(
+ "cannot use the prefix %r with an empty namespace" % (prefix,))
+ if prefix == "xml" and namespaceURI != XML_NAMESPACE:
+ raise xml.dom.NamespaceErr(
+ "illegal use of the 'xml' prefix for the wrong namespace")
+ if attribute and (prefix == "xmlns") != (namespaceURI == XMLNS_NAMESPACE):
+ raise xml.dom.NamespaceErr(
+ "illegal use of the 'xmlns' prefix for the wrong namespace")
+
+
+def _check_qualified_name(namespaceURI, qualifiedName, attribute=False):
+ """Check a namespace URI and a qualified name (see DOM Level 2 Core)."""
+ _check_name(qualifiedName)
+ prefix, sep, localName = qualifiedName.partition(':')
+ if sep:
+ if not localName or ':' in localName or not xml.is_valid_name(localName):
+ raise xml.dom.NamespaceErr(
+ "%r is not a valid qualified name" % (qualifiedName,))
+ _check_prefix(prefix, namespaceURI, attribute)
+ elif attribute and (qualifiedName == "xmlns") != (namespaceURI == XMLNS_NAMESPACE):
+ raise xml.dom.NamespaceErr(
+ "illegal use of the 'xmlns' attribute for the wrong namespace")
+
+
def _is_ancestor(node, other):
"Returns true iff node is an ancestor of other."
other = other.parentNode
@@ -444,11 +474,8 @@ def _get_prefix(self):
return self._prefix
def _set_prefix(self, prefix):
- nsuri = self.namespaceURI
- if prefix == "xmlns":
- if nsuri and nsuri != XMLNS_NAMESPACE:
- raise xml.dom.NamespaceErr(
- "illegal use of 'xmlns' prefix for the wrong namespace")
+ if prefix is not None:
+ _check_prefix(prefix, self.namespaceURI, True)
self._prefix = prefix
if prefix is None:
newName = self.localName
@@ -807,10 +834,10 @@ def setAttribute(self, attname, value):
_clear_id_cache(self)
def setAttributeNS(self, namespaceURI, qualifiedName, value):
+ _check_qualified_name(namespaceURI, qualifiedName, True)
prefix, localname = _nssplit(qualifiedName)
attr = self.getAttributeNodeNS(namespaceURI, localname)
if attr is None:
- _check_name(qualifiedName)
attr = Attr(qualifiedName, namespaceURI, localname, prefix)
attr.value = value
attr.ownerDocument = self.ownerDocument
@@ -940,6 +967,10 @@ def writexml(self, writer, indent="", addindent="", newl=""):
self.childNodes[0].nodeType in (
Node.TEXT_NODE, Node.CDATA_SECTION_NODE)):
self.childNodes[0].writexml(writer, '', '', '')
+ elif self._preserves_whitespace():
+ # Adding whitespace here would change the content.
+ for node in self.childNodes:
+ node.writexml(writer, '', '', '')
else:
writer.write(newl)
for node in self.childNodes:
@@ -949,6 +980,25 @@ def writexml(self, writer, indent="", addindent="", newl=""):
else:
writer.write("/>%s"%(newl))
+ def _preserves_whitespace(self):
+ """Returns true iff whitespace in the content is significant.
+
+ This is the case if the element is marked with xml:space="preserve",
+ if the DTD declares that its content model is not element content,
+ or, in absence of such declaration, if it contains text.
+ """
+ if self.getAttribute("xml:space") == "preserve":
+ return True
+ doc = self.ownerDocument
+ info = doc and doc._get_elem_info(self)
+ if info is not None:
+ # Only whitespace in element content is ignorable
+ # (see XML 1.0, 3.2.1).
+ return not info.isElementContent()
+ return any(node.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE)
+ and node.data.strip(_XML_WHITESPACE)
+ for node in self.childNodes)
+
def _get_attributes(self):
self._ensure_attributes()
return NamedNodeMap(self._attrs, self._attrsNS, self)
@@ -1817,14 +1867,14 @@ def createAttribute(self, qName):
return a
def createElementNS(self, namespaceURI, qualifiedName):
- _check_name(qualifiedName)
+ _check_qualified_name(namespaceURI, qualifiedName)
prefix, localName = _nssplit(qualifiedName)
e = Element(qualifiedName, namespaceURI, prefix)
e.ownerDocument = self
return e
def createAttributeNS(self, namespaceURI, qualifiedName):
- _check_name(qualifiedName)
+ _check_qualified_name(namespaceURI, qualifiedName, True)
prefix, localName = _nssplit(qualifiedName)
a = Attr(qualifiedName, namespaceURI, localName, prefix)
a.ownerDocument = self
diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py
index ce98e4dc24a0d3a..0cd04ead801600e 100644
--- a/Lib/xml/etree/ElementTree.py
+++ b/Lib/xml/etree/ElementTree.py
@@ -104,6 +104,9 @@
# The white space characters of the XML specification (see XML 1.0, 2.3).
_XML_WHITESPACE = " \t\r\n"
+# The xml:space attribute (see XML 1.0, 2.10).
+_XML_SPACE = "{http://www.w3.org/XML/1998/namespace}space"
+
class ParseError(SyntaxError):
"""An error when parsing an XML document.
@@ -754,9 +757,10 @@ def write(self, file_or_filename,
if method == "text":
_serialize_text(write, self._root)
else:
- qnames, namespaces = _namespaces(self._root, default_namespace)
+ qnames, attr_qnames, namespaces = _namespaces(
+ self._root, default_namespace)
serialize = _serialize[method]
- serialize(write, self._root, qnames, namespaces,
+ serialize(write, self._root, qnames, attr_qnames, namespaces,
short_empty_elements=short_empty_elements)
# --------------------------------------------------------------------
@@ -817,28 +821,59 @@ def _namespaces(elem, default_namespace=None):
# maps qnames to *encoded* prefix:local names
qnames = {None: None}
+ # The default namespace declaration does not apply to attribute names,
+ # so they are encoded separately: an unqualified name is left as is,
+ # and a qualified name always gets a prefix.
+ attr_qnames = {None: None} if default_namespace else qnames
- # maps uri:s to prefixes
+ # maps prefixes to uri:s
namespaces = {}
+ # maps uri:s to prefixes, "" is the prefix of the default namespace
+ prefixes = {}
+ # maps uri:s to prefixes usable in attribute names
+ attr_prefixes = {} if default_namespace else prefixes
if default_namespace:
- namespaces[default_namespace] = ""
-
- def add_qname(qname):
+ namespaces[""] = default_namespace
+ prefixes[default_namespace] = ""
+
+ def get_prefix(uri, isattr):
+ # find or create the prefix for the namespace uri
+ if isattr:
+ prefix = attr_prefixes.get(uri)
+ if prefix is None:
+ # the empty prefix is of no use for an attribute name
+ prefix = prefixes.get(uri) or None
+ else:
+ prefix = prefixes.get(uri)
+ if prefix is not None:
+ return prefix
+ prefix = _namespace_map.get(uri)
+ if prefix is None or not prefix and (isattr or default_namespace):
+ # the empty prefix is of no use for an attribute name,
+ # and the default namespace is used for other uri
+ prefix = "ns%d" % len(namespaces)
+ if prefix != "xml":
+ namespaces[prefix] = uri
+ if isattr:
+ attr_prefixes[uri] = prefix
+ prefixes.setdefault(uri, prefix)
+ return prefix
+
+ def add_qname(qname, isattr=False):
# calculate serialized qname representation
try:
if qname[:1] == "{":
uri, tag = qname[1:].rsplit("}", 1)
- prefix = namespaces.get(uri)
- if prefix is None:
- prefix = _namespace_map.get(uri)
- if prefix is None:
- prefix = "ns%d" % len(namespaces)
- if prefix != "xml":
- namespaces[uri] = prefix
+ prefix = get_prefix(uri, isattr)
if prefix:
- qnames[qname] = "%s:%s" % (prefix, tag)
+ if isattr:
+ attr_qnames[qname] = "%s:%s" % (prefix, tag)
+ else:
+ qnames[qname] = "%s:%s" % (prefix, tag)
else:
qnames[qname] = tag # default element
+ elif isattr:
+ attr_qnames[qname] = qname
else:
if default_namespace:
# FIXME: can this be handled in XML 1.0?
@@ -864,16 +899,16 @@ def add_qname(qname):
for key, value in elem.items():
if isinstance(key, QName):
key = key.text
- if key not in qnames:
- add_qname(key)
+ if key not in attr_qnames:
+ add_qname(key, isattr=True)
if isinstance(value, QName) and value.text not in qnames:
add_qname(value.text)
text = elem.text
if isinstance(text, QName) and text.text not in qnames:
add_qname(text.text)
- return qnames, namespaces
+ return qnames, attr_qnames, namespaces
-def _serialize_xml(write, elem, qnames, namespaces,
+def _serialize_xml(write, elem, qnames, attr_qnames, namespaces,
short_empty_elements, **kwargs):
tag = elem.tag
text = elem.text
@@ -887,15 +922,14 @@ def _serialize_xml(write, elem, qnames, namespaces,
if text:
write(_escape_cdata(text))
for e in elem:
- _serialize_xml(write, e, qnames, None,
+ _serialize_xml(write, e, qnames, attr_qnames, None,
short_empty_elements=short_empty_elements)
else:
write("<" + tag)
items = list(elem.items())
if items or namespaces:
if namespaces:
- for v, k in sorted(namespaces.items(),
- key=lambda x: x[1]): # sort on prefix
+ for k, v in sorted(namespaces.items()): # sort on prefix
if k:
k = ":" + k
write(" xmlns%s=\"%s\"" % (
@@ -909,13 +943,13 @@ def _serialize_xml(write, elem, qnames, namespaces,
v = qnames[v.text]
else:
v = _escape_attrib(v)
- write(" %s=\"%s\"" % (qnames[k], v))
+ write(" %s=\"%s\"" % (attr_qnames[k], v))
if text or len(elem) or not short_empty_elements:
write(">")
if text:
write(_escape_cdata(text))
for e in elem:
- _serialize_xml(write, e, qnames, None,
+ _serialize_xml(write, e, qnames, attr_qnames, None,
short_empty_elements=short_empty_elements)
write("" + tag + ">")
else:
@@ -930,7 +964,7 @@ def _serialize_xml(write, elem, qnames, namespaces,
"img", "input", "isindex", "link", "meta", "param", "source",
"track", "wbr", "plaintext"}
-def _serialize_html(write, elem, qnames, namespaces, **kwargs):
+def _serialize_html(write, elem, qnames, attr_qnames, namespaces, **kwargs):
tag = elem.tag
text = elem.text
if tag is Comment:
@@ -943,14 +977,13 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs):
if text:
write(_escape_cdata(text))
for e in elem:
- _serialize_html(write, e, qnames, None)
+ _serialize_html(write, e, qnames, attr_qnames, None)
else:
write("<" + tag)
items = list(elem.items())
if items or namespaces:
if namespaces:
- for v, k in sorted(namespaces.items(),
- key=lambda x: x[1]): # sort on prefix
+ for k, v in sorted(namespaces.items()): # sort on prefix
if k:
k = ":" + k
write(" xmlns%s=\"%s\"" % (
@@ -960,7 +993,7 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs):
for k, v in items:
if isinstance(k, QName):
k = k.text
- k = qnames[k]
+ k = attr_qnames[k]
if v is None:
write(" %s" % k) # empty attr
else:
@@ -977,7 +1010,7 @@ def _serialize_html(write, elem, qnames, namespaces, **kwargs):
else:
write(_escape_cdata(text))
for e in elem:
- _serialize_html(write, e, qnames, None)
+ _serialize_html(write, e, qnames, attr_qnames, None)
if ltag not in HTML_EMPTY:
write("" + tag + ">")
if elem.tail:
@@ -1196,7 +1229,20 @@ def indent(tree, space=" ", level=0):
# Reduce the memory consumption by reusing indentation strings.
indentations = ["\n" + level * space]
+ def _preserves_whitespace(elem):
+ # True iff whitespace in the content of the element is significant.
+ if elem.get(_XML_SPACE) == "preserve":
+ return True
+ if elem.text and elem.text.strip(_XML_WHITESPACE):
+ return True
+ return any(child.tail and child.tail.strip(_XML_WHITESPACE)
+ for child in elem)
+
def _indent_children(elem, level):
+ if _preserves_whitespace(elem):
+ # Adding whitespace here would change the content.
+ return
+
# Start a new indentation level for the first child.
child_level = level + 1
try:
@@ -1205,18 +1251,15 @@ def _indent_children(elem, level):
child_indentation = indentations[level] + space
indentations.append(child_indentation)
- if not elem.text or not elem.text.strip(_XML_WHITESPACE):
- elem.text = child_indentation
+ elem.text = child_indentation
for child in elem:
if len(child):
_indent_children(child, child_level)
- if not child.tail or not child.tail.strip(_XML_WHITESPACE):
- child.tail = child_indentation
+ child.tail = child_indentation
# Dedent after the last child by overwriting the previous indentation.
- if not child.tail.strip(_XML_WHITESPACE):
- child.tail = indentations[level]
+ child.tail = indentations[level]
_indent_children(tree, 0)
@@ -1239,7 +1282,7 @@ def parse(source, parser=None):
return tree
-def iterparse(source, events=None, parser=None):
+def iterparse(source, events=None, parser=None, *, target=None):
"""Incrementally parse XML document into ElementTree.
This class also reports what's going on to the user based on the
@@ -1250,14 +1293,14 @@ def iterparse(source, events=None, parser=None):
*source* is a filename or file object containing XML data, *events* is
a list of events to report back, *parser* is an optional parser
- instance.
+ instance, *target* is an optional target of the standard parser.
Returns an iterator providing (event, elem) pairs.
"""
# Use the internal, undocumented _parser argument for now; When the
# parser argument of iterparse is removed, this can be killed.
- pullparser = XMLPullParser(events=events, _parser=parser)
+ pullparser = XMLPullParser(events=events, target=target, _parser=parser)
if not hasattr(source, "read"):
source = open(source, "rb")
@@ -1309,13 +1352,19 @@ def __del__(self, _warn=warnings.warn):
class XMLPullParser:
- def __init__(self, events=None, *, _parser=None):
+ def __init__(self, events=None, *, target=None, _parser=None):
# The _parser argument is for internal use only and must not be relied
# upon in user code. It will be removed in a future release.
# See https://bugs.python.org/issue17741 for more details.
self._events_queue = collections.deque()
- self._parser = _parser or XMLParser(target=TreeBuilder())
+ if _parser is None:
+ if target is None:
+ target = TreeBuilder()
+ _parser = XMLParser(target=target)
+ elif target is not None:
+ raise ValueError("can't specify both parser and target")
+ self._parser = _parser
# wire up the parser for event reporting
if events is None:
events = ("end",)
@@ -1611,6 +1660,10 @@ def _setevents(self, events_queue, events_to_report):
parser = self._parser
append = events_queue.append
for event_name in events_to_report:
+ if (event_name in ("start", "end", "comment", "pi")
+ and not hasattr(self.target, event_name)):
+ raise TypeError("the target does not support %r events"
+ % event_name)
if event_name == "start":
parser.ordered_attributes = 1
def handler(tag, attrib_in, event=event_name, append=append,
@@ -1643,13 +1696,14 @@ def handler(prefix, event=event_name, append=append):
append((event, None))
parser.EndNamespaceDeclHandler = handler
elif event_name == 'comment':
- def handler(text, event=event_name, append=append, self=self):
- append((event, self.target.comment(text)))
+ def handler(text, event=event_name, append=append,
+ comment=self.target.comment):
+ append((event, comment(text)))
parser.CommentHandler = handler
elif event_name == 'pi':
def handler(pi_target, data, event=event_name, append=append,
- self=self):
- append((event, self.target.pi(pi_target, data)))
+ pi=self.target.pi):
+ append((event, pi(pi_target, data)))
parser.ProcessingInstructionHandler = handler
else:
raise ValueError("unknown event %r" % event_name)
diff --git a/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst b/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst
new file mode 100644
index 000000000000000..bdc02a4c55ee03b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-30-18-00-00.gh-issue-81623.Vh2Kt6.rst
@@ -0,0 +1,6 @@
+:meth:`~xml.dom.minidom.Node.toprettyxml` in :mod:`xml.dom.minidom` and
+:func:`~xml.etree.ElementTree.indent` in :mod:`xml.etree.ElementTree` no longer
+add whitespace inside an element which is marked with ``xml:space="preserve"``
+or which contains text (:meth:`!toprettyxml` also takes into account the
+content model declared in the DTD). Previously such indentation changed the
+content of the element.
diff --git a/Misc/NEWS.d/next/Library/2026-08-30-21-00-00.gh-issue-156665.Jm3Kp9.rst b/Misc/NEWS.d/next/Library/2026-08-30-21-00-00.gh-issue-156665.Jm3Kp9.rst
new file mode 100644
index 000000000000000..b1068591078a17f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-30-21-00-00.gh-issue-156665.Jm3Kp9.rst
@@ -0,0 +1,8 @@
+:mod:`xml.dom.minidom` now validates namespaces in
+:meth:`~xml.dom.Document.createElementNS`,
+:meth:`~xml.dom.Document.createAttributeNS` and
+:meth:`~xml.dom.Element.setAttributeNS`, and when setting
+:attr:`~xml.dom.Node.prefix` of an attribute.
+:exc:`~xml.dom.NamespaceErr` is now raised for a malformed qualified name, for
+a prefix with an empty namespace, and for illegal use of the ``xml`` and
+``xmlns`` prefixes.
diff --git a/Misc/NEWS.d/next/Library/2026-08-31-17-10-00.gh-issue-156713.Nt2URL.rst b/Misc/NEWS.d/next/Library/2026-08-31-17-10-00.gh-issue-156713.Nt2URL.rst
new file mode 100644
index 000000000000000..1d21fc70fd9256f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-31-17-10-00.gh-issue-156713.Nt2URL.rst
@@ -0,0 +1,4 @@
+Fix :func:`!nturl2path.pathname2url` and :func:`!nturl2path.url2pathname`:
+the filesystem encoding and error handler are now used for percent-encoding
+and decoding, as in :mod:`urllib.request`. Previously paths containing
+surrogate characters raised :exc:`UnicodeEncodeError`.
diff --git a/Misc/NEWS.d/next/Library/2026-09-01-00-30-00.gh-issue-61290.Vx7pQ2.rst b/Misc/NEWS.d/next/Library/2026-09-01-00-30-00.gh-issue-61290.Vx7pQ2.rst
new file mode 100644
index 000000000000000..585ba119c44b427
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-01-00-30-00.gh-issue-61290.Vx7pQ2.rst
@@ -0,0 +1,5 @@
+:mod:`xml.etree.ElementTree` no longer refuses to serialize attributes
+without a namespace when the *default_namespace* option is used.
+The default namespace declaration does not apply to attribute names,
+so an unqualified attribute name is written as is,
+and a qualified attribute name is always written with a prefix.
diff --git a/Misc/NEWS.d/next/Library/2026-09-01-02-30-00.gh-issue-63102.Pz3wK8.rst b/Misc/NEWS.d/next/Library/2026-09-01-02-30-00.gh-issue-63102.Pz3wK8.rst
new file mode 100644
index 000000000000000..cd769c804aab6c6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-01-02-30-00.gh-issue-63102.Pz3wK8.rst
@@ -0,0 +1,6 @@
+:class:`~xml.etree.ElementTree.XMLPullParser` and
+:func:`~xml.etree.ElementTree.iterparse` now support the *target* parameter.
+The reported object is the value returned by the corresponding method
+of the target, so no tree is built if the target does not build one.
+Only the standard :class:`~xml.etree.ElementTree.TreeBuilder` was supported
+in the C implementation before.
diff --git a/Misc/NEWS.d/next/Library/2026-09-13-07-14-35.gh-issue-128509.9F2GCE.rst b/Misc/NEWS.d/next/Library/2026-09-13-07-14-35.gh-issue-128509.9F2GCE.rst
new file mode 100644
index 000000000000000..7699242c8c647ea
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-13-07-14-35.gh-issue-128509.9F2GCE.rst
@@ -0,0 +1,2 @@
+:func:`marshal.load` and :func:`marshal.loads` can now get 1-byte string
+singletons. Patch by Victor Stinner.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2026-08-05-18-16-23.gh-issue-108271.Yvq2Tv.rst b/Misc/NEWS.d/next/Tools-Demos/2026-08-05-18-16-23.gh-issue-108271.Yvq2Tv.rst
new file mode 100644
index 000000000000000..3e9d07600223b91
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2026-08-05-18-16-23.gh-issue-108271.Yvq2Tv.rst
@@ -0,0 +1,3 @@
+Argument Clinic: add support for parameter aliases.
+A keyword-only parameter with a default value which shares the C name of a
+preceding parameter declares an alternative name for it.
diff --git a/Misc/NEWS.d/next/Tools-Demos/2026-08-05-18-16-24.gh-issue-108270.kVDJSS.rst b/Misc/NEWS.d/next/Tools-Demos/2026-08-05-18-16-24.gh-issue-108270.kVDJSS.rst
new file mode 100644
index 000000000000000..07ba709e3898a3e
--- /dev/null
+++ b/Misc/NEWS.d/next/Tools-Demos/2026-08-05-18-16-24.gh-issue-108270.kVDJSS.rst
@@ -0,0 +1,3 @@
+Argument Clinic: add support for deprecating a parameter with the ``[until
+X.Y]`` marker.
+Passing such argument emits a :exc:`DeprecationWarning`.
diff --git a/Misc/NEWS.d/next/Windows/2026-09-12-18-59-21.gh-issue-157368.2DirHP.rst b/Misc/NEWS.d/next/Windows/2026-09-12-18-59-21.gh-issue-157368.2DirHP.rst
new file mode 100644
index 000000000000000..72e11bfe2b15384
--- /dev/null
+++ b/Misc/NEWS.d/next/Windows/2026-09-12-18-59-21.gh-issue-157368.2DirHP.rst
@@ -0,0 +1,3 @@
+Fix truncated :data:`sys.version` in the case of clang-cl versions 22 or newer
+on Windows. Also fixes the Windows on Arm case, where the Microsoft
+compiler was reported instead of clang. Patch by Chris Eibl.
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 9fc0be043bac9bb..d10ed146db92b6b 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -173,7 +173,7 @@
@MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
@MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c
@MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c _testinternalcapi/test_lock.c _testinternalcapi/pytime.c _testinternalcapi/set.c _testinternalcapi/test_critical_sections.c _testinternalcapi/complex.c _testinternalcapi/interpreter.c _testinternalcapi/tokenizer.c _testinternalcapi/tuple.c _testinternalcapi/typecache.c
-@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c
+@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/heaptype.c _testcapi/abstract.c _testcapi/unicode.c _testcapi/dict.c _testcapi/set.c _testcapi/list.c _testcapi/tuple.c _testcapi/getargs.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/complex.c _testcapi/numbers.c _testcapi/structmember.c _testcapi/exceptions.c _testcapi/code.c _testcapi/buffer.c _testcapi/pyatomic.c _testcapi/run.c _testcapi/file.c _testcapi/codec.c _testcapi/immortal.c _testcapi/gc.c _testcapi/hash.c _testcapi/time.c _testcapi/bytes.c _testcapi/object.c _testcapi/modsupport.c _testcapi/monitoring.c _testcapi/config.c _testcapi/import.c _testcapi/frame.c _testcapi/type.c _testcapi/function.c _testcapi/module.c _testcapi/weakref.c _testcapi/marshal.c
@MODULE__TESTLIMITEDCAPI_TRUE@_testlimitedcapi _testlimitedcapi.c _testlimitedcapi/abstract.c _testlimitedcapi/bytearray.c _testlimitedcapi/bytes.c _testlimitedcapi/capsule.c _testlimitedcapi/codec.c _testlimitedcapi/complex.c _testlimitedcapi/dict.c _testlimitedcapi/eval.c _testlimitedcapi/float.c _testlimitedcapi/heaptype_relative.c _testlimitedcapi/import.c _testlimitedcapi/list.c _testlimitedcapi/long.c _testlimitedcapi/object.c _testlimitedcapi/pyos.c _testlimitedcapi/set.c _testlimitedcapi/slots.c _testlimitedcapi/sys.c _testlimitedcapi/threadstate.c _testlimitedcapi/tuple.c _testlimitedcapi/unicode.c _testlimitedcapi/vectorcall_limited.c _testlimitedcapi/version.c _testlimitedcapi/file.c _testlimitedcapi/weakref.c _testlimitedcapi/run.c _testlimitedcapi/type.c
@MODULE__TESTCLINIC_TRUE@_testclinic _testclinic.c
@MODULE__TESTCLINIC_LIMITED_TRUE@_testclinic_limited _testclinic_limited.c
diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c
index 6f51f10b2b22759..36115e61c2c2152 100644
--- a/Modules/_elementtree.c
+++ b/Modules/_elementtree.c
@@ -2482,14 +2482,6 @@ typedef struct {
PyObject *pi_factory;
/* element tracing */
- PyObject *events_append; /* the append method of the list of events, or NULL */
- PyObject *start_event_obj; /* event objects (NULL to ignore) */
- PyObject *end_event_obj;
- PyObject *start_ns_event_obj;
- PyObject *end_ns_event_obj;
- PyObject *comment_event_obj;
- PyObject *pi_event_obj;
-
char insert_comments;
char insert_pis;
elementtreestate *state;
@@ -2523,10 +2515,6 @@ treebuilder_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
}
t->index = 0;
- t->events_append = NULL;
- t->start_event_obj = t->end_event_obj = NULL;
- t->start_ns_event_obj = t->end_ns_event_obj = NULL;
- t->comment_event_obj = t->pi_event_obj = NULL;
t->insert_comments = t->insert_pis = 0;
t->state = get_elementtree_state_by_type(type);
}
@@ -2609,13 +2597,6 @@ treebuilder_gc_traverse(PyObject *op, visitproc visit, void *arg)
{
TreeBuilderObject *self = _TreeBuilder_CAST(op);
Py_VISIT(Py_TYPE(self));
- Py_VISIT(self->pi_event_obj);
- Py_VISIT(self->comment_event_obj);
- Py_VISIT(self->end_ns_event_obj);
- Py_VISIT(self->start_ns_event_obj);
- Py_VISIT(self->end_event_obj);
- Py_VISIT(self->start_event_obj);
- Py_VISIT(self->events_append);
Py_VISIT(self->root);
Py_VISIT(self->this);
Py_VISIT(self->last);
@@ -2632,13 +2613,6 @@ static int
treebuilder_gc_clear(PyObject *op)
{
TreeBuilderObject *self = _TreeBuilder_CAST(op);
- Py_CLEAR(self->pi_event_obj);
- Py_CLEAR(self->comment_event_obj);
- Py_CLEAR(self->end_ns_event_obj);
- Py_CLEAR(self->start_ns_event_obj);
- Py_CLEAR(self->end_event_obj);
- Py_CLEAR(self->start_event_obj);
- Py_CLEAR(self->events_append);
Py_CLEAR(self->stack);
Py_CLEAR(self->data);
Py_CLEAR(self->last);
@@ -2808,24 +2782,6 @@ treebuilder_add_subelement(elementtreestate *st, PyObject *element,
}
}
-LOCAL(int)
-treebuilder_append_event(TreeBuilderObject *self, PyObject *action,
- PyObject *node)
-{
- if (action != NULL) {
- PyObject *res;
- PyObject *event = _PyTuple_FromPair(action, node);
- if (event == NULL)
- return -1;
- res = PyObject_CallOneArg(self->events_append, event);
- Py_DECREF(event);
- if (res == NULL)
- return -1;
- Py_DECREF(res);
- }
- return 0;
-}
-
/* -------------------------------------------------------------------- */
/* handlers */
@@ -2891,9 +2847,6 @@ treebuilder_handle_start(TreeBuilderObject* self, PyObject* tag,
Py_SETREF(self->this, Py_NewRef(node));
Py_SETREF(self->last, Py_NewRef(node));
- if (treebuilder_append_event(self, self->start_event_obj, node) < 0)
- goto error;
-
return node;
error:
@@ -2954,11 +2907,6 @@ treebuilder_handle_end(TreeBuilderObject* self, PyObject* tag)
Py_DECREF(last);
Py_XDECREF(last_for_tail);
- if (treebuilder_append_event(self, self->end_event_obj, self->last) < 0) {
- Py_DECREF(this);
- return NULL;
- }
-
return this;
}
@@ -2988,11 +2936,6 @@ treebuilder_handle_comment(TreeBuilderObject* self, PyObject* text)
comment = Py_NewRef(text);
}
- if (self->events_append && self->comment_event_obj) {
- if (treebuilder_append_event(self, self->comment_event_obj, comment) < 0)
- goto error;
- }
-
return comment;
error:
@@ -3031,11 +2974,6 @@ treebuilder_handle_pi(TreeBuilderObject* self, PyObject* target, PyObject* text)
}
}
- if (self->events_append && self->pi_event_obj) {
- if (treebuilder_append_event(self, self->pi_event_obj, pi) < 0)
- goto error;
- }
-
return pi;
error:
@@ -3043,39 +2981,6 @@ treebuilder_handle_pi(TreeBuilderObject* self, PyObject* target, PyObject* text)
return NULL;
}
-LOCAL(PyObject*)
-treebuilder_handle_start_ns(TreeBuilderObject* self, PyObject* prefix, PyObject* uri)
-{
- PyObject* parcel;
-
- if (self->events_append && self->start_ns_event_obj) {
- parcel = _PyTuple_FromPair(prefix, uri);
- if (!parcel) {
- return NULL;
- }
-
- if (treebuilder_append_event(self, self->start_ns_event_obj, parcel) < 0) {
- Py_DECREF(parcel);
- return NULL;
- }
- Py_DECREF(parcel);
- }
-
- Py_RETURN_NONE;
-}
-
-LOCAL(PyObject*)
-treebuilder_handle_end_ns(TreeBuilderObject* self, PyObject* prefix)
-{
- if (self->events_append && self->end_ns_event_obj) {
- if (treebuilder_append_event(self, self->end_ns_event_obj, prefix) < 0) {
- return NULL;
- }
- }
-
- Py_RETURN_NONE;
-}
-
/* -------------------------------------------------------------------- */
/* methods (in alphabetical order) */
@@ -3222,6 +3127,15 @@ typedef struct {
PyObject *handle_start_ns;
PyObject *handle_end_ns;
+
+ /* event reporting for the pull API */
+ PyObject *events_append; /* the append method of the list of events */
+ PyObject *start_event_obj; /* event objects (NULL to ignore) */
+ PyObject *end_event_obj;
+ PyObject *start_ns_event_obj;
+ PyObject *end_ns_event_obj;
+ PyObject *comment_event_obj;
+ PyObject *pi_event_obj;
PyObject *handle_start;
PyObject *handle_data;
PyObject *handle_end;
@@ -3411,6 +3325,26 @@ expat_default_handler(void *op, const XML_Char *data_in, int data_len)
Py_DECREF(key);
}
+/* Append (action, node) to the list of events of the pull parser. */
+LOCAL(int)
+xmlparser_append_event(XMLParserObject *self, PyObject *action, PyObject *node)
+{
+ if (self->events_append == NULL || action == NULL || node == NULL) {
+ return 0;
+ }
+ PyObject *event = _PyTuple_FromPair(action, node);
+ if (event == NULL) {
+ return -1;
+ }
+ PyObject *res = PyObject_CallOneArg(self->events_append, event);
+ Py_DECREF(event);
+ if (res == NULL) {
+ return -1;
+ }
+ Py_DECREF(res);
+ return 0;
+}
+
static void
expat_start_handler(void *op, const XML_Char *tag_in,
const XML_Char **attrib_in)
@@ -3486,7 +3420,10 @@ expat_start_handler(void *op, const XML_Char *tag_in,
Py_DECREF(tag);
Py_XDECREF(attrib);
- Py_XDECREF(res);
+ if (res != NULL) {
+ (void)xmlparser_append_event(self, self->start_event_obj, res);
+ Py_DECREF(res);
+ }
}
static void
@@ -3543,7 +3480,10 @@ expat_end_handler(void *op, const XML_Char *tag_in)
}
}
- Py_XDECREF(res);
+ if (res != NULL) {
+ (void)xmlparser_append_event(self, self->end_event_obj, res);
+ Py_DECREF(res);
+ }
}
static void
@@ -3563,42 +3503,34 @@ expat_start_ns_handler(void *op, const XML_Char *prefix_in,
if (!prefix_in)
prefix_in = "";
- elementtreestate *st = self->state;
- if (TreeBuilder_CheckExact(st, self->target)) {
- /* shortcut - TreeBuilder does not actually implement .start_ns() */
- TreeBuilderObject *target = (TreeBuilderObject*) self->target;
-
- if (target->events_append && target->start_ns_event_obj) {
- prefix = PyUnicode_DecodeUTF8(prefix_in, strlen(prefix_in), "strict");
- if (!prefix)
- return;
- uri = PyUnicode_DecodeUTF8(uri_in, strlen(uri_in), "strict");
- if (!uri) {
- Py_DECREF(prefix);
- return;
- }
+ if (self->handle_start_ns == NULL && self->start_ns_event_obj == NULL) {
+ return;
+ }
- res = treebuilder_handle_start_ns(target, prefix, uri);
- Py_DECREF(uri);
- Py_DECREF(prefix);
- }
- } else if (self->handle_start_ns) {
- prefix = PyUnicode_DecodeUTF8(prefix_in, strlen(prefix_in), "strict");
- if (!prefix)
- return;
- uri = PyUnicode_DecodeUTF8(uri_in, strlen(uri_in), "strict");
- if (!uri) {
- Py_DECREF(prefix);
- return;
- }
+ prefix = PyUnicode_DecodeUTF8(prefix_in, strlen(prefix_in), "strict");
+ if (!prefix)
+ return;
+ uri = PyUnicode_DecodeUTF8(uri_in, strlen(uri_in), "strict");
+ if (!uri) {
+ Py_DECREF(prefix);
+ return;
+ }
+ if (self->handle_start_ns) {
PyObject *args[2] = {prefix, uri};
res = PyObject_Vectorcall(self->handle_start_ns, args, 2, NULL);
- Py_DECREF(uri);
- Py_DECREF(prefix);
}
+ else {
+ /* the target does not implement .start_ns(), report the pair */
+ res = _PyTuple_FromPair(prefix, uri);
+ }
+ Py_DECREF(uri);
+ Py_DECREF(prefix);
- Py_XDECREF(res);
+ if (res != NULL) {
+ (void)xmlparser_append_event(self, self->start_ns_event_obj, res);
+ Py_DECREF(res);
+ }
}
static void
@@ -3614,15 +3546,7 @@ expat_end_ns_handler(void *op, const XML_Char *prefix_in)
if (!prefix_in)
prefix_in = "";
- elementtreestate *st = self->state;
- if (TreeBuilder_CheckExact(st, self->target)) {
- /* shortcut - TreeBuilder does not actually implement .end_ns() */
- TreeBuilderObject *target = (TreeBuilderObject*) self->target;
-
- if (target->events_append && target->end_ns_event_obj) {
- res = treebuilder_handle_end_ns(target, Py_None);
- }
- } else if (self->handle_end_ns) {
+ if (self->handle_end_ns) {
prefix = PyUnicode_DecodeUTF8(prefix_in, strlen(prefix_in), "strict");
if (!prefix)
return;
@@ -3630,8 +3554,15 @@ expat_end_ns_handler(void *op, const XML_Char *prefix_in)
res = PyObject_CallOneArg(self->handle_end_ns, prefix);
Py_DECREF(prefix);
}
+ else if (self->end_ns_event_obj) {
+ /* the target does not implement .end_ns() */
+ res = Py_NewRef(Py_None);
+ }
- Py_XDECREF(res);
+ if (res != NULL) {
+ (void)xmlparser_append_event(self, self->end_ns_event_obj, res);
+ Py_DECREF(res);
+ }
}
static void
@@ -3654,16 +3585,22 @@ expat_comment_handler(void *op, const XML_Char *comment_in)
return; /* parser will look for errors */
res = treebuilder_handle_comment(target, comment);
- Py_XDECREF(res);
Py_DECREF(comment);
+ if (res != NULL) {
+ (void)xmlparser_append_event(self, self->comment_event_obj, res);
+ Py_DECREF(res);
+ }
} else if (self->handle_comment) {
comment = PyUnicode_DecodeUTF8(comment_in, strlen(comment_in), "strict");
if (!comment)
return;
res = PyObject_CallOneArg(self->handle_comment, comment);
- Py_XDECREF(res);
Py_DECREF(comment);
+ if (res != NULL) {
+ (void)xmlparser_append_event(self, self->comment_event_obj, res);
+ Py_DECREF(res);
+ }
}
}
@@ -3743,7 +3680,7 @@ expat_pi_handler(void *op, const XML_Char *target_in,
/* shortcut */
TreeBuilderObject *target = (TreeBuilderObject*) self->target;
- if ((target->events_append && target->pi_event_obj) || target->insert_pis) {
+ if (self->pi_event_obj || target->insert_pis) {
pi_target = PyUnicode_DecodeUTF8(target_in, strlen(target_in), "strict");
if (!pi_target)
goto error;
@@ -3751,9 +3688,12 @@ expat_pi_handler(void *op, const XML_Char *target_in,
if (!data)
goto error;
res = treebuilder_handle_pi(target, pi_target, data);
- Py_XDECREF(res);
Py_DECREF(data);
Py_DECREF(pi_target);
+ if (res != NULL) {
+ (void)xmlparser_append_event(self, self->pi_event_obj, res);
+ Py_DECREF(res);
+ }
}
} else if (self->handle_pi) {
pi_target = PyUnicode_DecodeUTF8(target_in, strlen(target_in), "strict");
@@ -3765,9 +3705,12 @@ expat_pi_handler(void *op, const XML_Char *target_in,
PyObject *args[2] = {pi_target, data};
res = PyObject_Vectorcall(self->handle_pi, args, 2, NULL);
- Py_XDECREF(res);
Py_DECREF(data);
Py_DECREF(pi_target);
+ if (res != NULL) {
+ (void)xmlparser_append_event(self, self->pi_event_obj, res);
+ Py_DECREF(res);
+ }
}
return;
@@ -3790,6 +3733,10 @@ xmlparser_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
self->handle_start = self->handle_data = self->handle_end = NULL;
self->handle_comment = self->handle_pi = self->handle_close = NULL;
self->handle_doctype = NULL;
+ self->events_append = NULL;
+ self->start_event_obj = self->end_event_obj = NULL;
+ self->start_ns_event_obj = self->end_ns_event_obj = NULL;
+ self->comment_event_obj = self->pi_event_obj = NULL;
self->elementtree_module = PyType_GetModuleByDef(type, &elementtreemodule);
assert(self->elementtree_module != NULL);
Py_INCREF(self->elementtree_module);
@@ -3965,6 +3912,13 @@ xmlparser_gc_traverse(PyObject *op, visitproc visit, void *arg)
Py_VISIT(self->handle_start_ns);
Py_VISIT(self->handle_end_ns);
Py_VISIT(self->handle_doctype);
+ Py_VISIT(self->events_append);
+ Py_VISIT(self->start_event_obj);
+ Py_VISIT(self->end_event_obj);
+ Py_VISIT(self->start_ns_event_obj);
+ Py_VISIT(self->end_ns_event_obj);
+ Py_VISIT(self->comment_event_obj);
+ Py_VISIT(self->pi_event_obj);
Py_VISIT(self->target);
Py_VISIT(self->entity);
@@ -3994,6 +3948,13 @@ xmlparser_gc_clear(PyObject *op)
Py_CLEAR(self->handle_start_ns);
Py_CLEAR(self->handle_end_ns);
Py_CLEAR(self->handle_doctype);
+ Py_CLEAR(self->events_append);
+ Py_CLEAR(self->start_event_obj);
+ Py_CLEAR(self->end_event_obj);
+ Py_CLEAR(self->start_ns_event_obj);
+ Py_CLEAR(self->end_ns_event_obj);
+ Py_CLEAR(self->comment_event_obj);
+ Py_CLEAR(self->pi_event_obj);
Py_CLEAR(self->target);
Py_CLEAR(self->entity);
@@ -4287,40 +4248,28 @@ _elementtree_XMLParser__setevents_impl(XMLParserObject *self,
{
/* activate element event reporting */
Py_ssize_t i;
- TreeBuilderObject *target;
PyObject *events_append, *events_seq;
if (!_check_xmlparser(self)) {
return NULL;
}
elementtreestate *st = self->state;
- if (!TreeBuilder_CheckExact(st, self->target)) {
- PyErr_SetString(
- PyExc_TypeError,
- "event handling only supported for ElementTree.TreeBuilder "
- "targets"
- );
- return NULL;
- }
-
- target = (TreeBuilderObject*) self->target;
-
events_append = PyObject_GetAttrString(events_queue, "append");
if (events_append == NULL)
return NULL;
- Py_XSETREF(target->events_append, events_append);
+ Py_XSETREF(self->events_append, events_append);
/* clear out existing events */
- Py_CLEAR(target->start_event_obj);
- Py_CLEAR(target->end_event_obj);
- Py_CLEAR(target->start_ns_event_obj);
- Py_CLEAR(target->end_ns_event_obj);
- Py_CLEAR(target->comment_event_obj);
- Py_CLEAR(target->pi_event_obj);
+ Py_CLEAR(self->start_event_obj);
+ Py_CLEAR(self->end_event_obj);
+ Py_CLEAR(self->start_ns_event_obj);
+ Py_CLEAR(self->end_ns_event_obj);
+ Py_CLEAR(self->comment_event_obj);
+ Py_CLEAR(self->pi_event_obj);
if (events_to_report == Py_None) {
/* default is "end" only */
- target->end_event_obj = PyUnicode_FromString("end");
+ self->end_event_obj = PyUnicode_FromString("end");
Py_RETURN_NONE;
}
@@ -4339,32 +4288,53 @@ _elementtree_XMLParser__setevents_impl(XMLParserObject *self,
Py_DECREF(events_seq);
return NULL;
}
+
+ /* the target must implement the method of the event,
+ except for the namespace events */
+ PyObject *handler = Py_None;
+ if (strcmp(event_name, "start") == 0) {
+ handler = self->handle_start;
+ } else if (strcmp(event_name, "end") == 0) {
+ handler = self->handle_end;
+ } else if (strcmp(event_name, "comment") == 0) {
+ handler = self->handle_comment;
+ } else if (strcmp(event_name, "pi") == 0) {
+ handler = self->handle_pi;
+ }
+ if (handler == NULL) {
+ PyErr_Format(PyExc_TypeError,
+ "the target does not support %R events",
+ event_name_obj);
+ Py_DECREF(events_seq);
+ return NULL;
+ }
+
if (strcmp(event_name, "start") == 0) {
- Py_XSETREF(target->start_event_obj, Py_NewRef(event_name_obj));
+ Py_XSETREF(self->start_event_obj, Py_NewRef(event_name_obj));
} else if (strcmp(event_name, "end") == 0) {
- Py_XSETREF(target->end_event_obj, Py_NewRef(event_name_obj));
+ Py_XSETREF(self->end_event_obj, Py_NewRef(event_name_obj));
} else if (strcmp(event_name, "start-ns") == 0) {
- Py_XSETREF(target->start_ns_event_obj, Py_NewRef(event_name_obj));
+ Py_XSETREF(self->start_ns_event_obj, Py_NewRef(event_name_obj));
EXPAT(st, SetNamespaceDeclHandler)(
self->parser,
(XML_StartNamespaceDeclHandler) expat_start_ns_handler,
(XML_EndNamespaceDeclHandler) expat_end_ns_handler
);
} else if (strcmp(event_name, "end-ns") == 0) {
- Py_XSETREF(target->end_ns_event_obj, Py_NewRef(event_name_obj));
+ Py_XSETREF(self->end_ns_event_obj, Py_NewRef(event_name_obj));
EXPAT(st, SetNamespaceDeclHandler)(
self->parser,
(XML_StartNamespaceDeclHandler) expat_start_ns_handler,
(XML_EndNamespaceDeclHandler) expat_end_ns_handler
);
} else if (strcmp(event_name, "comment") == 0) {
- Py_XSETREF(target->comment_event_obj, Py_NewRef(event_name_obj));
+ Py_XSETREF(self->comment_event_obj, Py_NewRef(event_name_obj));
EXPAT(st, SetCommentHandler)(
self->parser,
(XML_CommentHandler) expat_comment_handler
);
} else if (strcmp(event_name, "pi") == 0) {
- Py_XSETREF(target->pi_event_obj, Py_NewRef(event_name_obj));
+ Py_XSETREF(self->pi_event_obj, Py_NewRef(event_name_obj));
EXPAT(st, SetProcessingInstructionHandler)(
self->parser,
(XML_ProcessingInstructionHandler) expat_pi_handler
diff --git a/Modules/_testcapi/marshal.c b/Modules/_testcapi/marshal.c
new file mode 100644
index 000000000000000..fe5b8259b885788
--- /dev/null
+++ b/Modules/_testcapi/marshal.c
@@ -0,0 +1,172 @@
+// Test PyMarshal C API
+
+#include "parts.h"
+#include "marshal.h" // PyMarshal_WriteLongToFile()
+
+static PyObject*
+pymarshal_write_long_to_file(PyObject* self, PyObject *args)
+{
+ long value;
+ PyObject *filename;
+ int version;
+ FILE *fp;
+
+ if (!PyArg_ParseTuple(args, "lOi:pymarshal_write_long_to_file",
+ &value, &filename, &version))
+ return NULL;
+
+ fp = Py_fopen(filename, "wb");
+ if (fp == NULL) {
+ return NULL;
+ }
+
+ PyMarshal_WriteLongToFile(value, fp, version);
+
+ fclose(fp);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject*
+pymarshal_write_object_to_file(PyObject* self, PyObject *args)
+{
+ PyObject *obj;
+ PyObject *filename;
+ int version;
+ FILE *fp;
+
+ if (!PyArg_ParseTuple(args, "OOi:pymarshal_write_object_to_file",
+ &obj, &filename, &version))
+ return NULL;
+
+ fp = Py_fopen(filename, "wb");
+ if (fp == NULL) {
+ return NULL;
+ }
+
+ PyMarshal_WriteObjectToFile(obj, fp, version);
+
+ fclose(fp);
+ if (PyErr_Occurred()) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject*
+pymarshal_read_short_from_file(PyObject* self, PyObject *args)
+{
+ int value;
+ long pos;
+ PyObject *filename;
+ FILE *fp;
+
+ if (!PyArg_ParseTuple(args, "O:pymarshal_read_short_from_file", &filename))
+ return NULL;
+
+ fp = Py_fopen(filename, "rb");
+ if (fp == NULL) {
+ return NULL;
+ }
+
+ value = PyMarshal_ReadShortFromFile(fp);
+ pos = ftell(fp);
+
+ fclose(fp);
+ if (PyErr_Occurred())
+ return NULL;
+ return Py_BuildValue("il", value, pos);
+}
+
+static PyObject*
+pymarshal_read_long_from_file(PyObject* self, PyObject *args)
+{
+ long value, pos;
+ PyObject *filename;
+ FILE *fp;
+
+ if (!PyArg_ParseTuple(args, "O:pymarshal_read_long_from_file", &filename))
+ return NULL;
+
+ fp = Py_fopen(filename, "rb");
+ if (fp == NULL) {
+ return NULL;
+ }
+
+ value = PyMarshal_ReadLongFromFile(fp);
+ pos = ftell(fp);
+
+ fclose(fp);
+ if (PyErr_Occurred())
+ return NULL;
+ return Py_BuildValue("ll", value, pos);
+}
+
+static PyObject*
+pymarshal_read_last_object_from_file(PyObject* self, PyObject *args)
+{
+ PyObject *filename;
+ if (!PyArg_ParseTuple(args, "O:pymarshal_read_last_object_from_file", &filename))
+ return NULL;
+
+ FILE *fp = Py_fopen(filename, "rb");
+ if (fp == NULL) {
+ return NULL;
+ }
+
+ PyObject *obj = PyMarshal_ReadLastObjectFromFile(fp);
+ long pos = ftell(fp);
+
+ fclose(fp);
+ if (obj == NULL) {
+ return NULL;
+ }
+ return Py_BuildValue("Nl", obj, pos);
+}
+
+static PyObject*
+pymarshal_read_object_from_file(PyObject* self, PyObject *args)
+{
+ PyObject *filename;
+ if (!PyArg_ParseTuple(args, "O:pymarshal_read_object_from_file", &filename))
+ return NULL;
+
+ FILE *fp = Py_fopen(filename, "rb");
+ if (fp == NULL) {
+ return NULL;
+ }
+
+ PyObject *obj = PyMarshal_ReadObjectFromFile(fp);
+ long pos = ftell(fp);
+
+ fclose(fp);
+ if (obj == NULL) {
+ return NULL;
+ }
+ return Py_BuildValue("Nl", obj, pos);
+}
+
+
+static PyMethodDef test_methods[] = {
+ {"pymarshal_write_long_to_file",
+ pymarshal_write_long_to_file, METH_VARARGS},
+ {"pymarshal_write_object_to_file",
+ pymarshal_write_object_to_file, METH_VARARGS},
+ {"pymarshal_read_short_from_file",
+ pymarshal_read_short_from_file, METH_VARARGS},
+ {"pymarshal_read_long_from_file",
+ pymarshal_read_long_from_file, METH_VARARGS},
+ {"pymarshal_read_last_object_from_file",
+ pymarshal_read_last_object_from_file, METH_VARARGS},
+ {"pymarshal_read_object_from_file",
+ pymarshal_read_object_from_file, METH_VARARGS},
+ {NULL},
+};
+
+int
+_PyTestCapi_Init_Marshal(PyObject *mod)
+{
+ return PyModule_AddFunctions(mod, test_methods);
+}
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index 98b5dd47accde35..1ae3f0773e42f80 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -68,5 +68,6 @@ int _PyTestCapi_Init_Type(PyObject *mod);
int _PyTestCapi_Init_Function(PyObject *mod);
int _PyTestCapi_Init_Module(PyObject *mod);
int _PyTestCapi_Init_Weakref(PyObject *mod);
+int _PyTestCapi_Init_Marshal(PyObject *mod);
#endif // Py_TESTCAPI_PARTS_H
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 0312ee9066231c4..eb769294fd21db8 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -13,7 +13,6 @@
#include "_testcapi/parts.h"
#include "frameobject.h" // PyFrame_New()
-#include "marshal.h" // PyMarshal_WriteLongToFile()
#ifdef bool
# error "The public headers should not include , see gh-90904"
@@ -1416,153 +1415,6 @@ join_temporary_c_thread(PyObject *self, PyObject *Py_UNUSED(ignored))
Py_RETURN_NONE;
}
-/* marshal */
-
-static PyObject*
-pymarshal_write_long_to_file(PyObject* self, PyObject *args)
-{
- long value;
- PyObject *filename;
- int version;
- FILE *fp;
-
- if (!PyArg_ParseTuple(args, "lOi:pymarshal_write_long_to_file",
- &value, &filename, &version))
- return NULL;
-
- fp = Py_fopen(filename, "wb");
- if (fp == NULL) {
- return NULL;
- }
-
- PyMarshal_WriteLongToFile(value, fp, version);
-
- fclose(fp);
- if (PyErr_Occurred()) {
- return NULL;
- }
- Py_RETURN_NONE;
-}
-
-static PyObject*
-pymarshal_write_object_to_file(PyObject* self, PyObject *args)
-{
- PyObject *obj;
- PyObject *filename;
- int version;
- FILE *fp;
-
- if (!PyArg_ParseTuple(args, "OOi:pymarshal_write_object_to_file",
- &obj, &filename, &version))
- return NULL;
-
- fp = Py_fopen(filename, "wb");
- if (fp == NULL) {
- return NULL;
- }
-
- PyMarshal_WriteObjectToFile(obj, fp, version);
-
- fclose(fp);
- if (PyErr_Occurred()) {
- return NULL;
- }
- Py_RETURN_NONE;
-}
-
-static PyObject*
-pymarshal_read_short_from_file(PyObject* self, PyObject *args)
-{
- int value;
- long pos;
- PyObject *filename;
- FILE *fp;
-
- if (!PyArg_ParseTuple(args, "O:pymarshal_read_short_from_file", &filename))
- return NULL;
-
- fp = Py_fopen(filename, "rb");
- if (fp == NULL) {
- return NULL;
- }
-
- value = PyMarshal_ReadShortFromFile(fp);
- pos = ftell(fp);
-
- fclose(fp);
- if (PyErr_Occurred())
- return NULL;
- return Py_BuildValue("il", value, pos);
-}
-
-static PyObject*
-pymarshal_read_long_from_file(PyObject* self, PyObject *args)
-{
- long value, pos;
- PyObject *filename;
- FILE *fp;
-
- if (!PyArg_ParseTuple(args, "O:pymarshal_read_long_from_file", &filename))
- return NULL;
-
- fp = Py_fopen(filename, "rb");
- if (fp == NULL) {
- return NULL;
- }
-
- value = PyMarshal_ReadLongFromFile(fp);
- pos = ftell(fp);
-
- fclose(fp);
- if (PyErr_Occurred())
- return NULL;
- return Py_BuildValue("ll", value, pos);
-}
-
-static PyObject*
-pymarshal_read_last_object_from_file(PyObject* self, PyObject *args)
-{
- PyObject *filename;
- if (!PyArg_ParseTuple(args, "O:pymarshal_read_last_object_from_file", &filename))
- return NULL;
-
- FILE *fp = Py_fopen(filename, "rb");
- if (fp == NULL) {
- return NULL;
- }
-
- PyObject *obj = PyMarshal_ReadLastObjectFromFile(fp);
- long pos = ftell(fp);
-
- fclose(fp);
- if (obj == NULL) {
- return NULL;
- }
- return Py_BuildValue("Nl", obj, pos);
-}
-
-static PyObject*
-pymarshal_read_object_from_file(PyObject* self, PyObject *args)
-{
- PyObject *filename;
- if (!PyArg_ParseTuple(args, "O:pymarshal_read_object_from_file", &filename))
- return NULL;
-
- FILE *fp = Py_fopen(filename, "rb");
- if (fp == NULL) {
- return NULL;
- }
-
- PyObject *obj = PyMarshal_ReadObjectFromFile(fp);
- long pos = ftell(fp);
-
- fclose(fp);
- if (obj == NULL) {
- return NULL;
- }
- return Py_BuildValue("Nl", obj, pos);
-}
-
static PyObject*
return_null_without_error(PyObject *self, PyObject *args)
{
@@ -3077,18 +2929,6 @@ static PyMethodDef TestMethods[] = {
{"call_in_temporary_c_thread", call_in_temporary_c_thread, METH_VARARGS,
PyDoc_STR("set_error_class(error_class) -> None")},
{"join_temporary_c_thread", join_temporary_c_thread, METH_NOARGS},
- {"pymarshal_write_long_to_file",
- pymarshal_write_long_to_file, METH_VARARGS},
- {"pymarshal_write_object_to_file",
- pymarshal_write_object_to_file, METH_VARARGS},
- {"pymarshal_read_short_from_file",
- pymarshal_read_short_from_file, METH_VARARGS},
- {"pymarshal_read_long_from_file",
- pymarshal_read_long_from_file, METH_VARARGS},
- {"pymarshal_read_last_object_from_file",
- pymarshal_read_last_object_from_file, METH_VARARGS},
- {"pymarshal_read_object_from_file",
- pymarshal_read_object_from_file, METH_VARARGS},
{"return_null_without_error", return_null_without_error, METH_NOARGS},
{"return_result_with_error", return_result_with_error, METH_NOARGS},
{"getitem_with_error", getitem_with_error, METH_VARARGS},
@@ -3974,7 +3814,9 @@ _testcapi_exec(PyObject *m)
if (_PyTestCapi_Init_Weakref(m) < 0) {
return -1;
}
-
+ if (_PyTestCapi_Init_Marshal(m) < 0) {
+ return -1;
+ }
return 0;
}
diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c
index 9cbacbd14f86a8a..8b1a547ff297d11 100644
--- a/Modules/_testclinic.c
+++ b/Modules/_testclinic.c
@@ -1523,6 +1523,40 @@ clone_with_conv_f2_impl(PyObject *module, custom_t path)
}
+/*[clinic input]
+alias_pos
+
+ a: object = None
+ *
+ b as a: object = None
+
+[clinic start generated code]*/
+
+static PyObject *
+alias_pos_impl(PyObject *module, PyObject *a)
+/*[clinic end generated code: output=f6cd3c7f098a894d input=8018ee6c26e3f435]*/
+{
+ return Py_NewRef(a);
+}
+
+
+/*[clinic input]
+alias_kwonly
+
+ *
+ a: object = None
+ b as a: object = None
+
+[clinic start generated code]*/
+
+static PyObject *
+alias_kwonly_impl(PyObject *module, PyObject *a)
+/*[clinic end generated code: output=9a6d4202ba972f46 input=8ad2d6c0f326571d]*/
+{
+ return Py_NewRef(a);
+}
+
+
/*[clinic input]
class _testclinic.TestClass "PyObject *" "&PyBaseObject_Type"
[clinic start generated code]*/
@@ -2399,6 +2433,40 @@ depr_kwd_multi_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
}
+/*[clinic input]
+depr_alias
+ a: object = None
+ *
+ [until 3.14] b as a: object = None
+[clinic start generated code]*/
+
+static PyObject *
+depr_alias_impl(PyObject *module, PyObject *a)
+/*[clinic end generated code: output=85e89838716d9423 input=92efd3f244c2ec3f]*/
+{
+ return Py_NewRef(a);
+}
+
+
+/*[clinic input]
+depr_param
+ a: object = None
+ [until 3.14] b: object = None
+ [until 3.14] c: object = None
+ /
+ *
+ [until 3.14] d: object = None
+[clinic start generated code]*/
+
+static PyObject *
+depr_param_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
+ PyObject *d)
+/*[clinic end generated code: output=5a42b461851c467b input=f689a85166408359]*/
+{
+ return pack_arguments_newref(4, a, b, c, d);
+}
+
+
/*[clinic input]
depr_multi
a: object
@@ -2736,6 +2804,9 @@ static PyMethodDef tester_methods[] = {
CLONE_WITH_CONV_F1_METHODDEF
CLONE_WITH_CONV_F2_METHODDEF
+ ALIAS_POS_METHODDEF
+ ALIAS_KWONLY_METHODDEF
+
DEPR_STAR_POS0_LEN1_METHODDEF
DEPR_STAR_POS0_LEN2_METHODDEF
DEPR_STAR_POS0_LEN3_WITH_KWD_METHODDEF
@@ -2756,6 +2827,8 @@ static PyMethodDef tester_methods[] = {
DEPR_KWD_NOINLINE_METHODDEF
DEPR_KWD_MULTI_METHODDEF
DEPR_MULTI_METHODDEF
+ DEPR_ALIAS_METHODDEF
+ DEPR_PARAM_METHODDEF
LONE_KWDS_METHODDEF
KWDS_WITH_POS_ONLY_METHODDEF
diff --git a/Modules/clinic/_testclinic.c.h b/Modules/clinic/_testclinic.c.h
index 9eee8c15fdedf9c..c3bf217a9e7b7b7 100644
--- a/Modules/clinic/_testclinic.c.h
+++ b/Modules/clinic/_testclinic.c.h
@@ -4259,6 +4259,158 @@ clone_with_conv_f2(PyObject *module, PyObject *const *args, Py_ssize_t nargs, Py
return return_value;
}
+PyDoc_STRVAR(alias_pos__doc__,
+"alias_pos($module, /, a=None)\n"
+"--\n"
+"\n");
+
+#define ALIAS_POS_METHODDEF \
+ {"alias_pos", _PyCFunction_CAST(alias_pos), METH_FASTCALL|METH_KEYWORDS, alias_pos__doc__},
+
+static PyObject *
+alias_pos_impl(PyObject *module, PyObject *a);
+
+static PyObject *
+alias_pos(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 2
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ Py_hash_t ob_hash;
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_hash = -1,
+ .ob_item = { _Py_LATIN1_CHR('a'), _Py_LATIN1_CHR('b'), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"a", "b", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "alias_pos",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
+ PyObject *a = Py_None;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (args[0]) {
+ a = args[0];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+skip_optional_pos:
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ if (args[0]) {
+ PyErr_Format(PyExc_TypeError,
+ "argument for alias_pos() given by "
+ "name ('b') and %s", 0 < nargs ? "position (1)" : "name ('a')");
+ goto exit;
+ }
+ a = args[1];
+skip_optional_kwonly:
+ return_value = alias_pos_impl(module, a);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(alias_kwonly__doc__,
+"alias_kwonly($module, /, *, a=None)\n"
+"--\n"
+"\n");
+
+#define ALIAS_KWONLY_METHODDEF \
+ {"alias_kwonly", _PyCFunction_CAST(alias_kwonly), METH_FASTCALL|METH_KEYWORDS, alias_kwonly__doc__},
+
+static PyObject *
+alias_kwonly_impl(PyObject *module, PyObject *a);
+
+static PyObject *
+alias_kwonly(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 2
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ Py_hash_t ob_hash;
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_hash = -1,
+ .ob_item = { _Py_LATIN1_CHR('a'), _Py_LATIN1_CHR('b'), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"a", "b", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "alias_kwonly",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
+ PyObject *a = Py_None;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 0, /*maxpos*/ 0, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ if (args[0]) {
+ a = args[0];
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
+ }
+ if (args[0]) {
+ PyErr_Format(PyExc_TypeError,
+ "argument for alias_kwonly() given by "
+ "name ('b') and name ('a')");
+ goto exit;
+ }
+ a = args[1];
+skip_optional_kwonly:
+ return_value = alias_kwonly_impl(module, a);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(_testclinic_TestClass_get_defining_class__doc__,
"get_defining_class($self, /)\n"
"--\n"
@@ -5197,4 +5349,4 @@ vc_kwonly_vectorcall(PyObject *type, PyObject *const *args,
kwnames ? PyTuple_GET_SIZE(kwnames) : 0,
NULL, kwnames);
}
-/*[clinic end generated code: output=10fcd30a5d85ce11 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=8a219f606f1296ac input=a9049054013a1b77]*/
diff --git a/Modules/clinic/_testclinic_depr.c.h b/Modules/clinic/_testclinic_depr.c.h
index 35f0394e2d3da14..1b0195185329a2a 100644
--- a/Modules/clinic/_testclinic_depr.c.h
+++ b/Modules/clinic/_testclinic_depr.c.h
@@ -2366,6 +2366,212 @@ depr_kwd_multi(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObje
return return_value;
}
+PyDoc_STRVAR(depr_alias__doc__,
+"depr_alias($module, /, a=None)\n"
+"--\n"
+"\n");
+
+#define DEPR_ALIAS_METHODDEF \
+ {"depr_alias", _PyCFunction_CAST(depr_alias), METH_FASTCALL|METH_KEYWORDS, depr_alias__doc__},
+
+static PyObject *
+depr_alias_impl(PyObject *module, PyObject *a);
+
+// Emit compiler warnings when we get to Python 3.14.
+#if PY_VERSION_HEX >= 0x030e00C0
+# error "Update the clinic input of 'depr_alias'."
+#elif PY_VERSION_HEX >= 0x030e00A0
+# ifdef _MSC_VER
+# pragma message ("Update the clinic input of 'depr_alias'.")
+# else
+# warning "Update the clinic input of 'depr_alias'."
+# endif
+#endif
+
+static PyObject *
+depr_alias(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 2
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ Py_hash_t ob_hash;
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_hash = -1,
+ .ob_item = { _Py_LATIN1_CHR('a'), _Py_LATIN1_CHR('b'), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"a", "b", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "depr_alias",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[2];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
+ PyObject *a = Py_None;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 0, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (!noptargs) {
+ goto skip_optional_pos;
+ }
+ if (args[0]) {
+ a = args[0];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
+skip_optional_pos:
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ if (args[0]) {
+ PyErr_Format(PyExc_TypeError,
+ "argument for depr_alias() given by "
+ "name ('b') and %s", 0 < nargs ? "position (1)" : "name ('a')");
+ goto exit;
+ }
+ if (PyErr_WarnEx(PyExc_DeprecationWarning,
+ "Passing the argument 'b' to depr_alias() is deprecated. Use 'a' "
+ "instead. It will be removed in Python 3.14.", 1))
+ {
+ goto exit;
+ }
+ a = args[1];
+skip_optional_kwonly:
+ return_value = depr_alias_impl(module, a);
+
+exit:
+ return return_value;
+}
+
+PyDoc_STRVAR(depr_param__doc__,
+"depr_param($module, a=None, b=None, c=None, /, *, d=None)\n"
+"--\n"
+"\n");
+
+#define DEPR_PARAM_METHODDEF \
+ {"depr_param", _PyCFunction_CAST(depr_param), METH_FASTCALL|METH_KEYWORDS, depr_param__doc__},
+
+static PyObject *
+depr_param_impl(PyObject *module, PyObject *a, PyObject *b, PyObject *c,
+ PyObject *d);
+
+// Emit compiler warnings when we get to Python 3.14.
+#if PY_VERSION_HEX >= 0x030e00C0
+# error "Update the clinic input of 'depr_param'."
+#elif PY_VERSION_HEX >= 0x030e00A0
+# ifdef _MSC_VER
+# pragma message ("Update the clinic input of 'depr_param'.")
+# else
+# warning "Update the clinic input of 'depr_param'."
+# endif
+#endif
+
+static PyObject *
+depr_param(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
+
+ #define NUM_KEYWORDS 1
+ static struct {
+ PyGC_Head _this_is_not_used;
+ PyObject_VAR_HEAD
+ Py_hash_t ob_hash;
+ PyObject *ob_item[NUM_KEYWORDS];
+ } _kwtuple = {
+ .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
+ .ob_hash = -1,
+ .ob_item = { _Py_LATIN1_CHR('d'), },
+ };
+ #undef NUM_KEYWORDS
+ #define KWTUPLE (&_kwtuple.ob_base.ob_base)
+
+ #else // !Py_BUILD_CORE
+ # define KWTUPLE NULL
+ #endif // !Py_BUILD_CORE
+
+ static const char * const _keywords[] = {"", "", "", "d", NULL};
+ static _PyArg_Parser _parser = {
+ .keywords = _keywords,
+ .fname = "depr_param",
+ .kwtuple = KWTUPLE,
+ };
+ #undef KWTUPLE
+ PyObject *argsbuf[4];
+ Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0;
+ PyObject *a = Py_None;
+ PyObject *b = Py_None;
+ PyObject *c = Py_None;
+ PyObject *d = Py_None;
+
+ args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
+ /*minpos*/ 0, /*maxpos*/ 3, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
+ if (!args) {
+ goto exit;
+ }
+ if (nargs < 1) {
+ goto skip_optional_posonly;
+ }
+ noptargs--;
+ a = args[0];
+ if (nargs < 2) {
+ goto skip_optional_posonly;
+ }
+ noptargs--;
+ if (PyErr_WarnEx(PyExc_DeprecationWarning,
+ "Passing the argument 'b' to depr_param() is deprecated. It will "
+ "be removed in Python 3.14.", 1))
+ {
+ goto exit;
+ }
+ b = args[1];
+ if (nargs < 3) {
+ goto skip_optional_posonly;
+ }
+ noptargs--;
+ if (PyErr_WarnEx(PyExc_DeprecationWarning,
+ "Passing the argument 'c' to depr_param() is deprecated. It will "
+ "be removed in Python 3.14.", 1))
+ {
+ goto exit;
+ }
+ c = args[2];
+skip_optional_posonly:
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ if (PyErr_WarnEx(PyExc_DeprecationWarning,
+ "Passing the argument 'd' to depr_param() is deprecated. It will "
+ "be removed in Python 3.14.", 1))
+ {
+ goto exit;
+ }
+ d = args[3];
+skip_optional_kwonly:
+ return_value = depr_param_impl(module, a, b, c, d);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(depr_multi__doc__,
"depr_multi($module, a, /, b, c, d, e, f, *, g)\n"
"--\n"
@@ -2475,4 +2681,4 @@ depr_multi(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *
exit:
return return_value;
}
-/*[clinic end generated code: output=9429e9340f69c4b7 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=858abe8a5a885725 input=a9049054013a1b77]*/
diff --git a/PC/pyconfig.h b/PC/pyconfig.h
index 982d4c662599a44..5b1df0fc146c39d 100644
--- a/PC/pyconfig.h
+++ b/PC/pyconfig.h
@@ -148,17 +148,24 @@ WIN32 is still required for the locale module.
#define MS_WIN64
#endif
+#ifdef __clang__
+#define _Py_CLANG_COMPILER(platform) ( \
+ "[Clang " _Py_STRINGIZE(__clang_major__) "." _Py_STRINGIZE(__clang_minor__) \
+ "." _Py_STRINGIZE(__clang_patchlevel__) " " platform \
+ " with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]")
+#endif
+
/* set the _Py_COMPILER and support tier
*
* win_amd64 MSVC (x86_64-pc-windows-msvc): 1
* win32 MSVC (i686-pc-windows-msvc): 1
* win_arm64 MSVC (aarch64-pc-windows-msvc): 2
- * other archs and ICC: 0
+ * other archs and clang-cl/ICC: 0
*/
#ifdef MS_WIN64
#if defined(_M_X64) || defined(_M_AMD64)
#if defined(__clang__)
-#define _Py_COMPILER ("[Clang " __clang_version__ "] 64 bit (AMD64) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]")
+#define _Py_COMPILER _Py_CLANG_COMPILER("64 bit (AMD64)")
#define PY_SUPPORT_TIER 0
#elif defined(__INTEL_COMPILER)
#define _Py_COMPILER ("[ICC v." _Py_STRINGIZE(__INTEL_COMPILER) " 64 bit (amd64) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]")
@@ -169,8 +176,13 @@ WIN32 is still required for the locale module.
#endif /* __clang__ */
#define PYD_PLATFORM_TAG "win_amd64"
#elif defined(_M_ARM64)
+#if defined(__clang__)
+#define _Py_COMPILER _Py_CLANG_COMPILER("64 bit (ARM64)")
+#define PY_SUPPORT_TIER 0
+#else
#define _Py_COMPILER _Py_PASTE_VERSION("64 bit (ARM64)")
#define PY_SUPPORT_TIER 2
+#endif /* __clang__ */
#define PYD_PLATFORM_TAG "win_arm64"
#else
#define _Py_COMPILER _Py_PASTE_VERSION("64 bit (Unknown)")
@@ -220,7 +232,7 @@ typedef _W64 int Py_ssize_t;
#if defined(MS_WIN32) && !defined(MS_WIN64)
#if defined(_M_IX86)
#if defined(__clang__)
-#define _Py_COMPILER ("[Clang " __clang_version__ "] 32 bit (Intel) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]")
+#define _Py_COMPILER _Py_CLANG_COMPILER("32 bit (Intel)")
#define PY_SUPPORT_TIER 0
#elif defined(__INTEL_COMPILER)
#define _Py_COMPILER ("[ICC v." _Py_STRINGIZE(__INTEL_COMPILER) " 32 bit (Intel) with MSC v." _Py_STRINGIZE(_MSC_VER) " CRT]")
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index 64e50b67be46561..d856b70bbdd5792 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -134,6 +134,7 @@
+
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index a3b62e1df663e00..554e5f3075f7ebf 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -135,6 +135,9 @@
Source Files
+
+ Source Files
+
diff --git a/Python/marshal.c b/Python/marshal.c
index 1897d700c055bd3..420c3ee115a7377 100644
--- a/Python/marshal.c
+++ b/Python/marshal.c
@@ -1388,16 +1388,12 @@ r_object(RFILE *p)
}
break;
}
- v = PyBytes_FromStringAndSize((char *)NULL, n);
- if (v == NULL)
- break;
ptr = r_string(n, p);
if (ptr == NULL) {
- Py_DECREF(v);
break;
}
- memcpy(PyBytes_AS_STRING(v), ptr, n);
- retval = v;
+ // Get a singleton for 1-byte string
+ retval = PyBytes_FromStringAndSize(ptr, n); // can be NULL
R_REF(retval);
break;
}
diff --git a/Tools/clinic/libclinic/clanguage.py b/Tools/clinic/libclinic/clanguage.py
index 3747e752f8a7608..ff6749ad1fe1736 100644
--- a/Tools/clinic/libclinic/clanguage.py
+++ b/Tools/clinic/libclinic/clanguage.py
@@ -91,7 +91,8 @@ def compiler_deprecated_warning(
) -> str | None:
minversion: VersionTuple | None = None
for p in parameters:
- for version in p.deprecated_positional, p.deprecated_keyword:
+ for version in (p.deprecated_positional, p.deprecated_keyword,
+ p.deprecated_until):
if version and (not minversion or minversion > version):
minversion = version
if not minversion:
diff --git a/Tools/clinic/libclinic/converter.py b/Tools/clinic/libclinic/converter.py
index 29cbad4d5a84c42..a21478678a478c1 100644
--- a/Tools/clinic/libclinic/converter.py
+++ b/Tools/clinic/libclinic/converter.py
@@ -281,11 +281,18 @@ def converter_init(self) -> None:
def c_default_init(self) -> None:
return
+ # An alternative name of a preceding parameter: they share
+ # the same C variable.
+ alias_of: Parameter | None = None
+
def is_optional(self) -> bool:
return (self.default is not unspecified)
def _render_self(self, parameter: Parameter, data: CRenderData) -> None:
self.parameter = parameter
+ if self.alias_of is not None:
+ # Everything is rendered for the aliased parameter.
+ return
name = self.parser_name
# impl_arguments
@@ -307,6 +314,13 @@ def _render_non_self(
self.parameter = parameter
name = self.name
+ if self.alias_of is not None:
+ # Only the keyword is new, the rest is rendered for the
+ # aliased parameter.
+ data.keywords.append(parameter.name)
+ data.format_units.append(self.format_unit)
+ return
+
# declarations
d = self.declaration(in_parser=True)
data.declarations.append(d)
diff --git a/Tools/clinic/libclinic/dsl_parser.py b/Tools/clinic/libclinic/dsl_parser.py
index 0202d9d3daf8875..bbb7939c2e15757 100644
--- a/Tools/clinic/libclinic/dsl_parser.py
+++ b/Tools/clinic/libclinic/dsl_parser.py
@@ -251,6 +251,7 @@ class DSLParser:
positional_only: bool
deprecated_positional: VersionTuple | None
deprecated_keyword: VersionTuple | None
+ deprecated_until: VersionTuple | None
group_stack: list[int]
group_count: int
parameter_state: ParamState
@@ -266,6 +267,7 @@ class DSLParser:
# Line of the file which is being parsed.
line_number: int | None
from_version_re = re.compile(r'([*/]) +\[from +(.+)\]')
+ until_version_re = re.compile(r'\[until +(.+?)\] +(.+)')
permit_long_summary = False
permit_long_docstring_body = False
@@ -295,6 +297,7 @@ def reset(self) -> None:
self.positional_only = False
self.deprecated_positional = None
self.deprecated_keyword = None
+ self.deprecated_until = None
self.group_stack = []
self.group_count = 0
self.parameter_state: ParamState = ParamState.START
@@ -922,6 +925,12 @@ def state_parameter(self, line: str) -> None:
line = match[1]
version = self.parse_version(match[2])
+ self.deprecated_until = None
+ match = self.until_version_re.fullmatch(line)
+ if match:
+ self.deprecated_until = self.parse_version(match[1], 'until')
+ line = match[2]
+
func = self.function
match line:
case '*':
@@ -1171,6 +1180,7 @@ def bad_node(self, node: ast.AST) -> None:
p = Parameter(parameter_name, kind, function=self.function,
converter=converter, default=value,
+ deprecated_until=self.deprecated_until,
group=self.group_stack[-1] if self.group_stack else 0,
group_depth=len(self.group_stack),
deprecated_positional=self.deprecated_positional,
@@ -1182,6 +1192,26 @@ def bad_node(self, node: ast.AST) -> None:
elif names and parameter_name == names[0] and c_name is None:
fail(f"Parameter {parameter_name!r} requires a custom C name")
+ # A parameter which shares the C variable of a preceding parameter
+ # is an alternative name (an alias) of it.
+ for existing in self.function.parameters.values():
+ if existing.converter.name == converter.name:
+ if not self.keyword_only:
+ fail(f"Alias {parameter_name!r} of the parameter "
+ f"{existing.name!r} must be keyword-only.")
+ if value is unspecified:
+ fail(f"Alias {parameter_name!r} of the parameter "
+ f"{existing.name!r} must have a default value.")
+ converter.alias_of = existing
+ break
+
+ # A deprecated parameter is going away, so calls which do not pass
+ # it must already be valid.
+ if self.deprecated_until is not None and value is unspecified:
+ fail(f"Deprecated parameter {parameter_name!r} "
+ f"must have a default value.")
+
+
key = f"{parameter_name}_as_{c_name}" if c_name else parameter_name
self.function.parameters[key] = p
@@ -1211,17 +1241,18 @@ def parse_converter(
"Annotations must be either a name, a function call, or a string."
)
- def parse_version(self, thenceforth: str) -> VersionTuple:
- """Parse Python version in `[from ...]` marker."""
+ def parse_version(self, version: str, marker: str = 'from') -> VersionTuple:
+ """Parse Python version in `[from ...]` or `[until ...]` marker."""
assert isinstance(self.function, Function)
try:
- major, minor = thenceforth.split(".")
+ major, minor = version.split(".")
return int(major), int(minor)
except ValueError:
fail(
- f"Function {self.function.name!r}: expected format '[from major.minor]' "
- f"where 'major' and 'minor' are integers; got {thenceforth!r}"
+ f"Function {self.function.name!r}: expected format "
+ f"'[{marker} major.minor]' where 'major' and 'minor' are "
+ f"integers; got {version!r}"
)
def parse_star(self, function: Function, version: VersionTuple | None) -> None:
@@ -1343,12 +1374,23 @@ def parse_slash(self, function: Function, version: VersionTuple | None) -> None:
fail(f"Function {function.name!r} has an unsupported group configuration. "
f"(Unexpected state {self.parameter_state}.d)")
# fixup preceding parameters
+ deprecated = None
for p in function.parameters.values():
if p.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD:
if version is None:
p.kind = inspect.Parameter.POSITIONAL_ONLY
elif p.deprecated_keyword is None:
p.deprecated_keyword = version
+ if p.kind is inspect.Parameter.POSITIONAL_ONLY:
+ # A positional-only argument can only be passed after all
+ # preceding ones, so removing a parameter would leave no
+ # way to pass those which follow it.
+ if p.deprecated_until is not None:
+ deprecated = p
+ elif deprecated is not None:
+ fail(f"Parameter {p.name!r} cannot follow the deprecated "
+ f"parameter {deprecated.name!r}: only the last "
+ f"positional-only parameters can be deprecated.")
def state_parameter_docstring_start(self, line: str) -> None:
assert self.indent.margin is not None, "self.margin.infer() has not yet been called to set the margin"
@@ -1503,7 +1545,10 @@ def docstring_line(index: int) -> int | None:
lines.insert(0, '{signature}')
# finalize docstring
- params = f.render_parameters
+ # An alias is not shown in the signature: only one of the
+ # alternative names can be used in a call.
+ params = [p for p in f.render_parameters
+ if p.converter.alias_of is None]
parameters = self.format_docstring_parameters(params)
signature = self.format_docstring_signature(f, params)
docstring = "\n".join(lines)
diff --git a/Tools/clinic/libclinic/function.py b/Tools/clinic/libclinic/function.py
index d61cc136b7fefac..83d929fcc9207ab 100644
--- a/Tools/clinic/libclinic/function.py
+++ b/Tools/clinic/libclinic/function.py
@@ -240,6 +240,8 @@ class Parameter:
# (`None` signifies that there is no deprecation)
deprecated_positional: VersionTuple | None = None
deprecated_keyword: VersionTuple | None = None
+ # The release in which the parameter will be removed.
+ deprecated_until: VersionTuple | None = None
# Line of the file on which the parameter is declared.
line_number: int | None = None
right_bracket_count: int = dc.field(init=False, default=0)
diff --git a/Tools/clinic/libclinic/parse_args.py b/Tools/clinic/libclinic/parse_args.py
index 4aa159010e8296a..4b6b54bc4febf3a 100644
--- a/Tools/clinic/libclinic/parse_args.py
+++ b/Tools/clinic/libclinic/parse_args.py
@@ -351,6 +351,9 @@ def __init__(self, func: Function, codegen: CodeGen) -> None:
self.max_pos = 0
self.min_kw_only = 0
for i, p in enumerate(self.parameters, 1):
+ if p.converter.alias_of is not None:
+ # An alias fills the slot of the parameter which it aliases.
+ continue
if p.is_keyword_only():
assert not p.is_positional_only()
if not p.is_optional():
@@ -891,6 +894,8 @@ def _parse_positional_args(
f"Using converter {p.converter} is not supported "
f"in function with var-positional parameter")
return None
+ if p.deprecated_until is not None:
+ parsearg = self.render_deprecated(p, parsearg)
if i >= self.min_pos:
# p and everything after it is optional.
parser_code.append(libclinic.normalize_snippet(f"""
@@ -932,8 +937,7 @@ def render_parse_all_arguments(self) -> str:
Fall back to the tuple convention if the stack one cannot be used.
"""
- for p in self.parameters:
- p.converter.use_converter()
+ self.use_converters()
if self.limited_capi:
# _PyArg_ParseStack() is not part of the limited C API.
self.fastcall = False
@@ -1045,6 +1049,71 @@ def parse_var_keyword(self) -> None:
parser_code.append(libclinic.normalize_snippet(self._parse_kwarg(), indent=4))
self.parser_body(*parser_code)
+ def use_converters(self) -> None:
+ """Prepare for parsing all arguments by a single call.
+
+ Such call leaves nowhere to put the code checking a particular
+ argument.
+ """
+ for p in self.parameters:
+ if p.converter.alias_of is not None:
+ fail(f"Parameter {p.name!r} cannot be an alias: "
+ f"the arguments are not parsed one by one.")
+ if p.deprecated_until is not None:
+ fail(f"Parameter {p.name!r} cannot be deprecated: "
+ f"the arguments are not parsed one by one.")
+ p.converter.use_converter()
+
+ def render_alias(self, p: Parameter, argname_fmt: str,
+ parsearg: str) -> str:
+ """Prepend the code checking that the alias is not in conflict.
+
+ Only one of the alternative names can be used in a call.
+ """
+ aliased = p.converter.alias_of
+ assert aliased is not None
+ i = self.parameters.index(aliased)
+ other = f"name ('{aliased.name}')"
+ arg = ''
+ if i < self.max_pos:
+ # The other name can be used for a positional argument too.
+ arg = f', {i} < nargs ? "position ({i + 1})" : "{other}"'
+ other = '%s'
+ return '\n'.join([
+ libclinic.normalize_snippet(f"""
+ if ({argname_fmt % i}) {{{{
+ PyErr_Format(PyExc_TypeError,
+ "argument for {self.func.name}() given by "
+ "name ('{p.name}') and {other}"{arg});
+ goto exit;
+ }}}}
+ """),
+ libclinic.normalize_snippet(parsearg),
+ ])
+
+ def render_deprecated(self, p: Parameter, parsearg: str) -> str:
+ """Prepend the code warning that the parameter is going away."""
+ assert p.deprecated_until is not None
+ major, minor = p.deprecated_until
+ aliased = p.converter.alias_of
+ instead = "" if aliased is None else f"Use {aliased.name!r} instead. "
+ message = (f"Passing the argument {p.name!r} to "
+ f"{self.func.fulldisplayname}() is deprecated. {instead}"
+ f"It will be removed in Python {major}.{minor}.")
+ code = [
+ libclinic.normalize_snippet("""
+ if (PyErr_WarnEx(PyExc_DeprecationWarning,
+ {}, 1))
+ {{{{
+ goto exit;
+ }}}}
+ """.format(
+ libclinic.wrapped_c_string_literal(
+ message, width=64, subsequent_indent=24))),
+ libclinic.normalize_snippet(parsearg),
+ ]
+ return '\n'.join(code)
+
def parse_general(self, clang: CLanguage) -> None:
deprecated_positionals: dict[int, Parameter] = {}
deprecated_keywords: dict[int, Parameter] = {}
@@ -1142,6 +1211,13 @@ def parse_general(self, clang: CLanguage) -> None:
"parameter (after clang)")
displayname = p.get_displayname(i+1)
parsearg = p.converter.parse_arg(argname_fmt % i, displayname, limited_capi=self.limited_capi)
+ if parsearg is not None:
+ # The conflict is reported before warning about the
+ # deprecated name which caused it.
+ if p.deprecated_until is not None:
+ parsearg = self.render_deprecated(p, parsearg)
+ if p.converter.alias_of is not None:
+ parsearg = self.render_alias(p, argname_fmt, parsearg)
if parsearg is None:
parser_code = []
use_parser_code = False
@@ -1198,8 +1274,7 @@ def parse_general(self, clang: CLanguage) -> None:
if self.varpos:
parser_code.append(libclinic.normalize_snippet(self._parse_vararg(), indent=4))
else:
- for parameter in self.parameters:
- parameter.converter.use_converter()
+ self.use_converters()
self.declarations = declare_parser(self.func, codegen=self.codegen,
hasformat=True)
diff --git a/Tools/unicode/makeunicodedata.py b/Tools/unicode/makeunicodedata.py
index edb5775eeb1bb66..c38bcfc25492aca 100644
--- a/Tools/unicode/makeunicodedata.py
+++ b/Tools/unicode/makeunicodedata.py
@@ -46,7 +46,7 @@
# The Unicode Database
# --------------------
# When changing UCD version please update
-# * Doc/library/stdtypes.rst (four occurrences)
+# * Doc/builtins/stdtypes.rst (four occurrences)
# * Doc/library/unicodedata.rst
# * Doc/library/re.rst
# * Doc/reference/lexical_analysis.rst (three occurrences)