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:

Reply via email to