Could ruamel.yaml support type descriptor like "num: !!float 4"?

Viewed 38

I am learning using ruamel.yaml, and I am wondering whether it supports type descriptor as the original YAML like "num: !!float 4"? The file is like:

num: !!float 4

I tried import a file like this, but met an error:

---------------------------------------------------------------------------<br>
ValueError                                Traceback (most recent call last)
Input In [22], in <cell line: 2>()
      1 from ruamel import yaml
      2 with open("net.yaml", "r", encoding="utf-8") as yaml_file:
----> 3     yaml_dict = yaml.round_trip_load(yaml_file)
      4 yaml_dict

...

File ~/software/python/anaconda/anaconda3/envs/conda-general/lib/python3.10/site-packages/ruamel/yaml/constructor.py:1469, in RoundTripConstructor.construct_mapping(self, node, maptyp, deep)
   1462     if not isinstance(key, Hashable):
   1463         raise ConstructorError(
   1464             'while constructing a mapping',
   1465             node.start_mark,
   1466             'found unhashable key',
   1467             key_node.start_mark,
   1468         )
-> 1469 value = self.construct_object(value_node, deep=deep)
   1470 if self.check_mapping_key(node, key_node, maptyp, key, value):
   1471     if key_node.comment and len(key_node.comment) > 4 and key_node.comment[4]:

File ~/software/python/anaconda/anaconda3/envs/conda-general/lib/python3.10/site-packages/ruamel/yaml/constructor.py:146, in BaseConstructor.construct_object(self, node, deep)
    142     # raise ConstructorError(
    143     #     None, None, 'found unconstructable recursive node', node.start_mark
    144     # )
    145 self.recursive_objects[node] = None
--> 146 data = self.construct_non_recursive_object(node)
    148 self.constructed_objects[node] = data
    149 del self.recursive_objects[node]

File ~/software/python/anaconda/anaconda3/envs/conda-general/lib/python3.10/site-packages/ruamel/yaml/constructor.py:181, in BaseConstructor.construct_non_recursive_object(self, node, tag)
    179             constructor = self.__class__.construct_mapping
    180 if tag_suffix is None:
--> 181     data = constructor(self, node)
    182 else:
    183     data = constructor(self, tag_suffix, node)

File ~/software/python/anaconda/anaconda3/envs/conda-general/lib/python3.10/site-packages/ruamel/yaml/constructor.py:1271, in RoundTripConstructor.construct_yaml_float(self, node)
   1259     return ScalarFloat(
   1260         sign * float(value_s),
   1261         width=width,
   (...)
   1268         anchor=node.anchor,
   1269     )
   1270 width = len(value_so)
-> 1271 prec = value_so.index('.')  # you can use index, this would not be float without dot
   1272 lead0 = leading_zeros(value_so)
   1273 return ScalarFloat(
   1274     sign * float(value_s),
   1275     width=width,
   (...)
   1279     anchor=node.anchor,
   1280 )

ValueError: substring not found

Why do I get this error, and how do I get rid of it?

1 Answers

That is a bug in ruamel.yaml<=0.17.21. The comment on the offending line (1271) says

# you can use index, this would not be float without dot

Obviously the author of that comment didn't know what he was talking about, as in your case, when using !!float 4 you have a float without a dot...

It is trivial to "fix" that by replacing index with find in line 1271, and when doing so that will load your document and you can dump the data. But the corresponding representer for dumping doesn't cope with that outputs the float as 4.0, dropping the tag.

You could temporarily fix this by registering a simpler float constructor (e.g. the simple one from the SafeLoader), although this will affect all floats:

import sys
import ruamel.yaml

yaml_str = """\
num: !!float 4
"""
    

yaml = ruamel.yaml.YAML()
yaml.constructor.add_constructor(
    'tag:yaml.org,2002:float', ruamel.yaml.constructor.SafeConstructor.construct_yaml_float
)
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)

which gives:

num: 4.0
Related