Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-xmltodict for
openSUSE:Factory checked in at 2026-02-27 17:02:35
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-xmltodict (Old)
and /work/SRC/openSUSE:Factory/.python-xmltodict.new.29461 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-xmltodict"
Fri Feb 27 17:02:35 2026 rev:15 rq:1335011 version:1.0.4
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-xmltodict/python-xmltodict.changes
2026-02-18 17:05:45.110527894 +0100
+++
/work/SRC/openSUSE:Factory/.python-xmltodict.new.29461/python-xmltodict.changes
2026-02-27 17:04:49.507477088 +0100
@@ -1,0 +2,8 @@
+Tue Feb 24 19:25:56 UTC 2026 - Martin Hauke <[email protected]>
+
+- Update to version 1.0.4
+ Bug Fixes
+ * unparse: add bytes_errors policy and handle bytes scalars
+ consistently (ed70434).
+
+-------------------------------------------------------------------
Old:
----
xmltodict-1.0.3.tar.gz
New:
----
xmltodict-1.0.4.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-xmltodict.spec ++++++
--- /var/tmp/diff_new_pack.4hoCp2/_old 2026-02-27 17:04:50.711526908 +0100
+++ /var/tmp/diff_new_pack.4hoCp2/_new 2026-02-27 17:04:50.711526908 +0100
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-xmltodict
-Version: 1.0.3
+Version: 1.0.4
Release: 0
Summary: Module to make XML working resemble JSON
License: MIT
++++++ xmltodict-1.0.3.tar.gz -> xmltodict-1.0.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xmltodict-1.0.3/PKG-INFO new/xmltodict-1.0.4/PKG-INFO
--- old/xmltodict-1.0.3/PKG-INFO 2026-02-15 05:04:50.649439600 +0100
+++ new/xmltodict-1.0.4/PKG-INFO 2026-02-22 03:21:09.137860300 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: xmltodict
-Version: 1.0.3
+Version: 1.0.4
Summary: Makes working with XML feel like you are working with JSON
Author: Martin Blech
License-Expression: MIT
@@ -255,6 +255,7 @@
- `input_dict`: Dictionary to convert to XML.
- `output=None`: File-like object to write XML to; returns string if None.
- `encoding='utf-8'`: Encoding of the output XML.
+- `bytes_errors='replace'`: Error handler used when decoding byte values
during unparse (for example `'replace'`, `'strict'`, `'ignore'`).
- `full_document=True`: Include XML declaration if True.
- `short_empty_elements=False`: Use short tags for empty elements (`<tag/>`).
- `attr_prefix='@'`: Prefix for dictionary keys representing attributes.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xmltodict-1.0.3/README.md
new/xmltodict-1.0.4/README.md
--- old/xmltodict-1.0.3/README.md 2026-02-15 05:04:43.000000000 +0100
+++ new/xmltodict-1.0.4/README.md 2026-02-22 03:21:02.000000000 +0100
@@ -228,6 +228,7 @@
- `input_dict`: Dictionary to convert to XML.
- `output=None`: File-like object to write XML to; returns string if None.
- `encoding='utf-8'`: Encoding of the output XML.
+- `bytes_errors='replace'`: Error handler used when decoding byte values
during unparse (for example `'replace'`, `'strict'`, `'ignore'`).
- `full_document=True`: Include XML declaration if True.
- `short_empty_elements=False`: Use short tags for empty elements (`<tag/>`).
- `attr_prefix='@'`: Prefix for dictionary keys representing attributes.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xmltodict-1.0.3/pyproject.toml
new/xmltodict-1.0.4/pyproject.toml
--- old/xmltodict-1.0.3/pyproject.toml 2026-02-15 05:04:43.000000000 +0100
+++ new/xmltodict-1.0.4/pyproject.toml 2026-02-22 03:21:02.000000000 +0100
@@ -4,7 +4,7 @@
[project]
name = "xmltodict"
-version = "1.0.3"
+version = "1.0.4"
description = "Makes working with XML feel like you are working with JSON"
readme = "README.md"
requires-python = ">=3.9"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xmltodict-1.0.3/tests/test_dicttoxml.py
new/xmltodict-1.0.4/tests/test_dicttoxml.py
--- old/xmltodict-1.0.3/tests/test_dicttoxml.py 2026-02-15 05:04:43.000000000
+0100
+++ new/xmltodict-1.0.4/tests/test_dicttoxml.py 2026-02-22 03:21:02.000000000
+0100
@@ -178,6 +178,29 @@
assert xml == "<!--t1--><!--t2--><a>1</a>"
+def test_unparse_with_bytes_comment_uses_output_encoding():
+ obj = {"#comment": b"caf\xe9", "a": "1"}
+ xml = _strip(unparse(obj, full_document=True, encoding="iso-8859-1"))
+ assert xml == "<!--caf\xe9--><a>1</a>"
+
+
+def test_unparse_invalid_bytes_comment_replaced_by_default():
+ obj = {"#comment": b"\xff", "a": "1"}
+ xml = _strip(unparse(obj, full_document=True, encoding="utf-8"))
+ assert xml == "<!--\ufffd--><a>1</a>"
+
+
+def test_unparse_rejects_invalid_bytes_comment_for_encoding_with_strict():
+ obj = {"#comment": b"\xff", "a": "1"}
+ with pytest.raises(UnicodeDecodeError, match="'utf-8' codec can't decode
byte 0xff"):
+ unparse(
+ obj,
+ full_document=True,
+ encoding="utf-8",
+ bytes_errors="strict",
+ )
+
+
def test_unparse_rejects_comment_with_double_hyphen():
obj = {"#comment": "bad--comment", "a": "1"}
with pytest.raises(ValueError, match="cannot contain '--'"):
@@ -298,6 +321,39 @@
assert xml == expected_xml
+def test_xmlns_values_use_consistent_boolean_coercion():
+ xml = unparse({"root": {"@xmlns": {"a": True}}}, full_document=False)
+ assert xml == '<root xmlns:a="true"></root>'
+
+
+def test_xmlns_values_decode_bytes_with_output_encoding():
+ xml = unparse(
+ {"root": {"@xmlns": {"a": b"http://ex\xe9.com/"}}},
+ full_document=False,
+ encoding="iso-8859-1",
+ )
+ assert xml == '<root xmlns:a="http://ex\xe9.com/"></root>'
+
+
+def test_xmlns_values_replace_invalid_bytes_by_default():
+ xml = unparse(
+ {"root": {"@xmlns": {"a": b"\xff"}}},
+ full_document=False,
+ encoding="utf-8",
+ )
+ assert xml == '<root xmlns:a="\ufffd"></root>'
+
+
+def test_xmlns_values_reject_invalid_bytes_with_strict():
+ with pytest.raises(UnicodeDecodeError, match="'utf-8' codec can't decode
byte 0xff"):
+ unparse(
+ {"root": {"@xmlns": {"a": b"\xff"}}},
+ full_document=False,
+ encoding="utf-8",
+ bytes_errors="strict",
+ )
+
+
def test_boolean_unparse():
expected_xml = '<?xml version="1.0" encoding="utf-8"?>\n<x>true</x>'
xml = unparse(dict(x=True))
@@ -307,6 +363,56 @@
xml = unparse(dict(x=False))
assert xml == expected_xml
+ expected_xml = '<?xml version="1.0" encoding="utf-8"?>\n<x
attr="true"></x>'
+ xml = unparse({'x': {'@attr': True}})
+ assert xml == expected_xml
+
+ expected_xml = '<?xml version="1.0" encoding="utf-8"?>\n<x
attr="false"></x>'
+ xml = unparse({'x': {'@attr': False}})
+ assert xml == expected_xml
+
+
+def test_unparse_bytes_in_attributes_and_cdata_use_output_encoding():
+ xml = unparse({"x": {"@attr": b"caf\xe9", "#text": b"caf\xe9"}},
full_document=False, encoding="iso-8859-1")
+ assert xml == '<x attr="caf\xe9">caf\xe9</x>'
+
+
+def test_unparse_bytes_text_node_uses_output_encoding():
+ xml = unparse({"x": b"caf\xe9"}, full_document=False,
encoding="iso-8859-1")
+ assert xml == "<x>caf\xe9</x>"
+
+
+def test_unparse_bytes_text_node_with_expand_iter_uses_output_encoding():
+ xml = unparse({"x": b"caf\xe9"}, full_document=False,
encoding="iso-8859-1", expand_iter="item")
+ assert xml == "<x>caf\xe9</x>"
+
+
+def test_unparse_invalid_bytes_in_attributes_and_cdata_replaced_by_default():
+ xml = unparse({"x": {"@attr": b"\xff", "#text": b"\xff"}},
full_document=False, encoding="utf-8")
+ assert xml == '<x attr="\ufffd">\ufffd</x>'
+
+
+def test_unparse_invalid_bytes_text_node_replaced_by_default():
+ xml = unparse({"x": b"\xff"}, full_document=False, encoding="utf-8")
+ assert xml == "<x>\ufffd</x>"
+
+
+def
test_unparse_rejects_invalid_bytes_in_attributes_and_cdata_for_encoding_with_strict():
+ with pytest.raises(UnicodeDecodeError, match="'utf-8' codec can't decode
byte 0xff"):
+ unparse({"x": {"@attr": b"\xff"}}, full_document=False,
encoding="utf-8", bytes_errors="strict")
+ with pytest.raises(UnicodeDecodeError, match="'utf-8' codec can't decode
byte 0xff"):
+ unparse({"x": {"#text": b"\xff"}}, full_document=False,
encoding="utf-8", bytes_errors="strict")
+
+
+def test_unparse_rejects_invalid_bytes_text_node_for_encoding_with_strict():
+ with pytest.raises(UnicodeDecodeError, match="'utf-8' codec can't decode
byte 0xff"):
+ unparse({"x": b"\xff"}, full_document=False, encoding="utf-8",
bytes_errors="strict")
+
+
+def test_unparse_rejects_invalid_bytes_errors_handler():
+ with pytest.raises(ValueError, match="Invalid bytes_errors handler: nope"):
+ unparse({"x": {"@attr": b"\xff"}}, full_document=False,
bytes_errors="nope")
+
def test_rejects_tag_name_with_angle_brackets():
# Minimal guard: disallow '<' or '>' to prevent breaking tag context
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xmltodict-1.0.3/xmltodict.egg-info/PKG-INFO
new/xmltodict-1.0.4/xmltodict.egg-info/PKG-INFO
--- old/xmltodict-1.0.3/xmltodict.egg-info/PKG-INFO 2026-02-15
05:04:50.000000000 +0100
+++ new/xmltodict-1.0.4/xmltodict.egg-info/PKG-INFO 2026-02-22
03:21:09.000000000 +0100
@@ -1,6 +1,6 @@
Metadata-Version: 2.4
Name: xmltodict
-Version: 1.0.3
+Version: 1.0.4
Summary: Makes working with XML feel like you are working with JSON
Author: Martin Blech
License-Expression: MIT
@@ -255,6 +255,7 @@
- `input_dict`: Dictionary to convert to XML.
- `output=None`: File-like object to write XML to; returns string if None.
- `encoding='utf-8'`: Encoding of the output XML.
+- `bytes_errors='replace'`: Error handler used when decoding byte values
during unparse (for example `'replace'`, `'strict'`, `'ignore'`).
- `full_document=True`: Include XML declaration if True.
- `short_empty_elements=False`: Use short tags for empty elements (`<tag/>`).
- `attr_prefix='@'`: Prefix for dictionary keys representing attributes.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/xmltodict-1.0.3/xmltodict.py
new/xmltodict-1.0.4/xmltodict.py
--- old/xmltodict-1.0.3/xmltodict.py 2026-02-15 05:04:43.000000000 +0100
+++ new/xmltodict-1.0.4/xmltodict.py 2026-02-22 03:21:02.000000000 +0100
@@ -6,6 +6,7 @@
from xml.sax.xmlreader import AttributesImpl
from io import StringIO
from inspect import isgenerator
+import codecs
class ParsingInterrupted(Exception):
pass
@@ -369,15 +370,17 @@
return handler.item
-def _convert_value_to_string(value):
+def _convert_value_to_string(value, encoding='utf-8', bytes_errors='replace'):
"""Convert a value to its string representation for XML output.
Handles boolean values consistently by converting them to lowercase.
"""
- if isinstance(value, (str, bytes)):
+ if isinstance(value, str):
return value
if isinstance(value, bool):
return "true" if value else "false"
+ if isinstance(value, (bytes, bytearray, memoryview)):
+ return bytes(value).decode(encoding, errors=bytes_errors)
return str(value)
@@ -448,6 +451,8 @@
namespaces=None,
full_document=True,
expand_iter=None,
+ encoding='utf-8',
+ bytes_errors='replace',
comment_key='#comment'):
if isinstance(key, str) and key == comment_key:
comments_list = value if isinstance(value, list) else [value]
@@ -456,7 +461,9 @@
for comment_text in comments_list:
if comment_text is None:
continue
- comment_text = _convert_value_to_string(comment_text)
+ comment_text = _convert_value_to_string(
+ comment_text, encoding=encoding, bytes_errors=bytes_errors
+ )
if not comment_text:
continue
if pretty:
@@ -474,7 +481,7 @@
key, value = result
# Minimal validation to avoid breaking out of tag context
_validate_name(key, "element")
- if not hasattr(value, '__iter__') or isinstance(value, (str, dict)):
+ if not hasattr(value, '__iter__') or isinstance(value, (str, bytes,
bytearray, memoryview, dict)):
value = [value]
for index, v in enumerate(value):
if full_document and depth == 0 and index > 0:
@@ -482,10 +489,10 @@
if v is None:
v = {}
elif not isinstance(v, (dict, str)):
- if expand_iter and hasattr(v, '__iter__'):
+ if expand_iter and hasattr(v, '__iter__') and not isinstance(v,
(bytes, bytearray, memoryview)):
v = {expand_iter: v}
else:
- v = _convert_value_to_string(v)
+ v = _convert_value_to_string(v, encoding=encoding,
bytes_errors=bytes_errors)
if isinstance(v, str):
v = {cdata_key: v}
cdata = None
@@ -496,7 +503,7 @@
if iv is None:
cdata = None
else:
- cdata = _convert_value_to_string(iv)
+ cdata = _convert_value_to_string(iv, encoding=encoding,
bytes_errors=bytes_errors)
continue
if isinstance(ik, str) and ik.startswith(attr_prefix):
ik = _process_namespace(ik, namespaces, namespace_separator,
@@ -505,12 +512,14 @@
for k, v in iv.items():
_validate_name(k, "attribute")
attr = 'xmlns{}'.format(f':{k}' if k else '')
- attrs[attr] = '' if v is None else str(v)
+ attrs[attr] = '' if v is None else
_convert_value_to_string(
+ v, encoding=encoding, bytes_errors=bytes_errors
+ )
continue
if iv is None:
iv = ''
elif not isinstance(iv, str):
- iv = str(iv)
+ iv = _convert_value_to_string(iv, encoding=encoding,
bytes_errors=bytes_errors)
attr_name = ik[len(attr_prefix) :]
_validate_name(attr_name, "attribute")
attrs[attr_name] = iv
@@ -530,7 +539,8 @@
attr_prefix, cdata_key, depth+1, preprocessor,
pretty, newl, indent, namespaces=namespaces,
namespace_separator=namespace_separator,
- expand_iter=expand_iter, comment_key=comment_key)
+ expand_iter=expand_iter, encoding=encoding,
+ bytes_errors=bytes_errors, comment_key=comment_key)
if cdata is not None:
content_handler.characters(cdata)
if pretty and children:
@@ -565,8 +575,16 @@
The `pretty` parameter (default=`False`) enables pretty-printing. In this
mode, lines are terminated with `'\n'` and indented with `'\t'`, but this
can be customized with the `newl` and `indent` parameters.
+ The `bytes_errors` parameter controls decoding errors for byte values and
+ defaults to `'replace'`.
"""
+ bytes_errors = kwargs.pop('bytes_errors', 'replace')
+ try:
+ codecs.lookup_error(bytes_errors)
+ except LookupError as exc:
+ raise ValueError(f"Invalid bytes_errors handler: {bytes_errors}") from
exc
+
must_return = False
if output is None:
output = StringIO()
@@ -581,7 +599,16 @@
for key, value in input_dict.items():
if key != comment_key and full_document and seen_root:
raise ValueError("Document must have exactly one root.")
- _emit(key, value, content_handler, full_document=full_document,
comment_key=comment_key, **kwargs)
+ _emit(
+ key,
+ value,
+ content_handler,
+ full_document=full_document,
+ encoding=encoding,
+ bytes_errors=bytes_errors,
+ comment_key=comment_key,
+ **kwargs,
+ )
if key != comment_key:
seen_root = True
if full_document and not seen_root: