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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions Doc/library/xml.dom.rst
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ Node Objects

All of the components of an XML document are subclasses of :class:`Node`.

.. versionchanged:: next
Setting a read-only attribute is deprecated.
It now emits a :exc:`DeprecationWarning`
and will raise :exc:`NoModificationAllowedErr` in a future version of Python.


.. attribute:: Node.nodeType

Expand Down Expand Up @@ -304,8 +309,9 @@ All of the components of an XML document are subclasses of :class:`Node`.

.. attribute:: Node.prefix

The part of the :attr:`tagName` preceding the colon if there is one, else the
empty string. The value is a string, or ``None``.
The part of the :attr:`Element.tagName` preceding the colon if there is one,
else the empty string. The value is a string, or ``None``.
This is a read-only attribute.


.. attribute:: Node.namespaceURI
Expand Down Expand Up @@ -455,12 +461,14 @@ following attributes:

The public identifier for the external subset of the document type definition.
This will be a string or ``None``.
This is a read-only attribute.


.. attribute:: DocumentType.systemId

The system identifier for the external subset of the document type definition.
This will be a URI as a string, or ``None``.
This is a read-only attribute.


.. attribute:: DocumentType.internalSubset
Expand All @@ -474,6 +482,7 @@ following attributes:

The name of the root element as given in the ``DOCTYPE`` declaration, if
present.
This is a read-only attribute.


.. attribute:: DocumentType.entities
Expand Down Expand Up @@ -587,6 +596,7 @@ of that class.

The element type name. In a namespace-using document it may have colons in it.
The value is a string.
This is a read-only attribute.


.. method:: Element.getElementsByTagName(tagName)
Expand Down Expand Up @@ -690,6 +700,7 @@ Attr Objects

The attribute name.
In a namespace-using document it may include a colon.
This is a read-only attribute.


.. attribute:: Attr.localName
Expand All @@ -703,6 +714,7 @@ Attr Objects

The part of the name preceding the colon if there is one, else the
empty string.
This is a read-only attribute.


.. attribute:: Attr.value
Expand Down
12 changes: 12 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,18 @@ New deprecations
open them one by one instead.
(Contributed by Serhiy Storchaka in :gh:`152638`.)

* :mod:`xml.dom.minidom`:

* Setting a read-only attribute of a node is deprecated.
It now emits a :exc:`DeprecationWarning`
and will raise :exc:`xml.dom.NoModificationAllowedErr` in the future.
This affects the :attr:`!nodeType`, :attr:`!nodeName`, :attr:`!name`,
:attr:`!tagName`, :attr:`!target`, :attr:`!prefix`, :attr:`!namespaceURI`,
:attr:`!publicId` and :attr:`!systemId` attributes.
Use ``Document.renameNode()`` to rename an element
or an attribute.
(Contributed by Serhiy Storchaka in :gh:`57336`.)

.. Add deprecations above alphabetically, not here at the end.

.. include:: ../deprecations/pending-removal-in-3.17.rst
Expand Down
37 changes: 36 additions & 1 deletion Lib/test/test_minidom.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pickle
import io
from test import support
from test.support import warnings_helper
import unittest

import xml.dom.minidom
Expand Down Expand Up @@ -777,7 +778,9 @@ def _setupCloneElement(self, deep):
self._testCloneElementCopiesAttributes(
root, clone, "testCloneElement" + (deep and "Deep" or "Shallow"))
# mutilate the original so shared data is detected
root.tagName = root.nodeName = "MODIFIED"
with warnings_helper.check_warnings(
('', DeprecationWarning), quiet=True):
root.tagName = root.nodeName = "MODIFIED"
root.setAttribute("attr", "NEW VALUE")
root.setAttribute("added", "VALUE")
return dom, clone
Expand Down Expand Up @@ -1325,6 +1328,38 @@ def checkRenameNodeSharedConstraints(self, doc, node):
self.assertRaises(xml.dom.WrongDocumentErr, doc2.renameNode, node,
xml.dom.EMPTY_NAMESPACE, "foo")

def test_readonly_attributes(self):
# These attributes are read-only in the DOM, and setting them
# is deprecated (gh-57336).
doc = parseString('<!DOCTYPE doc PUBLIC "p" "s">'
'<doc a="v">text<!--c--><?pi d?><![CDATA[x]]></doc>')
elem = doc.documentElement
attr = elem.attributes["a"]
text, comment, pi, cdata = elem.childNodes
for node, name in [
(doc, "nodeType"), (doc, "nodeName"),
(doc.doctype, "name"), (doc.doctype, "nodeName"),
(doc.doctype, "publicId"), (doc.doctype, "systemId"),
(elem, "nodeType"),
(elem, "tagName"), (elem, "nodeName"),
(elem, "prefix"), (elem, "namespaceURI"),
(attr, "name"), (attr, "nodeName"),
(attr, "prefix"), (attr, "namespaceURI"),
(text, "nodeName"), (comment, "nodeName"), (cdata, "nodeName"),
(pi, "nodeName"), (pi, "target"),
]:
with self.subTest(node=type(node).__name__, name=name):
value = getattr(node, name)
with self.assertWarns(DeprecationWarning):
setattr(node, name, value)
self.assertEqual(getattr(node, name), value)

# These are writable.
attr.value = "other"
text.data = "other"
self.assertEqual(attr.value, "other")
self.assertEqual(text.data, "other")

def testRenameAttribute(self):
doc = parseString("<doc a='v'/>")
elem = doc.documentElement
Expand Down
22 changes: 21 additions & 1 deletion Lib/xml/dom/minicompat.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
# defproperty() should be used for each version of
# the relevant _get_<property>() function.

__all__ = ["NodeList", "EmptyNodeList", "StringTypes", "defproperty"]
__all__ = ["NodeList", "EmptyNodeList", "StringTypes", "defproperty",
"defdeprecatedproperty"]

import xml.dom

Expand Down Expand Up @@ -107,3 +108,22 @@ def set(self, value, name=name):
"expected not to find _set_" + name
prop = property(get, set, doc=doc)
setattr(klass, name, prop)


def defdeprecatedproperty(klass, name, doc, private=None):
"""Define a read-only attribute whose setter is deprecated.

The value is stored in the *private* attribute ("_" + name by default).
Setting the attribute still works, but emits a DeprecationWarning.
"""
if private is None:
private = "_" + name
def get(self, private=private):
return getattr(self, private)
def set(self, value, name=name, private=private):
import warnings
warnings.warn(f"attempt to modify read-only attribute {name!r} "
f"is deprecated", DeprecationWarning, stacklevel=2)
setattr(self, private, value)
prop = property(get, set, doc=doc)
setattr(klass, name, prop)
Loading
Loading