Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-ruamel.yaml for 
openSUSE:Factory checked in at 2023-06-04 00:11:44
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-ruamel.yaml (Old)
 and      /work/SRC/openSUSE:Factory/.python-ruamel.yaml.new.15902 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-ruamel.yaml"

Sun Jun  4 00:11:44 2023 rev:34 rq:1090264 version:0.17.31

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-ruamel.yaml/python-ruamel.yaml.changes    
2023-05-19 11:54:51.295034300 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-ruamel.yaml.new.15902/python-ruamel.yaml.changes
 2023-06-04 00:11:45.537231422 +0200
@@ -1,0 +2,27 @@
+Thu Jun  1 05:46:10 UTC 2023 - Johannes Kastl <[email protected]>
+
+- update to 0.17.31:
+  * added tag.setter on `ScalarEvent` and on `Node`, that takes
+    either a `Tag` instance, or a str (reported by Sorin Sbarnea)
+- update to 0.17.30:
+  * fix issue 467, caused by Tag instances not being hashable
+    (reported by Douglas Raillard)
+- update to 0.17.29:
+  * changed the internals of the tag property from a string to a
+    class which allows for preservation of the original handle and
+    suffix. This should result in better results using documents
+    with %TAG directives, as well as preserving URI escapes in tag
+    suffixes.
+- update to 0.17.28:
+  * fix for issue 464: documents ending with document end marker
+    without final newline fail to load (reported by Mariusz
+    Rusiniak)
+- update to 0.17.27:
+  * fix issue with inline mappings as value for merge keys
+    (reported by Sirish)
+  * fix for 468, error inserting after accessing merge attribute on
+    ``CommentedMap`` (reported by Bastien gerard)
+  * fix for issue 461 pop + insert on same `CommentedMap` key
+    throwing error (reported by `John Thorvald Wodder II)
+
+-------------------------------------------------------------------

Old:
----
  ruamel.yaml-0.17.26.tar.gz

New:
----
  ruamel.yaml-0.17.31.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-ruamel.yaml.spec ++++++
--- /var/tmp/diff_new_pack.iJFKEp/_old  2023-06-04 00:11:46.153235104 +0200
+++ /var/tmp/diff_new_pack.iJFKEp/_new  2023-06-04 00:11:46.157235129 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-ruamel.yaml
-Version:        0.17.26
+Version:        0.17.31
 Release:        0
 Summary:        Python YAML parser
 License:        MIT

++++++ ruamel.yaml-0.17.26.tar.gz -> ruamel.yaml-0.17.31.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/CHANGES 
new/ruamel.yaml-0.17.31/CHANGES
--- old/ruamel.yaml-0.17.26/CHANGES     2023-05-09 21:59:31.000000000 +0200
+++ new/ruamel.yaml-0.17.31/CHANGES     2023-05-31 07:56:28.000000000 +0200
@@ -1,3 +1,31 @@
+[0, 17, 31]: 2023-05-31
+  - added tag.setter on `ScalarEvent` and on `Node`, that takes either 
+    a `Tag` instance, or a str 
+    (reported by `Sorin Sbarnea 
<https://sourceforge.net/u/ssbarnea/profile/>`__)
+
+[0, 17, 30]: 2023-05-30
+  - fix issue 467, caused by Tag instances not being hashable (reported by
+    `Douglas Raillard
+    <https://bitbucket.org/%7Bcf052d92-a278-4339-9aa8-de41923bb556%7D/>`__)
+
+[0, 17, 29]: 2023-05-30
+  - changed the internals of the tag property from a string to a class which 
allows
+    for preservation of the original handle and suffix. This should
+    result in better results using documents with %TAG directives, as well
+    as preserving URI escapes in tag suffixes.
+
+[0, 17, 28]: 2023-05-26
+  - fix for issue 464: documents ending with document end marker without final 
newline
+    fail to load (reported by `Mariusz Rusiniak 
<https://sourceforge.net/u/r2dan/profile/>`__)
+
+[0, 17, 27]: 2023-05-25
+  - fix issue with inline mappings as value for merge keys
+    (reported by Sirish on `StackOverflow 
<https://stackoverflow.com/q/76331049/1307905>`__)
+  - fix for 468, error inserting after accessing merge attribute on 
``CommentedMap``
+    (reported by `Bastien gerard <https://sourceforge.net/u/bagerard/>`__)
+  - fix for issue 461 pop + insert on same `CommentedMap` key throwing error
+    (reported by `John Thorvald Wodder II 
<https://sourceforge.net/u/jwodder/profile/>`__) 
+
 [0, 17, 26]: 2023-05-09
   - Fix for error on edge cage for issue 459
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/PKG-INFO 
new/ruamel.yaml-0.17.31/PKG-INFO
--- old/ruamel.yaml-0.17.26/PKG-INFO    2023-05-09 22:00:46.626469100 +0200
+++ new/ruamel.yaml-0.17.31/PKG-INFO    2023-05-31 07:58:00.526932700 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: ruamel.yaml
-Version: 0.17.26
+Version: 0.17.31
 Summary: ruamel.yaml is a YAML parser/emitter that supports roundtrip 
preservation of comments, seq/map flow style, and map key order
 Home-page: https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree
 Author: Anthon van der Neut
@@ -34,8 +34,8 @@
 
 ``ruamel.yaml`` is a YAML 1.2 loader/dumper package for Python.
 
-:version:       0.17.26
-:updated:       2023-05-09
+:version:       0.17.31
+:updated:       2023-05-31
 :documentation: http://yaml.readthedocs.io
 :repository:    https://sourceforge.net/projects/ruamel-yaml/
 :pypi:          https://pypi.org/project/ruamel.yaml/
@@ -91,8 +91,36 @@
 
 .. should insert NEXT: at the beginning of line for next key (with empty line)
 
+0.17.31 (2023-05-31):
+  - added tag.setter on `ScalarEvent` and on `Node`, that takes either 
+    a `Tag` instance, or a str 
+    (reported by `Sorin Sbarnea 
<https://sourceforge.net/u/ssbarnea/profile/>`__)
+
+0.17.30 (2023-05-30):
+  - fix issue 467, caused by Tag instances not being hashable (reported by
+    `Douglas Raillard
+    <https://bitbucket.org/%7Bcf052d92-a278-4339-9aa8-de41923bb556%7D/>`__)
+
+0.17.29 (2023-05-30):
+  - changed the internals of the tag property from a string to a class which 
allows
+    for preservation of the original handle and suffix. This should
+    result in better results using documents with %TAG directives, as well
+    as preserving URI escapes in tag suffixes.
+
+0.17.28 (2023-05-26):
+  - fix for issue 464: documents ending with document end marker without final 
newline
+    fail to load (reported by `Mariusz Rusiniak 
<https://sourceforge.net/u/r2dan/profile/>`__)
+
+0.17.27 (2023-05-25):
+  - fix issue with inline mappings as value for merge keys
+    (reported by Sirish on `StackOverflow 
<https://stackoverflow.com/q/76331049/1307905>`__)
+  - fix for 468, error inserting after accessing merge attribute on 
``CommentedMap``
+    (reported by `Bastien gerard <https://sourceforge.net/u/bagerard/>`__)
+  - fix for issue 461 pop + insert on same `CommentedMap` key throwing error
+    (reported by `John Thorvald Wodder II 
<https://sourceforge.net/u/jwodder/profile/>`__) 
+
 0.17.26 (2023-05-09):
-  - Fix for error on edge cage for issue 459
+  - fix for error on edge cage for issue 459
 
 0.17.25 (2023-05-09):
   - fix for regression while dumping wrapped strings with too many backslashes 
removed
@@ -188,7 +216,7 @@
     attrs with `@attr.s()` (both reported by `ssph 
<https://sourceforge.net/u/sph/>`__)
 
 0.17.11 (2021-08-19):
-  - fix error baseclass for ``DuplicateKeyErorr`` (reported by `Łukasz 
Rogalski
+  - fix error baseclass for ``DuplicateKeyError`` (reported by `Łukasz 
Rogalski
     <https://sourceforge.net/u/lrogalski/>`__)
   - fix typo in reader error message, causing `KeyError` during reader error 
     (reported by `MTU <https://sourceforge.net/u/mtu/>`__)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/README.rst 
new/ruamel.yaml-0.17.31/README.rst
--- old/ruamel.yaml-0.17.26/README.rst  2023-05-09 21:59:31.000000000 +0200
+++ new/ruamel.yaml-0.17.31/README.rst  2023-05-31 07:56:28.000000000 +0200
@@ -4,8 +4,8 @@
 
 ``ruamel.yaml`` is a YAML 1.2 loader/dumper package for Python.
 
-:version:       0.17.26
-:updated:       2023-05-09
+:version:       0.17.31
+:updated:       2023-05-31
 :documentation: http://yaml.readthedocs.io
 :repository:    https://sourceforge.net/projects/ruamel-yaml/
 :pypi:          https://pypi.org/project/ruamel.yaml/
@@ -61,8 +61,36 @@
 
 .. should insert NEXT: at the beginning of line for next key (with empty line)
 
+0.17.31 (2023-05-31):
+  - added tag.setter on `ScalarEvent` and on `Node`, that takes either 
+    a `Tag` instance, or a str 
+    (reported by `Sorin Sbarnea 
<https://sourceforge.net/u/ssbarnea/profile/>`__)
+
+0.17.30 (2023-05-30):
+  - fix issue 467, caused by Tag instances not being hashable (reported by
+    `Douglas Raillard
+    <https://bitbucket.org/%7Bcf052d92-a278-4339-9aa8-de41923bb556%7D/>`__)
+
+0.17.29 (2023-05-30):
+  - changed the internals of the tag property from a string to a class which 
allows
+    for preservation of the original handle and suffix. This should
+    result in better results using documents with %TAG directives, as well
+    as preserving URI escapes in tag suffixes.
+
+0.17.28 (2023-05-26):
+  - fix for issue 464: documents ending with document end marker without final 
newline
+    fail to load (reported by `Mariusz Rusiniak 
<https://sourceforge.net/u/r2dan/profile/>`__)
+
+0.17.27 (2023-05-25):
+  - fix issue with inline mappings as value for merge keys
+    (reported by Sirish on `StackOverflow 
<https://stackoverflow.com/q/76331049/1307905>`__)
+  - fix for 468, error inserting after accessing merge attribute on 
``CommentedMap``
+    (reported by `Bastien gerard <https://sourceforge.net/u/bagerard/>`__)
+  - fix for issue 461 pop + insert on same `CommentedMap` key throwing error
+    (reported by `John Thorvald Wodder II 
<https://sourceforge.net/u/jwodder/profile/>`__) 
+
 0.17.26 (2023-05-09):
-  - Fix for error on edge cage for issue 459
+  - fix for error on edge cage for issue 459
 
 0.17.25 (2023-05-09):
   - fix for regression while dumping wrapped strings with too many backslashes 
removed
@@ -158,7 +186,7 @@
     attrs with `@attr.s()` (both reported by `ssph 
<https://sourceforge.net/u/sph/>`__)
 
 0.17.11 (2021-08-19):
-  - fix error baseclass for ``DuplicateKeyErorr`` (reported by `Łukasz 
Rogalski
+  - fix error baseclass for ``DuplicateKeyError`` (reported by `Łukasz 
Rogalski
     <https://sourceforge.net/u/lrogalski/>`__)
   - fix typo in reader error message, causing `KeyError` during reader error 
     (reported by `MTU <https://sourceforge.net/u/mtu/>`__)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/__init__.py 
new/ruamel.yaml-0.17.31/__init__.py
--- old/ruamel.yaml-0.17.26/__init__.py 2023-05-09 21:59:45.000000000 +0200
+++ new/ruamel.yaml-0.17.31/__init__.py 2023-05-31 07:56:46.000000000 +0200
@@ -5,9 +5,9 @@
 
 _package_data = dict(
     full_package_name='ruamel.yaml',
-    version_info=(0, 17, 26),
-    __version__='0.17.26',
-    version_timestamp='2023-05-09 21:59:45',
+    version_info=(0, 17, 31),
+    __version__='0.17.31',
+    version_timestamp='2023-05-31 07:56:46',
     author='Anthon van der Neut',
     author_email='[email protected]',
     description='ruamel.yaml is a YAML parser/emitter that supports roundtrip 
preservation of comments, seq/map flow style, and map key order',  # NOQA
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/comments.py 
new/ruamel.yaml-0.17.31/comments.py
--- old/ruamel.yaml-0.17.26/comments.py 2023-05-06 09:57:34.000000000 +0200
+++ new/ruamel.yaml-0.17.31/comments.py 2023-05-30 07:31:24.000000000 +0200
@@ -14,6 +14,7 @@
 from ruamel.yaml.compat import MutableSliceableSequence, nprintf  # NOQA
 from ruamel.yaml.scalarstring import ScalarString
 from ruamel.yaml.anchor import Anchor
+from ruamel.yaml.tag import Tag
 
 from collections.abc import MutableSet, Sized, Set, Mapping
 
@@ -79,7 +80,6 @@
 format_attrib = '_yaml_format'
 line_col_attrib = '_yaml_line_col'
 merge_attrib = '_yaml_merge'
-tag_attrib = '_yaml_tag'
 
 
 class Comment:
@@ -194,8 +194,8 @@
 
 
 # to distinguish key from None
-def NoComment() -> None:
-    pass
+class NotNone:
+    pass  # NOQA
 
 
 class Format:
@@ -264,19 +264,6 @@
         return f'LineCol({self.line}, {self.col})'
 
 
-class Tag:
-    """store tag information for roundtripping"""
-
-    __slots__ = ('value',)
-    attrib = tag_attrib
-
-    def __init__(self) -> None:
-        self.value = None
-
-    def __repr__(self) -> Any:
-        return f'{self.__class__.__name__}({self.value!r})'
-
-
 class CommentedBase:
     @property
     def ca(self):
@@ -380,7 +367,7 @@
         return getattr(self, Format.attrib)
 
     def yaml_add_eol_comment(
-        self, comment: Any, key: Optional[Any] = NoComment, column: 
Optional[Any] = None
+        self, comment: Any, key: Optional[Any] = NotNone, column: 
Optional[Any] = None
     ) -> None:
         """
         there is a problem as eol comments should start with ' #'
@@ -442,8 +429,8 @@
             setattr(self, Tag.attrib, Tag())
         return getattr(self, Tag.attrib)
 
-    def yaml_set_tag(self, value: Any) -> None:
-        self.tag.value = value
+    def yaml_set_ctag(self, value: Tag) -> None:
+        setattr(self, Tag.attrib, value)
 
     def copy_attributes(self, t: Any, memo: Any = None) -> None:
         # fmt: off
@@ -511,8 +498,8 @@
     def __eq__(self, other: Any) -> bool:
         return list.__eq__(self, other)
 
-    def _yaml_add_comment(self, comment: Any, key: Optional[Any] = NoComment) 
-> None:
-        if key is not NoComment:
+    def _yaml_add_comment(self, comment: Any, key: Optional[Any] = NotNone) -> 
None:
+        if key is not NotNone:
             self.yaml_key_comment_extend(key, comment)
         else:
             self.ca.comment = comment
@@ -593,8 +580,8 @@
 class CommentedKeySeq(tuple, CommentedBase):  # type: ignore
     """This primarily exists to be able to roundtrip keys that are sequences"""
 
-    def _yaml_add_comment(self, comment: Any, key: Optional[Any] = NoComment) 
-> None:
-        if key is not NoComment:
+    def _yaml_add_comment(self, comment: Any, key: Optional[Any] = NotNone) -> 
None:
+        if key is not NotNone:
             self.yaml_key_comment_extend(key, comment)
         else:
             self.ca.comment = comment
@@ -714,13 +701,13 @@
         ordereddict.__init__(self, *args, **kw)
 
     def _yaml_add_comment(
-        self, comment: Any, key: Optional[Any] = NoComment, value: 
Optional[Any] = NoComment
+        self, comment: Any, key: Optional[Any] = NotNone, value: Optional[Any] 
= NotNone
     ) -> None:
         """values is set to key to indicate a value attachment of comment"""
-        if key is not NoComment:
+        if key is not NotNone:
             self.yaml_key_comment_extend(key, comment)
             return
-        if value is not NoComment:
+        if value is not NotNone:
             self.yaml_value_comment_extend(value, comment)
         else:
             self.ca.comment = comment
@@ -799,8 +786,11 @@
         if key in self._ok:
             del self[key]
         keys = [k for k in self.keys() if k in self._ok]
-        ma0 = getattr(self, merge_attrib, [[-1]])[0]
-        merge_pos = ma0[0]
+        try:
+            ma0 = getattr(self, merge_attrib, [[-1]])[0]
+            merge_pos = ma0[0]
+        except IndexError:
+            merge_pos = -1
         if merge_pos >= 0:
             if merge_pos >= pos:
                 getattr(self, merge_attrib)[0] = (merge_pos + 1, ma0[1])
@@ -920,6 +910,16 @@
         for x in ordereddict.__iter__(self):
             yield x
 
+    def pop(self, key: Any, default: Any = NotNone) -> Any:
+        try:
+            result = self[key]
+        except KeyError:
+            if default is NotNone:
+                raise
+            return default
+        del self[key]
+        return result
+
     def _keys(self) -> Any:
         for x in ordereddict.__iter__(self):
             yield x
@@ -1030,8 +1030,8 @@
     def fromkeys(keys: Any, v: Any = None) -> Any:
         return CommentedKeyMap(dict.fromkeys(keys, v))
 
-    def _yaml_add_comment(self, comment: Any, key: Optional[Any] = NoComment) 
-> None:
-        if key is not NoComment:
+    def _yaml_add_comment(self, comment: Any, key: Optional[Any] = NotNone) -> 
None:
+        if key is not NotNone:
             self.yaml_key_comment_extend(key, comment)
         else:
             self.ca.comment = comment
@@ -1085,13 +1085,13 @@
             self |= values
 
     def _yaml_add_comment(
-        self, comment: Any, key: Optional[Any] = NoComment, value: 
Optional[Any] = NoComment
+        self, comment: Any, key: Optional[Any] = NotNone, value: Optional[Any] 
= NotNone
     ) -> None:
         """values is set to key to indicate a value attachment of comment"""
-        if key is not NoComment:
+        if key is not NotNone:
             self.yaml_key_comment_extend(key, comment)
             return
-        if value is not NoComment:
+        if value is not NotNone:
             self.yaml_value_comment_extend(value, comment)
         else:
             self.ca.comment = comment
@@ -1128,7 +1128,9 @@
         self.value = value
         self.style = style
         if tag is not None:
-            self.yaml_set_tag(tag)
+            if isinstance(tag, str):
+                tag = Tag(suffix=tag)
+            self.yaml_set_ctag(tag)
 
     def __str__(self) -> Any:
         return self.value
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/composer.py 
new/ruamel.yaml-0.17.31/composer.py
--- old/ruamel.yaml-0.17.26/composer.py 2023-05-02 06:22:32.000000000 +0200
+++ new/ruamel.yaml-0.17.31/composer.py 2023-05-28 08:45:22.000000000 +0200
@@ -32,6 +32,7 @@
         if self.loader is not None and getattr(self.loader, '_composer', None) 
is None:
             self.loader._composer = self
         self.anchors: Dict[Any, Any] = {}
+        self.warn_double_anchors = True
 
     @property
     def parser(self) -> Any:
@@ -111,7 +112,7 @@
         event = self.parser.peek_event()
         anchor = event.anchor
         if anchor is not None:  # have an anchor
-            if anchor in self.anchors:
+            if self.warn_double_anchors and anchor in self.anchors:
                 ws = (
                     f'\nfound duplicate anchor {anchor!r}\n'
                     f'first occurrence {self.anchors[anchor].start_mark}\n'
@@ -130,9 +131,11 @@
 
     def compose_scalar_node(self, anchor: Any) -> Any:
         event = self.parser.get_event()
-        tag = event.tag
-        if tag is None or tag == '!':
+        tag = event.ctag
+        if tag is None or str(tag) == '!':
             tag = self.resolver.resolve(ScalarNode, event.value, 
event.implicit)
+            assert not isinstance(tag, str)
+            # e.g tag.yaml.org,2002:str
         node = ScalarNode(
             tag,
             event.value,
@@ -148,9 +151,10 @@
 
     def compose_sequence_node(self, anchor: Any) -> Any:
         start_event = self.parser.get_event()
-        tag = start_event.tag
-        if tag is None or tag == '!':
+        tag = start_event.ctag
+        if tag is None or str(tag) == '!':
             tag = self.resolver.resolve(SequenceNode, None, 
start_event.implicit)
+            assert not isinstance(tag, str)
         node = SequenceNode(
             tag,
             [],
@@ -180,9 +184,10 @@
 
     def compose_mapping_node(self, anchor: Any) -> Any:
         start_event = self.parser.get_event()
-        tag = start_event.tag
-        if tag is None or tag == '!':
+        tag = start_event.ctag
+        if tag is None or str(tag) == '!':
             tag = self.resolver.resolve(MappingNode, None, 
start_event.implicit)
+            assert not isinstance(tag, str)
         node = MappingNode(
             tag,
             [],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/constructor.py 
new/ruamel.yaml-0.17.31/constructor.py
--- old/ruamel.yaml-0.17.26/constructor.py      2023-05-02 06:22:32.000000000 
+0200
+++ new/ruamel.yaml-0.17.31/constructor.py      2023-05-30 07:32:48.000000000 
+0200
@@ -986,6 +986,17 @@
                 return SingleQuotedScalarString(node.value, anchor=node.anchor)
             if node.style == '"':
                 return DoubleQuotedScalarString(node.value, anchor=node.anchor)
+        # if node.ctag:
+        #     data2 = TaggedScalar()
+        #     data2.value = node.value
+        #     data2.style = node.style
+        #     data2.yaml_set_ctag(node.ctag)
+        #     if node.anchor:
+        #         from ruamel.yaml.serializer import templated_id
+
+        #         if not templated_id(node.anchor):
+        #             data2.yaml_set_anchor(node.anchor, always_dump=True)
+        #     return data2
         if node.anchor:
             return PlainScalarString(node.value, anchor=node.anchor)
         return node.value
@@ -1162,7 +1173,10 @@
         )
 
     def construct_yaml_str(self, node: Any) -> Any:
-        value = self.construct_scalar(node)
+        if node.ctag.handle:
+            value = self.construct_unknown(node)
+        else:
+            value = self.construct_scalar(node)
         if isinstance(value, ScalarString):
             return value
         return value
@@ -1218,7 +1232,7 @@
             if value_node in self.constructed_objects:
                 value = self.constructed_objects[value_node]
             else:
-                value = self.construct_object(value_node, deep=False)
+                value = self.construct_object(value_node, deep=True)
             return value
 
         # merge = []
@@ -1569,7 +1583,7 @@
                     data.fa.set_flow_style()
                 elif node.flow_style is False:
                     data.fa.set_block_style()
-                data.yaml_set_tag(node.tag)
+                data.yaml_set_ctag(node.ctag)
                 yield data
                 if node.anchor:
                     from ruamel.yaml.serializer import templated_id
@@ -1582,7 +1596,7 @@
                 data2 = TaggedScalar()
                 data2.value = self.construct_scalar(node)
                 data2.style = node.style
-                data2.yaml_set_tag(node.tag)
+                data2.yaml_set_ctag(node.ctag)
                 yield data2
                 if node.anchor:
                     from ruamel.yaml.serializer import templated_id
@@ -1597,7 +1611,7 @@
                     data3.fa.set_flow_style()
                 elif node.flow_style is False:
                     data3.fa.set_block_style()
-                data3.yaml_set_tag(node.tag)
+                data3.yaml_set_ctag(node.ctag)
                 yield data3
                 if node.anchor:
                     from ruamel.yaml.serializer import templated_id
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/emitter.py 
new/ruamel.yaml-0.17.31/emitter.py
--- old/ruamel.yaml-0.17.26/emitter.py  2023-05-09 21:49:20.000000000 +0200
+++ new/ruamel.yaml-0.17.31/emitter.py  2023-05-30 07:34:24.000000000 +0200
@@ -748,7 +748,7 @@
             and self.event.tag is not None
         ):
             if self.prepared_tag is None:
-                self.prepared_tag = self.prepare_tag(self.event.tag)
+                self.prepared_tag = self.prepare_tag(self.event.ctag)
             length += len(self.prepared_tag)
         if isinstance(self.event, ScalarEvent):
             if self.analysis is None:
@@ -813,7 +813,7 @@
         if tag is None:
             raise EmitterError('tag is not specified')
         if self.prepared_tag is None:
-            self.prepared_tag = self.prepare_tag(tag)
+            self.prepared_tag = self.prepare_tag(self.event.ctag)
         if self.prepared_tag:
             self.write_indicator(self.prepared_tag, True)
             if (
@@ -825,6 +825,9 @@
         self.prepared_tag = None
 
     def choose_scalar_style(self) -> Any:
+        # issue 449 needs this otherwise emits single quoted empty string
+        if self.event.value == '' and self.event.ctag.handle == '!!':
+            return None
         if self.analysis is None:
             self.analysis = self.analyze_scalar(self.event.value)
         if self.event.style == '"' or self.canonical:
@@ -956,6 +959,7 @@
     def prepare_tag(self, tag: Any) -> Any:
         if not tag:
             raise EmitterError('tag must not be empty')
+        tag = str(tag)
         if tag == '!' or tag == '!!':
             return tag
         handle = None
@@ -1723,3 +1727,26 @@
         comment = event.comment[0]
         self.write_comment(comment)
         return True
+
+
+class RoundTripEmitter(Emitter):
+    def prepare_tag(self, ctag: Any) -> Any:
+        if not ctag:
+            raise EmitterError('tag must not be empty')
+        tag = str(ctag)
+        # print('handling', repr(tag))
+        if tag == '!' or tag == '!!':
+            return tag
+        handle = ctag.handle
+        suffix = ctag.suffix
+        prefixes = sorted(self.tag_prefixes.keys())
+        # print('handling', repr(tag), repr(suffix), repr(handle))
+        if handle is None:
+            for prefix in prefixes:
+                if tag.startswith(prefix) and (prefix == '!' or len(prefix) < 
len(tag)):
+                    handle = self.tag_prefixes[prefix]
+                    suffix = suffix[len(prefix) :]
+        if handle:
+            return f'{handle!s}{suffix!s}'
+        else:
+            return f'!<{suffix!s}>'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/events.py 
new/ruamel.yaml-0.17.31/events.py
--- old/ruamel.yaml-0.17.26/events.py   2023-05-03 09:46:17.000000000 +0200
+++ new/ruamel.yaml-0.17.31/events.py   2023-05-31 07:51:50.000000000 +0200
@@ -3,6 +3,7 @@
 # Abstract classes.
 
 from typing import Any, Dict, Optional, List  # NOQA
+from ruamel.yaml.tag import Tag
 
 SHOW_LINES = False
 
@@ -13,6 +14,7 @@
 
 class Event:
     __slots__ = 'start_mark', 'end_mark', 'comment'
+    crepr = 'Unspecified Event'
 
     def __init__(
         self, start_mark: Any = None, end_mark: Any = None, comment: Any = 
CommentCheck
@@ -55,6 +57,9 @@
                 arguments += f', comment={self.comment!r}'
         return f'{self.__class__.__name__!s}({arguments!s})'
 
+    def compact_repr(self) -> str:
+        return f'{self.crepr}'
+
 
 class NodeEvent(Event):
     __slots__ = ('anchor',)
@@ -67,7 +72,7 @@
 
 
 class CollectionStartEvent(NodeEvent):
-    __slots__ = 'tag', 'implicit', 'flow_style', 'nr_items'
+    __slots__ = 'ctag', 'implicit', 'flow_style', 'nr_items'
 
     def __init__(
         self,
@@ -81,11 +86,15 @@
         nr_items: Optional[int] = None,
     ) -> None:
         NodeEvent.__init__(self, anchor, start_mark, end_mark, comment)
-        self.tag = tag
+        self.ctag = tag
         self.implicit = implicit
         self.flow_style = flow_style
         self.nr_items = nr_items
 
+    @property
+    def tag(self) -> Optional[str]:
+        return None if self.ctag is None else str(self.ctag)
+
 
 class CollectionEndEvent(Event):
     __slots__ = ()
@@ -96,6 +105,7 @@
 
 class StreamStartEvent(Event):
     __slots__ = ('encoding',)
+    crepr = '+STR'
 
     def __init__(
         self,
@@ -110,10 +120,12 @@
 
 class StreamEndEvent(Event):
     __slots__ = ()
+    crepr = '-STR'
 
 
 class DocumentStartEvent(Event):
     __slots__ = 'explicit', 'version', 'tags'
+    crepr = '+DOC'
 
     def __init__(
         self,
@@ -129,9 +141,14 @@
         self.version = version
         self.tags = tags
 
+    def compact_repr(self) -> str:
+        start = ' ---' if self.explicit else ''
+        return f'{self.crepr}{start}'
+
 
 class DocumentEndEvent(Event):
     __slots__ = ('explicit',)
+    crepr = '-DOC'
 
     def __init__(
         self,
@@ -143,9 +160,14 @@
         Event.__init__(self, start_mark, end_mark, comment)
         self.explicit = explicit
 
+    def compact_repr(self) -> str:
+        end = ' ...' if self.explicit else ''
+        return f'{self.crepr}{end}'
+
 
 class AliasEvent(NodeEvent):
     __slots__ = 'style'
+    crepr = '=ALI'
 
     def __init__(
         self,
@@ -158,9 +180,13 @@
         NodeEvent.__init__(self, anchor, start_mark, end_mark, comment)
         self.style = style
 
+    def compact_repr(self) -> str:
+        return f'{self.crepr} *{self.anchor}'
+
 
 class ScalarEvent(NodeEvent):
-    __slots__ = 'tag', 'implicit', 'value', 'style'
+    __slots__ = 'ctag', 'implicit', 'value', 'style'
+    crepr = '=VAL'
 
     def __init__(
         self,
@@ -174,23 +200,65 @@
         comment: Any = None,
     ) -> None:
         NodeEvent.__init__(self, anchor, start_mark, end_mark, comment)
-        self.tag = tag
+        self.ctag = tag
         self.implicit = implicit
         self.value = value
         self.style = style
 
+    @property
+    def tag(self) -> Optional[str]:
+        return None if self.ctag is None else str(self.ctag)
+
+    @tag.setter
+    def tag(self, val: Any) -> None:
+        if isinstance(val, str):
+            val = Tag(suffix=val)
+        self.ctag = val
+
+    def compact_repr(self) -> str:
+        style = ':' if self.style is None else self.style
+        anchor = f'&{self.anchor} ' if self.anchor else ''
+        tag = f'<{self.tag!s}> ' if self.tag else ''
+        value = self.value
+        for ch, rep in [
+            ('\\', '\\\\'),
+            ('\t', '\\t'),
+            ('\n', '\\n'),
+            ('\a', ''),  # remove from folded
+            ('\r', '\\r'),
+            ('\b', '\\b'),
+        ]:
+            value = value.replace(ch, rep)
+        return f'{self.crepr} {anchor}{tag}{style}{value}'
+
 
 class SequenceStartEvent(CollectionStartEvent):
     __slots__ = ()
+    crepr = '+SEQ'
+
+    def compact_repr(self) -> str:
+        flow = ' []' if self.flow_style else ''
+        anchor = f' &{self.anchor}' if self.anchor else ''
+        tag = f' <{self.tag!s}>' if self.tag else ''
+        return f'{self.crepr}{flow}{anchor}{tag}'
 
 
 class SequenceEndEvent(CollectionEndEvent):
     __slots__ = ()
+    crepr = '-SEQ'
 
 
 class MappingStartEvent(CollectionStartEvent):
     __slots__ = ()
+    crepr = '+MAP'
+
+    def compact_repr(self) -> str:
+        flow = ' {}' if self.flow_style else ''
+        anchor = f' &{self.anchor}' if self.anchor else ''
+        tag = f' <{self.tag!s}>' if self.tag else ''
+        return f'{self.crepr}{flow}{anchor}{tag}'
 
 
 class MappingEndEvent(CollectionEndEvent):
     __slots__ = ()
+    crepr = '-MAP'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/main.py 
new/ruamel.yaml-0.17.31/main.py
--- old/ruamel.yaml-0.17.26/main.py     2023-05-05 20:07:12.000000000 +0200
+++ new/ruamel.yaml-0.17.31/main.py     2023-05-28 16:39:30.000000000 +0200
@@ -118,7 +118,7 @@
         elif 'rtsc' in self.typ:
             self.default_flow_style = False
             # no optimized rt-dumper yet
-            self.Emitter = ruamel.yaml.emitter.Emitter
+            self.Emitter = ruamel.yaml.emitter.RoundTripEmitter
             self.Serializer = ruamel.yaml.serializer.Serializer
             self.Representer = ruamel.yaml.representer.RoundTripRepresenter
             self.Scanner = ruamel.yaml.scanner.RoundTripScannerSC
@@ -133,7 +133,7 @@
         if setup_rt:
             self.default_flow_style = False
             # no optimized rt-dumper yet
-            self.Emitter = ruamel.yaml.emitter.Emitter
+            self.Emitter = ruamel.yaml.emitter.RoundTripEmitter
             self.Serializer = ruamel.yaml.serializer.Serializer
             self.Representer = ruamel.yaml.representer.RoundTripRepresenter
             self.Scanner = ruamel.yaml.scanner.RoundTripScanner
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/nodes.py 
new/ruamel.yaml-0.17.31/nodes.py
--- old/ruamel.yaml-0.17.26/nodes.py    2023-05-02 06:22:32.000000000 +0200
+++ new/ruamel.yaml-0.17.31/nodes.py    2023-05-31 07:51:15.000000000 +0200
@@ -2,11 +2,12 @@
 
 import sys
 
-from typing import Dict, Any, Text  # NOQA
+from typing import Dict, Any, Text, Optional  # NOQA
+from ruamel.yaml.tag import Tag
 
 
 class Node:
-    __slots__ = 'tag', 'value', 'start_mark', 'end_mark', 'comment', 'anchor'
+    __slots__ = 'ctag', 'value', 'start_mark', 'end_mark', 'comment', 'anchor'
 
     def __init__(
         self,
@@ -17,13 +18,24 @@
         comment: Any = None,
         anchor: Any = None,
     ) -> None:
-        self.tag = tag
+        # you can still get a string from the serializer
+        self.ctag = tag if isinstance(tag, Tag) else Tag(suffix=tag)
         self.value = value
         self.start_mark = start_mark
         self.end_mark = end_mark
         self.comment = comment
         self.anchor = anchor
 
+    @property
+    def tag(self) -> Optional[str]:
+        return None if self.ctag is None else str(self.ctag)
+
+    @tag.setter
+    def tag(self, val: Any) -> None:
+        if isinstance(val, str):
+            val = Tag(suffix=val)
+        self.ctag = val
+
     def __repr__(self) -> Any:
         value = self.value
         # if isinstance(value, list):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/parser.py 
new/ruamel.yaml-0.17.31/parser.py
--- old/ruamel.yaml-0.17.26/parser.py   2023-05-02 06:22:32.000000000 +0200
+++ new/ruamel.yaml-0.17.31/parser.py   2023-05-30 07:39:57.000000000 +0200
@@ -81,6 +81,7 @@
 from ruamel.yaml.scanner import BlankLineComment
 from ruamel.yaml.comments import C_PRE, C_POST, C_SPLIT_ON_FIRST_BLANK
 from ruamel.yaml.compat import nprint, nprintf  # NOQA
+from ruamel.yaml.tag import Tag
 
 from typing import Any, Dict, Optional, List, Optional  # NOQA
 
@@ -182,6 +183,7 @@
     def parse_implicit_document_start(self) -> Any:
         # Parse an implicit document.
         if not self.scanner.check_token(DirectiveToken, DocumentStartToken, 
StreamEndToken):
+            # don't need copy, as an implicit tag doesn't add tag_handles
             self.tag_handles = self.DEFAULT_TAGS
             token = self.scanner.peek_token()
             start_mark = end_mark = token.start_mark
@@ -243,6 +245,18 @@
         explicit = False
         if self.scanner.check_token(DocumentEndToken):
             token = self.scanner.get_token()
+            # if token.end_mark.line != self.peek_event().start_mark.line:
+            pt = self.scanner.peek_token()
+            if not isinstance(pt, StreamEndToken) and (
+                token.end_mark.line == pt.start_mark.line
+            ):
+                raise ParserError(
+                    None,
+                    None,
+                    'found non-comment content after document end marker, '
+                    f'{self.scanner.peek_token().id,!r}',
+                    self.scanner.peek_token().start_mark,
+                )
             end_mark = token.end_mark
             explicit = True
         event = DocumentEndEvent(start_mark, end_mark, explicit=explicit)
@@ -251,7 +265,11 @@
         if self.resolver.processing_version == (1, 1):
             self.state = self.parse_document_start
         else:
-            self.state = self.parse_implicit_document_start
+            if explicit:
+                # found a document end marker, can be followed by implicit 
document
+                self.state = self.parse_implicit_document_start
+            else:
+                self.state = self.parse_document_start
 
         return event
 
@@ -331,8 +349,13 @@
     def parse_block_node_or_indentless_sequence(self) -> Any:
         return self.parse_node(block=True, indentless_sequence=True)
 
-    def transform_tag(self, handle: Any, suffix: Any) -> Any:
-        return self.tag_handles[handle] + suffix
+    # def transform_tag(self, handle: Any, suffix: Any) -> Any:
+    #     return self.tag_handles[handle] + suffix
+
+    def select_tag_transform(self, tag: Tag) -> None:
+        if tag is None:
+            return
+        tag.select_transform(False)
 
     def parse_node(self, block: bool = False, indentless_sequence: bool = 
False) -> Any:
         if self.scanner.check_token(AliasToken):
@@ -354,39 +377,34 @@
                 token = self.scanner.get_token()
                 tag_mark = token.start_mark
                 end_mark = token.end_mark
-                tag = token.value
+                # tag = token.value
+                tag = Tag(
+                    handle=token.value[0], suffix=token.value[1], 
handles=self.tag_handles,
+                )
         elif self.scanner.check_token(TagToken):
             token = self.scanner.get_token()
             start_mark = tag_mark = token.start_mark
             end_mark = token.end_mark
-            tag = token.value
+            # tag = token.value
+            tag = Tag(handle=token.value[0], suffix=token.value[1], 
handles=self.tag_handles)
             if self.scanner.check_token(AnchorToken):
                 token = self.scanner.get_token()
                 start_mark = tag_mark = token.start_mark
                 end_mark = token.end_mark
                 anchor = token.value
         if tag is not None:
-            handle, suffix = tag
-            if handle is not None:
-                if handle not in self.tag_handles:
-                    raise ParserError(
-                        'while parsing a node',
-                        start_mark,
-                        f'found undefined tag handle {handle!r}',
-                        tag_mark,
-                    )
-                tag = self.transform_tag(handle, suffix)
-            else:
-                tag = suffix
-        # if tag == '!':
-        #     raise ParserError("while parsing a node", start_mark,
-        #             "found non-specific tag '!'", tag_mark,
-        #      "Please check 'http://pyyaml.org/wiki/YAMLNonSpecificTag'
-        #     and share your opinion.")
+            self.select_tag_transform(tag)
+            if tag.check_handle():
+                raise ParserError(
+                    'while parsing a node',
+                    start_mark,
+                    f'found undefined tag handle {tag.handle!r}',
+                    tag_mark,
+                )
         if start_mark is None:
             start_mark = end_mark = self.scanner.peek_token().start_mark
         event = None
-        implicit = tag is None or tag == '!'
+        implicit = tag is None or str(tag) == '!'
         if indentless_sequence and self.scanner.check_token(BlockEntryToken):
             comment = None
             pt = self.scanner.peek_token()
@@ -399,7 +417,7 @@
                     comment = pt.comment
             end_mark = self.scanner.peek_token().end_mark
             event = SequenceStartEvent(
-                anchor, tag, implicit, start_mark, end_mark, flow_style=False, 
comment=comment
+                anchor, tag, implicit, start_mark, end_mark, flow_style=False, 
comment=comment,
             )
             self.state = self.parse_indentless_sequence_entry
             return event
@@ -408,17 +426,17 @@
             token = self.scanner.get_token()
             # self.scanner.peek_token_same_line_comment(token)
             end_mark = token.end_mark
-            if (token.plain and tag is None) or tag == '!':
-                implicit = (True, False)
+            if (token.plain and tag is None) or str(tag) == '!':
+                dimplicit = (True, False)
             elif tag is None:
-                implicit = (False, True)
+                dimplicit = (False, True)
             else:
-                implicit = (False, False)
+                dimplicit = (False, False)
             # nprint('se', token.value, token.comment)
             event = ScalarEvent(
                 anchor,
                 tag,
-                implicit,
+                dimplicit,
                 token.value,
                 start_mark,
                 end_mark,
@@ -775,24 +793,10 @@
 class RoundTripParser(Parser):
     """roundtrip is a safe loader, that wants to see the unmangled tag"""
 
-    def transform_tag(self, handle: Any, suffix: Any) -> Any:
-        # return self.tag_handles[handle]+suffix
-        if handle == '!!' and suffix in (
-            'null',
-            'bool',
-            'int',
-            'float',
-            'binary',
-            'timestamp',
-            'omap',
-            'pairs',
-            'set',
-            'str',
-            'seq',
-            'map',
-        ):
-            return Parser.transform_tag(self, handle, suffix)
-        return handle + suffix
+    def select_tag_transform(self, tag: Tag) -> None:
+        if tag is None:
+            return
+        tag.select_transform(True)
 
     def move_token_comment(
         self, token: Any, nt: Optional[Any] = None, empty: Optional[bool] = 
False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/representer.py 
new/ruamel.yaml-0.17.31/representer.py
--- old/ruamel.yaml-0.17.26/representer.py      2023-05-02 06:22:32.000000000 
+0200
+++ new/ruamel.yaml-0.17.31/representer.py      2023-05-29 07:53:10.000000000 
+0200
@@ -148,6 +148,8 @@
             comment = getattr(value, 'comment', None)
             if comment:
                 comment = [None, [comment]]
+        if isinstance(tag, str):
+            tag = Tag(suffix=tag)
         node = ScalarNode(tag, value, style=style, comment=comment, 
anchor=anchor)
         if self.alias_key is not None:
             self.represented_objects[self.alias_key] = node
@@ -157,6 +159,8 @@
         self, tag: Any, sequence: Any, flow_style: Any = None
     ) -> SequenceNode:
         value: List[Any] = []
+        if isinstance(tag, str):
+            tag = Tag(suffix=tag)
         node = SequenceNode(tag, value, flow_style=flow_style)
         if self.alias_key is not None:
             self.represented_objects[self.alias_key] = node
@@ -175,6 +179,8 @@
 
     def represent_omap(self, tag: Any, omap: Any, flow_style: Any = None) -> 
SequenceNode:
         value: List[Any] = []
+        if isinstance(tag, str):
+            tag = Tag(suffix=tag)
         node = SequenceNode(tag, value, flow_style=flow_style)
         if self.alias_key is not None:
             self.represented_objects[self.alias_key] = node
@@ -195,6 +201,8 @@
 
     def represent_mapping(self, tag: Any, mapping: Any, flow_style: Any = 
None) -> MappingNode:
         value: List[Any] = []
+        if isinstance(tag, str):
+            tag = Tag(suffix=tag)
         node = MappingNode(tag, value, flow_style=flow_style)
         if self.alias_key is not None:
             self.represented_objects[self.alias_key] = node
@@ -709,6 +717,8 @@
             anchor = sequence.yaml_anchor()
         except AttributeError:
             anchor = None
+        if isinstance(tag, str):
+            tag = Tag(suffix=tag)
         node = SequenceNode(tag, value, flow_style=flow_style, anchor=anchor)
         if self.alias_key is not None:
             self.represented_objects[self.alias_key] = node
@@ -784,6 +794,8 @@
             anchor = mapping.yaml_anchor()
         except AttributeError:
             anchor = None
+        if isinstance(tag, str):
+            tag = Tag(suffix=tag)
         node = MappingNode(tag, value, flow_style=flow_style, anchor=anchor)
         if self.alias_key is not None:
             self.represented_objects[self.alias_key] = node
@@ -858,7 +870,9 @@
             else:
                 arg = self.represent_data(merge_list)
                 arg.flow_style = True
-            value.insert(merge_pos, (ScalarNode('tag:yaml.org,2002:merge', 
'<<'), arg))
+            value.insert(
+                merge_pos, (ScalarNode(Tag(suffix='tag:yaml.org,2002:merge'), 
'<<'), arg)
+            )
         return node
 
     def represent_omap(self, tag: Any, omap: Any, flow_style: Any = None) -> 
SequenceNode:
@@ -871,6 +885,8 @@
             anchor = omap.yaml_anchor()
         except AttributeError:
             anchor = None
+        if isinstance(tag, str):
+            tag = Tag(suffix=tag)
         node = SequenceNode(tag, value, flow_style=flow_style, anchor=anchor)
         if self.alias_key is not None:
             self.represented_objects[self.alias_key] = node
@@ -926,7 +942,7 @@
 
     def represent_set(self, setting: Any) -> MappingNode:
         flow_style = False
-        tag = 'tag:yaml.org,2002:set'
+        tag = Tag(suffix='tag:yaml.org,2002:set')
         # return self.represent_mapping(tag, value)
         value: List[Any] = []
         flow_style = setting.fa.flow_style(flow_style)
@@ -979,30 +995,32 @@
     def represent_dict(self, data: Any) -> MappingNode:
         """write out tag if saved on loading"""
         try:
-            t = data.tag.value
+            _ = data.tag
         except AttributeError:
-            t = None
-        if t:
-            if t.startswith('!!'):
-                tag = 'tag:yaml.org,2002:' + t[2:]
-            else:
-                tag = t
+            tag = Tag(suffix='tag:yaml.org,2002:map')
         else:
-            tag = 'tag:yaml.org,2002:map'
+            if data.tag.trval:
+                if data.tag.startswith('!!'):
+                    tag = Tag(suffix='tag:yaml.org,2002:' + data.tag.trval[2:])
+                else:
+                    tag = data.tag
+            else:
+                tag = Tag(suffix='tag:yaml.org,2002:map')
         return self.represent_mapping(tag, data)
 
     def represent_list(self, data: Any) -> SequenceNode:
         try:
-            t = data.tag.value
+            _ = data.tag
         except AttributeError:
-            t = None
-        if t:
-            if t.startswith('!!'):
-                tag = 'tag:yaml.org,2002:' + t[2:]
-            else:
-                tag = t
+            tag = Tag(suffix='tag:yaml.org,2002:seq')
         else:
-            tag = 'tag:yaml.org,2002:seq'
+            if data.tag.trval:
+                if data.tag.startswith('!!'):
+                    tag = Tag(suffix='tag:yaml.org,2002:' + data.tag.trval[2:])
+                else:
+                    tag = data.tag
+            else:
+                tag = Tag(suffix='tag:yaml.org,2002:seq')
         return self.represent_sequence(tag, data)
 
     def represent_datetime(self, data: Any) -> ScalarNode:
@@ -1019,7 +1037,10 @@
 
     def represent_tagged_scalar(self, data: Any) -> ScalarNode:
         try:
-            tag = data.tag.value
+            if data.tag.handle == '!!':
+                tag = f'{data.tag.handle} {data.tag.suffix}'
+            else:
+                tag = data.tag
         except AttributeError:
             tag = None
         try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/resolver.py 
new/ruamel.yaml-0.17.31/resolver.py
--- old/ruamel.yaml-0.17.26/resolver.py 2023-05-02 06:22:32.000000000 +0200
+++ new/ruamel.yaml-0.17.31/resolver.py 2023-05-30 07:42:47.000000000 +0200
@@ -5,6 +5,7 @@
 from typing import Any, Dict, List, Union, Text, Optional  # NOQA
 from ruamel.yaml.compat import VersionType  # NOQA
 
+from ruamel.yaml.tag import Tag
 from ruamel.yaml.compat import _DEFAULT_YAML_VERSION  # NOQA
 from ruamel.yaml.error import *  # NOQA
 from ruamel.yaml.nodes import MappingNode, ScalarNode, SequenceNode  # NOQA
@@ -102,9 +103,9 @@
 
 class BaseResolver:
 
-    DEFAULT_SCALAR_TAG = 'tag:yaml.org,2002:str'
-    DEFAULT_SEQUENCE_TAG = 'tag:yaml.org,2002:seq'
-    DEFAULT_MAPPING_TAG = 'tag:yaml.org,2002:map'
+    DEFAULT_SCALAR_TAG = Tag(suffix='tag:yaml.org,2002:str')
+    DEFAULT_SEQUENCE_TAG = Tag(suffix='tag:yaml.org,2002:seq')
+    DEFAULT_MAPPING_TAG = Tag(suffix='tag:yaml.org,2002:map')
 
     yaml_implicit_resolvers: Dict[Any, Any] = {}
     yaml_path_resolvers: Dict[Any, Any] = {}
@@ -268,14 +269,14 @@
             resolvers += self.yaml_implicit_resolvers.get(None, [])
             for tag, regexp in resolvers:
                 if regexp.match(value):
-                    return tag
+                    return Tag(suffix=tag)
             implicit = implicit[1]
         if bool(self.yaml_path_resolvers):
             exact_paths = self.resolver_exact_paths[-1]
             if kind in exact_paths:
-                return exact_paths[kind]
+                return Tag(suffix=exact_paths[kind])
             if None in exact_paths:
-                return exact_paths[None]
+                return Tag(suffix=exact_paths[None])
         if kind is ScalarNode:
             return self.DEFAULT_SCALAR_TAG
         elif kind is SequenceNode:
@@ -354,14 +355,14 @@
             resolvers += self.versioned_resolver.get(None, [])
             for tag, regexp in resolvers:
                 if regexp.match(value):
-                    return tag
+                    return Tag(suffix=tag)
             implicit = implicit[1]
         if bool(self.yaml_path_resolvers):
             exact_paths = self.resolver_exact_paths[-1]
             if kind in exact_paths:
-                return exact_paths[kind]
+                return Tag(suffix=exact_paths[kind])
             if None in exact_paths:
-                return exact_paths[None]
+                return Tag(suffix=exact_paths[None])
         if kind is ScalarNode:
             return self.DEFAULT_SCALAR_TAG
         elif kind is SequenceNode:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/ruamel.yaml.egg-info/PKG-INFO 
new/ruamel.yaml-0.17.31/ruamel.yaml.egg-info/PKG-INFO
--- old/ruamel.yaml-0.17.26/ruamel.yaml.egg-info/PKG-INFO       2023-05-09 
22:00:46.000000000 +0200
+++ new/ruamel.yaml-0.17.31/ruamel.yaml.egg-info/PKG-INFO       2023-05-31 
07:58:00.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: ruamel.yaml
-Version: 0.17.26
+Version: 0.17.31
 Summary: ruamel.yaml is a YAML parser/emitter that supports roundtrip 
preservation of comments, seq/map flow style, and map key order
 Home-page: https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree
 Author: Anthon van der Neut
@@ -34,8 +34,8 @@
 
 ``ruamel.yaml`` is a YAML 1.2 loader/dumper package for Python.
 
-:version:       0.17.26
-:updated:       2023-05-09
+:version:       0.17.31
+:updated:       2023-05-31
 :documentation: http://yaml.readthedocs.io
 :repository:    https://sourceforge.net/projects/ruamel-yaml/
 :pypi:          https://pypi.org/project/ruamel.yaml/
@@ -91,8 +91,36 @@
 
 .. should insert NEXT: at the beginning of line for next key (with empty line)
 
+0.17.31 (2023-05-31):
+  - added tag.setter on `ScalarEvent` and on `Node`, that takes either 
+    a `Tag` instance, or a str 
+    (reported by `Sorin Sbarnea 
<https://sourceforge.net/u/ssbarnea/profile/>`__)
+
+0.17.30 (2023-05-30):
+  - fix issue 467, caused by Tag instances not being hashable (reported by
+    `Douglas Raillard
+    <https://bitbucket.org/%7Bcf052d92-a278-4339-9aa8-de41923bb556%7D/>`__)
+
+0.17.29 (2023-05-30):
+  - changed the internals of the tag property from a string to a class which 
allows
+    for preservation of the original handle and suffix. This should
+    result in better results using documents with %TAG directives, as well
+    as preserving URI escapes in tag suffixes.
+
+0.17.28 (2023-05-26):
+  - fix for issue 464: documents ending with document end marker without final 
newline
+    fail to load (reported by `Mariusz Rusiniak 
<https://sourceforge.net/u/r2dan/profile/>`__)
+
+0.17.27 (2023-05-25):
+  - fix issue with inline mappings as value for merge keys
+    (reported by Sirish on `StackOverflow 
<https://stackoverflow.com/q/76331049/1307905>`__)
+  - fix for 468, error inserting after accessing merge attribute on 
``CommentedMap``
+    (reported by `Bastien gerard <https://sourceforge.net/u/bagerard/>`__)
+  - fix for issue 461 pop + insert on same `CommentedMap` key throwing error
+    (reported by `John Thorvald Wodder II 
<https://sourceforge.net/u/jwodder/profile/>`__) 
+
 0.17.26 (2023-05-09):
-  - Fix for error on edge cage for issue 459
+  - fix for error on edge cage for issue 459
 
 0.17.25 (2023-05-09):
   - fix for regression while dumping wrapped strings with too many backslashes 
removed
@@ -188,7 +216,7 @@
     attrs with `@attr.s()` (both reported by `ssph 
<https://sourceforge.net/u/sph/>`__)
 
 0.17.11 (2021-08-19):
-  - fix error baseclass for ``DuplicateKeyErorr`` (reported by `Łukasz 
Rogalski
+  - fix error baseclass for ``DuplicateKeyError`` (reported by `Łukasz 
Rogalski
     <https://sourceforge.net/u/lrogalski/>`__)
   - fix typo in reader error message, causing `KeyError` during reader error 
     (reported by `MTU <https://sourceforge.net/u/mtu/>`__)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/ruamel.yaml.egg-info/SOURCES.txt 
new/ruamel.yaml-0.17.31/ruamel.yaml.egg-info/SOURCES.txt
--- old/ruamel.yaml-0.17.26/ruamel.yaml.egg-info/SOURCES.txt    2023-05-09 
22:00:46.000000000 +0200
+++ new/ruamel.yaml-0.17.31/ruamel.yaml.egg-info/SOURCES.txt    2023-05-31 
07:58:00.000000000 +0200
@@ -31,6 +31,7 @@
 ./scalarstring.py
 ./scanner.py
 ./serializer.py
+./tag.py
 ./timestamp.py
 ./tokens.py
 ./util.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/scanner.py 
new/ruamel.yaml-0.17.31/scanner.py
--- old/ruamel.yaml-0.17.26/scanner.py  2023-05-06 12:19:26.000000000 +0200
+++ new/ruamel.yaml-0.17.31/scanner.py  2023-05-29 13:27:38.000000000 +0200
@@ -1291,16 +1291,25 @@
         srp = self.reader.peek
         srf = self.reader.forward
         chunks = []
+        first_indent = -1
         max_indent = 0
         end_mark = self.reader.get_mark()
         while srp() in ' \r\n\x85\u2028\u2029':
             if srp() != ' ':
+                if first_indent < 0:
+                    first_indent = self.reader.column
                 chunks.append(self.scan_line_break())
                 end_mark = self.reader.get_mark()
             else:
                 srf()
                 if self.reader.column > max_indent:
                     max_indent = self.reader.column
+        if first_indent > 0 and max_indent > first_indent:
+            start_mark = self.reader.get_mark()
+            raise ScannerError(
+                'more indented follow up line than first in a block scalar',
+                start_mark,
+            )
         return chunks, max_indent, end_mark
 
     def scan_block_scalar_breaks(self, indent: int) -> Any:
@@ -1493,7 +1502,9 @@
                 break
             while True:
                 ch = srp(length)
-                if ch == ':' and srp(length + 1) not in _THE_END_SPACE_TAB:
+                if ch == ':' and srp(length + 1) == ',':
+                    break
+                elif ch == ':' and srp(length + 1) not in _THE_END_SPACE_TAB:
                     pass
                 elif ch == '?' and self.scanner_processing_version != (1, 1):
                     pass
@@ -1918,6 +1929,37 @@
     def scan_block_scalar(self, style: Any, rt: Optional[bool] = True) -> Any:
         return Scanner.scan_block_scalar(self, style, rt=rt)
 
+    def scan_uri_escapes(self, name: Any, start_mark: Any) -> Any:
+        """
+        The roundtripscanner doesn't do URI escaping
+        """
+        # See the specification for details.
+        srp = self.reader.peek
+        srf = self.reader.forward
+        code_bytes: List[Any] = []
+        chunk = ''
+        mark = self.reader.get_mark()
+        while srp() == '%':
+            chunk += '%'
+            srf()
+            for k in range(2):
+                if srp(k) not in '0123456789ABCDEFabcdef':
+                    raise ScannerError(
+                        f'while scanning an {name!s}',
+                        start_mark,
+                        f'expected URI escape sequence of 2 hexdecimal 
numbers, '
+                        f'but found {srp(k)!r}',
+                        self.reader.get_mark(),
+                    )
+            code_bytes.append(int(self.reader.prefix(2), 16))
+            chunk += self.reader.prefix(2)
+            srf(2)
+        try:
+            _ = bytes(code_bytes).decode('utf-8')
+        except UnicodeDecodeError as exc:
+            raise ScannerError(f'while scanning an {name!s}', start_mark, 
str(exc), mark)
+        return chunk
+
 
 # commenthandling 2021, differentiatiation not needed
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/serializer.py 
new/ruamel.yaml-0.17.31/serializer.py
--- old/ruamel.yaml-0.17.26/serializer.py       2023-05-02 06:22:32.000000000 
+0200
+++ new/ruamel.yaml-0.17.31/serializer.py       2023-05-29 07:48:22.000000000 
+0200
@@ -158,14 +158,14 @@
                 detected_tag = self.resolver.resolve(ScalarNode, node.value, 
(True, False))
                 default_tag = self.resolver.resolve(ScalarNode, node.value, 
(False, True))
                 implicit = (
-                    (node.tag == detected_tag),
-                    (node.tag == default_tag),
-                    node.tag.startswith('tag:yaml.org,2002:'),
+                    (node.ctag == detected_tag),
+                    (node.ctag == default_tag),
+                    node.tag.startswith('tag:yaml.org,2002:'),  # type: ignore
                 )
                 self.emitter.emit(
                     ScalarEvent(
                         alias,
-                        node.tag,
+                        node.ctag,
                         implicit,
                         node.value,
                         style=node.style,
@@ -173,7 +173,7 @@
                     )
                 )
             elif isinstance(node, SequenceNode):
-                implicit = node.tag == self.resolver.resolve(SequenceNode, 
node.value, True)
+                implicit = node.ctag == self.resolver.resolve(SequenceNode, 
node.value, True)
                 comment = node.comment
                 end_comment = None
                 seq_comment = None
@@ -188,7 +188,7 @@
                 self.emitter.emit(
                     SequenceStartEvent(
                         alias,
-                        node.tag,
+                        node.ctag,
                         implicit,
                         flow_style=node.flow_style,
                         comment=node.comment,
@@ -200,7 +200,7 @@
                     index += 1
                 self.emitter.emit(SequenceEndEvent(comment=[seq_comment, 
end_comment]))
             elif isinstance(node, MappingNode):
-                implicit = node.tag == self.resolver.resolve(MappingNode, 
node.value, True)
+                implicit = node.ctag == self.resolver.resolve(MappingNode, 
node.value, True)
                 comment = node.comment
                 end_comment = None
                 map_comment = None
@@ -213,7 +213,7 @@
                 self.emitter.emit(
                     MappingStartEvent(
                         alias,
-                        node.tag,
+                        node.ctag,
                         implicit,
                         flow_style=node.flow_style,
                         comment=node.comment,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/tag.py 
new/ruamel.yaml-0.17.31/tag.py
--- old/ruamel.yaml-0.17.26/tag.py      1970-01-01 01:00:00.000000000 +0100
+++ new/ruamel.yaml-0.17.31/tag.py      2023-05-30 14:47:15.000000000 +0200
@@ -0,0 +1,122 @@
+# coding: utf-8
+
+"""
+In round-trip mode the original tag needs to be preserved, but the tag
+transformed based on the directives needs to be available as well.
+
+A Tag that is created during loading has a handle and a suffix.
+Not all objects loaded currently have a Tag, that .tag attribute can be None
+A Tag that is created for dumping only (on an object loaded without a tag) has 
a suffix
+only.
+"""
+
+from typing import Any, Dict, Optional, List, Union, Optional, Iterator  # NOQA
+
+tag_attrib = '_yaml_tag'
+
+
+class Tag:
+    """store original tag information for roundtripping"""
+
+    attrib = tag_attrib
+
+    def __init__(self, handle: Any = None, suffix: Any = None, handles: Any = 
None) -> None:
+        self.handle = handle
+        self.suffix = suffix
+        self.handles = handles
+        self._transform_type: Optional[bool] = None
+
+    def __repr__(self) -> str:
+        return f'{self.__class__.__name__}({self.trval!r})'
+
+    def __str__(self) -> str:
+        return f'{self.trval}'
+
+    def __hash__(self) -> int:
+        try:
+            return self._hash_id  # type: ignore
+        except AttributeError:
+            self._hash_id = res = hash((self.handle, self.suffix))
+            return res
+
+    def __eq__(self, other: Any) -> bool:
+        # other should not be a string, but the serializer sometimes provides 
these
+        if isinstance(other, str):
+            return self.trval == other
+        return bool(self.trval == other.trval)
+
+    def startswith(self, x: str) -> bool:
+        if self.trval is not None:
+            return self.trval.startswith(x)
+        return False
+
+    @property
+    def trval(self) -> Optional[str]:
+        try:
+            return self._trval
+        except AttributeError:
+            pass
+        if self.handle is None:
+            self._trval: Optional[str] = self.uri_decoded_suffix
+            return self._trval
+        assert self._transform_type is not None
+        if not self._transform_type:
+            # the non-round-trip case
+            self._trval = self.handles[self.handle] + self.uri_decoded_suffix
+            return self._trval
+        # round-trip case
+        if self.handle == '!!' and self.suffix in (
+            'null',
+            'bool',
+            'int',
+            'float',
+            'binary',
+            'timestamp',
+            'omap',
+            'pairs',
+            'set',
+            'str',
+            'seq',
+            'map',
+        ):
+            self._trval = self.handles[self.handle] + self.uri_decoded_suffix
+        else:
+            # self._trval = self.handle + self.suffix
+            self._trval = self.handles[self.handle] + self.uri_decoded_suffix
+        return self._trval
+
+    @property
+    def uri_decoded_suffix(self) -> Optional[str]:
+        try:
+            return self._uri_decoded_suffix
+        except AttributeError:
+            pass
+        if self.suffix is None:
+            self._uri_decoded_suffix: Optional[str] = None
+            return None
+        res = ''
+        # don't have to check for scanner errors here
+        idx = 0
+        while idx < len(self.suffix):
+            ch = self.suffix[idx]
+            idx += 1
+            if ch != '%':
+                res += ch
+            else:
+                res += chr(int(self.suffix[idx : idx + 2], 16))
+                idx += 2
+        self._uri_decoded_suffix = res
+        return res
+
+    def select_transform(self, val: bool) -> None:
+        """
+        val: False -> non-round-trip
+             True -> round-trip
+        """
+        assert self._transform_type is None
+        self._transform_type = val
+
+    def check_handle(self) -> bool:
+        if self.handle is None:
+            return False
+        return self.handle not in self.handles
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ruamel.yaml-0.17.26/timestamp.py 
new/ruamel.yaml-0.17.31/timestamp.py
--- old/ruamel.yaml-0.17.26/timestamp.py        2023-05-02 06:22:32.000000000 
+0200
+++ new/ruamel.yaml-0.17.31/timestamp.py        2023-05-29 13:14:11.000000000 
+0200
@@ -5,6 +5,8 @@
 
 # ToDo: at least on PY3 you could probably attach the tzinfo correctly to the 
object
 #       a more complete datetime might be used by safe loading as well
+#
+#       add type information (iso8601, spaced)
 
 from typing import Any, Dict, Optional, List  # NOQA
 

Reply via email to