representer.py 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  1. # coding: utf-8
  2. from ruamel.yaml.error import * # NOQA
  3. from ruamel.yaml.nodes import * # NOQA
  4. from ruamel.yaml.compat import ordereddict
  5. from ruamel.yaml.compat import nprint, nprintf # NOQA
  6. from ruamel.yaml.scalarstring import (
  7. LiteralScalarString,
  8. FoldedScalarString,
  9. SingleQuotedScalarString,
  10. DoubleQuotedScalarString,
  11. PlainScalarString,
  12. )
  13. from ruamel.yaml.comments import (
  14. CommentedMap,
  15. CommentedOrderedMap,
  16. CommentedSeq,
  17. CommentedKeySeq,
  18. CommentedKeyMap,
  19. CommentedSet,
  20. comment_attrib,
  21. merge_attrib,
  22. TaggedScalar,
  23. )
  24. from ruamel.yaml.scalarint import ScalarInt, BinaryInt, OctalInt, HexInt, HexCapsInt
  25. from ruamel.yaml.scalarfloat import ScalarFloat
  26. from ruamel.yaml.scalarbool import ScalarBoolean
  27. from ruamel.yaml.timestamp import TimeStamp
  28. from ruamel.yaml.anchor import Anchor
  29. import collections
  30. import datetime
  31. import types
  32. import copyreg
  33. import base64
  34. from typing import Dict, List, Any, Union, Text, Optional # NOQA
  35. # fmt: off
  36. __all__ = ['BaseRepresenter', 'SafeRepresenter', 'Representer',
  37. 'RepresenterError', 'RoundTripRepresenter']
  38. # fmt: on
  39. class RepresenterError(YAMLError):
  40. pass
  41. class BaseRepresenter:
  42. yaml_representers: Dict[Any, Any] = {}
  43. yaml_multi_representers: Dict[Any, Any] = {}
  44. def __init__(
  45. self: Any,
  46. default_style: Any = None,
  47. default_flow_style: Any = None,
  48. dumper: Any = None,
  49. ) -> None:
  50. self.dumper = dumper
  51. if self.dumper is not None:
  52. self.dumper._representer = self
  53. self.default_style = default_style
  54. self.default_flow_style = default_flow_style
  55. self.represented_objects: Dict[Any, Any] = {}
  56. self.object_keeper: List[Any] = []
  57. self.alias_key: Optional[int] = None
  58. self.sort_base_mapping_type_on_output = True
  59. @property
  60. def serializer(self) -> Any:
  61. try:
  62. if hasattr(self.dumper, 'typ'):
  63. return self.dumper.serializer
  64. return self.dumper._serializer
  65. except AttributeError:
  66. return self # cyaml
  67. def represent(self, data: Any) -> None:
  68. node = self.represent_data(data)
  69. self.serializer.serialize(node)
  70. self.represented_objects = {}
  71. self.object_keeper = []
  72. self.alias_key = None
  73. def represent_data(self, data: Any) -> Any:
  74. if self.ignore_aliases(data):
  75. self.alias_key = None
  76. else:
  77. self.alias_key = id(data)
  78. if self.alias_key is not None:
  79. if self.alias_key in self.represented_objects:
  80. node = self.represented_objects[self.alias_key]
  81. # if node is None:
  82. # raise RepresenterError(
  83. # f"recursive objects are not allowed: {data!r}")
  84. return node
  85. # self.represented_objects[alias_key] = None
  86. self.object_keeper.append(data)
  87. data_types = type(data).__mro__
  88. if data_types[0] in self.yaml_representers:
  89. node = self.yaml_representers[data_types[0]](self, data)
  90. else:
  91. for data_type in data_types:
  92. if data_type in self.yaml_multi_representers:
  93. node = self.yaml_multi_representers[data_type](self, data)
  94. break
  95. else:
  96. if None in self.yaml_multi_representers:
  97. node = self.yaml_multi_representers[None](self, data)
  98. elif None in self.yaml_representers:
  99. node = self.yaml_representers[None](self, data)
  100. else:
  101. node = ScalarNode(None, str(data))
  102. # if alias_key is not None:
  103. # self.represented_objects[alias_key] = node
  104. return node
  105. def represent_key(self, data: Any) -> Any:
  106. """
  107. David Fraser: Extract a method to represent keys in mappings, so that
  108. a subclass can choose not to quote them (for example)
  109. used in represent_mapping
  110. https://bitbucket.org/davidfraser/pyyaml/commits/d81df6eb95f20cac4a79eed95ae553b5c6f77b8c
  111. """
  112. return self.represent_data(data)
  113. @classmethod
  114. def add_representer(cls, data_type: Any, representer: Any) -> None:
  115. if 'yaml_representers' not in cls.__dict__:
  116. cls.yaml_representers = cls.yaml_representers.copy()
  117. cls.yaml_representers[data_type] = representer
  118. @classmethod
  119. def add_multi_representer(cls, data_type: Any, representer: Any) -> None:
  120. if 'yaml_multi_representers' not in cls.__dict__:
  121. cls.yaml_multi_representers = cls.yaml_multi_representers.copy()
  122. cls.yaml_multi_representers[data_type] = representer
  123. def represent_scalar(
  124. self, tag: Any, value: Any, style: Any = None, anchor: Any = None,
  125. ) -> ScalarNode:
  126. if style is None:
  127. style = self.default_style
  128. comment = None
  129. if style and style[0] in '|>':
  130. comment = getattr(value, 'comment', None)
  131. if comment:
  132. comment = [None, [comment]]
  133. if isinstance(tag, str):
  134. tag = Tag(suffix=tag)
  135. node = ScalarNode(tag, value, style=style, comment=comment, anchor=anchor)
  136. if self.alias_key is not None:
  137. self.represented_objects[self.alias_key] = node
  138. return node
  139. def represent_sequence(
  140. self, tag: Any, sequence: Any, flow_style: Any = None,
  141. ) -> SequenceNode:
  142. value: List[Any] = []
  143. if isinstance(tag, str):
  144. tag = Tag(suffix=tag)
  145. node = SequenceNode(tag, value, flow_style=flow_style)
  146. if self.alias_key is not None:
  147. self.represented_objects[self.alias_key] = node
  148. best_style = True
  149. for item in sequence:
  150. node_item = self.represent_data(item)
  151. if not (isinstance(node_item, ScalarNode) and not node_item.style):
  152. best_style = False
  153. value.append(node_item)
  154. if flow_style is None:
  155. if self.default_flow_style is not None:
  156. node.flow_style = self.default_flow_style
  157. else:
  158. node.flow_style = best_style
  159. return node
  160. def represent_omap(self, tag: Any, omap: Any, flow_style: Any = None) -> SequenceNode:
  161. value: List[Any] = []
  162. if isinstance(tag, str):
  163. tag = Tag(suffix=tag)
  164. node = SequenceNode(tag, value, flow_style=flow_style)
  165. if self.alias_key is not None:
  166. self.represented_objects[self.alias_key] = node
  167. best_style = True
  168. for item_key in omap:
  169. item_val = omap[item_key]
  170. node_item = self.represent_data({item_key: item_val})
  171. # if not (isinstance(node_item, ScalarNode) \
  172. # and not node_item.style):
  173. # best_style = False
  174. value.append(node_item)
  175. if flow_style is None:
  176. if self.default_flow_style is not None:
  177. node.flow_style = self.default_flow_style
  178. else:
  179. node.flow_style = best_style
  180. return node
  181. def represent_mapping(self, tag: Any, mapping: Any, flow_style: Any = None) -> MappingNode:
  182. value: List[Any] = []
  183. if isinstance(tag, str):
  184. tag = Tag(suffix=tag)
  185. node = MappingNode(tag, value, flow_style=flow_style)
  186. if self.alias_key is not None:
  187. self.represented_objects[self.alias_key] = node
  188. best_style = True
  189. if hasattr(mapping, 'items'):
  190. mapping = list(mapping.items())
  191. if self.sort_base_mapping_type_on_output:
  192. try:
  193. mapping = sorted(mapping)
  194. except TypeError:
  195. pass
  196. for item_key, item_value in mapping:
  197. node_key = self.represent_key(item_key)
  198. node_value = self.represent_data(item_value)
  199. if not (isinstance(node_key, ScalarNode) and not node_key.style):
  200. best_style = False
  201. if not (isinstance(node_value, ScalarNode) and not node_value.style):
  202. best_style = False
  203. value.append((node_key, node_value))
  204. if flow_style is None:
  205. if self.default_flow_style is not None:
  206. node.flow_style = self.default_flow_style
  207. else:
  208. node.flow_style = best_style
  209. return node
  210. def ignore_aliases(self, data: Any) -> bool:
  211. return False
  212. class SafeRepresenter(BaseRepresenter):
  213. def ignore_aliases(self, data: Any) -> bool:
  214. # https://docs.python.org/3/reference/expressions.html#parenthesized-forms :
  215. # "i.e. two occurrences of the empty tuple may or may not yield the same object"
  216. # so "data is ()" should not be used
  217. if data is None or (isinstance(data, tuple) and data == ()):
  218. return True
  219. if isinstance(data, (bytes, str, bool, int, float)):
  220. return True
  221. return False
  222. def represent_none(self, data: Any) -> ScalarNode:
  223. return self.represent_scalar('tag:yaml.org,2002:null', 'null')
  224. def represent_str(self, data: Any) -> Any:
  225. return self.represent_scalar('tag:yaml.org,2002:str', data)
  226. def represent_binary(self, data: Any) -> ScalarNode:
  227. if hasattr(base64, 'encodebytes'):
  228. data = base64.encodebytes(data).decode('ascii')
  229. else:
  230. # check py2 only?
  231. data = base64.encodestring(data).decode('ascii') # type: ignore
  232. return self.represent_scalar('tag:yaml.org,2002:binary', data, style='|')
  233. def represent_bool(self, data: Any, anchor: Optional[Any] = None) -> ScalarNode:
  234. try:
  235. value = self.dumper.boolean_representation[bool(data)]
  236. except AttributeError:
  237. if data:
  238. value = 'true'
  239. else:
  240. value = 'false'
  241. return self.represent_scalar('tag:yaml.org,2002:bool', value, anchor=anchor)
  242. def represent_int(self, data: Any) -> ScalarNode:
  243. return self.represent_scalar('tag:yaml.org,2002:int', str(data))
  244. inf_value = 1e300
  245. while repr(inf_value) != repr(inf_value * inf_value):
  246. inf_value *= inf_value
  247. def represent_float(self, data: Any) -> ScalarNode:
  248. if data != data or (data == 0.0 and data == 1.0):
  249. value = '.nan'
  250. elif data == self.inf_value:
  251. value = '.inf'
  252. elif data == -self.inf_value:
  253. value = '-.inf'
  254. else:
  255. value = repr(data).lower()
  256. if getattr(self.serializer, 'use_version', None) == (1, 1):
  257. if '.' not in value and 'e' in value:
  258. # Note that in some cases `repr(data)` represents a float number
  259. # without the decimal parts. For instance:
  260. # >>> repr(1e17)
  261. # '1e17'
  262. # Unfortunately, this is not a valid float representation according
  263. # to the definition of the `!!float` tag in YAML 1.1. We fix
  264. # this by adding '.0' before the 'e' symbol.
  265. value = value.replace('e', '.0e', 1)
  266. return self.represent_scalar('tag:yaml.org,2002:float', value)
  267. def represent_list(self, data: Any) -> SequenceNode:
  268. # pairs = (len(data) > 0 and isinstance(data, list))
  269. # if pairs:
  270. # for item in data:
  271. # if not isinstance(item, tuple) or len(item) != 2:
  272. # pairs = False
  273. # break
  274. # if not pairs:
  275. return self.represent_sequence('tag:yaml.org,2002:seq', data)
  276. # value = []
  277. # for item_key, item_value in data:
  278. # value.append(self.represent_mapping('tag:yaml.org,2002:map',
  279. # [(item_key, item_value)]))
  280. # return SequenceNode('tag:yaml.org,2002:pairs', value)
  281. def represent_dict(self, data: Any) -> MappingNode:
  282. return self.represent_mapping('tag:yaml.org,2002:map', data)
  283. def represent_ordereddict(self, data: Any) -> SequenceNode:
  284. return self.represent_omap('tag:yaml.org,2002:omap', data)
  285. def represent_set(self, data: Any) -> MappingNode:
  286. value: Dict[Any, None] = {}
  287. for key in data:
  288. value[key] = None
  289. return self.represent_mapping('tag:yaml.org,2002:set', value)
  290. def represent_date(self, data: Any) -> ScalarNode:
  291. value = data.isoformat()
  292. return self.represent_scalar('tag:yaml.org,2002:timestamp', value)
  293. def represent_datetime(self, data: Any) -> ScalarNode:
  294. value = data.isoformat(' ')
  295. return self.represent_scalar('tag:yaml.org,2002:timestamp', value)
  296. def represent_yaml_object(
  297. self, tag: Any, data: Any, cls: Any, flow_style: Any = None,
  298. ) -> MappingNode:
  299. if hasattr(data, '__getstate__'):
  300. state = data.__getstate__()
  301. else:
  302. state = data.__dict__.copy()
  303. return self.represent_mapping(tag, state, flow_style=flow_style)
  304. def represent_undefined(self, data: Any) -> None:
  305. raise RepresenterError(f'cannot represent an object: {data!s}')
  306. SafeRepresenter.add_representer(type(None), SafeRepresenter.represent_none)
  307. SafeRepresenter.add_representer(str, SafeRepresenter.represent_str)
  308. SafeRepresenter.add_representer(bytes, SafeRepresenter.represent_binary)
  309. SafeRepresenter.add_representer(bool, SafeRepresenter.represent_bool)
  310. SafeRepresenter.add_representer(int, SafeRepresenter.represent_int)
  311. SafeRepresenter.add_representer(float, SafeRepresenter.represent_float)
  312. SafeRepresenter.add_representer(list, SafeRepresenter.represent_list)
  313. SafeRepresenter.add_representer(tuple, SafeRepresenter.represent_list)
  314. SafeRepresenter.add_representer(dict, SafeRepresenter.represent_dict)
  315. SafeRepresenter.add_representer(set, SafeRepresenter.represent_set)
  316. SafeRepresenter.add_representer(ordereddict, SafeRepresenter.represent_ordereddict)
  317. SafeRepresenter.add_representer(
  318. collections.OrderedDict, SafeRepresenter.represent_ordereddict,
  319. )
  320. SafeRepresenter.add_representer(datetime.date, SafeRepresenter.represent_date)
  321. SafeRepresenter.add_representer(datetime.datetime, SafeRepresenter.represent_datetime)
  322. SafeRepresenter.add_representer(None, SafeRepresenter.represent_undefined)
  323. class Representer(SafeRepresenter):
  324. def represent_complex(self, data: Any) -> Any:
  325. if data.imag == 0.0:
  326. data = repr(data.real)
  327. elif data.real == 0.0:
  328. data = f'{data.imag!r}j'
  329. elif data.imag > 0:
  330. data = f'{data.real!r}+{data.imag!r}j'
  331. else:
  332. data = f'{data.real!r}{data.imag!r}j'
  333. return self.represent_scalar('tag:yaml.org,2002:python/complex', data)
  334. def represent_tuple(self, data: Any) -> SequenceNode:
  335. return self.represent_sequence('tag:yaml.org,2002:python/tuple', data)
  336. def represent_name(self, data: Any) -> ScalarNode:
  337. try:
  338. name = f'{data.__module__!s}.{data.__qualname__!s}'
  339. except AttributeError:
  340. # ToDo: check if this can be reached in Py3
  341. name = f'{data.__module__!s}.{data.__name__!s}'
  342. return self.represent_scalar('tag:yaml.org,2002:python/name:' + name, "")
  343. def represent_module(self, data: Any) -> ScalarNode:
  344. return self.represent_scalar('tag:yaml.org,2002:python/module:' + data.__name__, "")
  345. def represent_object(self, data: Any) -> Union[SequenceNode, MappingNode]:
  346. # We use __reduce__ API to save the data. data.__reduce__ returns
  347. # a tuple of length 2-5:
  348. # (function, args, state, listitems, dictitems)
  349. # For reconstructing, we calls function(*args), then set its state,
  350. # listitems, and dictitems if they are not None.
  351. # A special case is when function.__name__ == '__newobj__'. In this
  352. # case we create the object with args[0].__new__(*args).
  353. # Another special case is when __reduce__ returns a string - we don't
  354. # support it.
  355. # We produce a !!python/object, !!python/object/new or
  356. # !!python/object/apply node.
  357. cls = type(data)
  358. if cls in copyreg.dispatch_table:
  359. reduce: Any = copyreg.dispatch_table[cls](data)
  360. elif hasattr(data, '__reduce_ex__'):
  361. reduce = data.__reduce_ex__(2)
  362. elif hasattr(data, '__reduce__'):
  363. reduce = data.__reduce__()
  364. else:
  365. raise RepresenterError(f'cannot represent object: {data!r}')
  366. reduce = (list(reduce) + [None] * 5)[:5]
  367. function, args, state, listitems, dictitems = reduce
  368. args = list(args)
  369. if state is None:
  370. state = {}
  371. if listitems is not None:
  372. listitems = list(listitems)
  373. if dictitems is not None:
  374. dictitems = dict(dictitems)
  375. if function.__name__ == '__newobj__':
  376. function = args[0]
  377. args = args[1:]
  378. tag = 'tag:yaml.org,2002:python/object/new:'
  379. newobj = True
  380. else:
  381. tag = 'tag:yaml.org,2002:python/object/apply:'
  382. newobj = False
  383. try:
  384. function_name = f'{function.__module__!s}.{function.__qualname__!s}'
  385. except AttributeError:
  386. # ToDo: check if this can be reached in Py3
  387. function_name = f'{function.__module__!s}.{function.__name__!s}'
  388. if not args and not listitems and not dictitems and isinstance(state, dict) and newobj:
  389. return self.represent_mapping(
  390. 'tag:yaml.org,2002:python/object:' + function_name, state,
  391. )
  392. if not listitems and not dictitems and isinstance(state, dict) and not state:
  393. return self.represent_sequence(tag + function_name, args)
  394. value = {}
  395. if args:
  396. value['args'] = args
  397. if state or not isinstance(state, dict):
  398. value['state'] = state
  399. if listitems:
  400. value['listitems'] = listitems
  401. if dictitems:
  402. value['dictitems'] = dictitems
  403. return self.represent_mapping(tag + function_name, value)
  404. Representer.add_representer(complex, Representer.represent_complex)
  405. Representer.add_representer(tuple, Representer.represent_tuple)
  406. Representer.add_representer(type, Representer.represent_name)
  407. Representer.add_representer(types.FunctionType, Representer.represent_name)
  408. Representer.add_representer(types.BuiltinFunctionType, Representer.represent_name)
  409. Representer.add_representer(types.ModuleType, Representer.represent_module)
  410. Representer.add_multi_representer(object, Representer.represent_object)
  411. Representer.add_multi_representer(type, Representer.represent_name)
  412. class RoundTripRepresenter(SafeRepresenter):
  413. # need to add type here and write out the .comment
  414. # in serializer and emitter
  415. def __init__(
  416. self, default_style: Any = None, default_flow_style: Any = None, dumper: Any = None,
  417. ) -> None:
  418. if not hasattr(dumper, 'typ') and default_flow_style is None:
  419. default_flow_style = False
  420. SafeRepresenter.__init__(
  421. self,
  422. default_style=default_style,
  423. default_flow_style=default_flow_style,
  424. dumper=dumper,
  425. )
  426. def ignore_aliases(self, data: Any) -> bool:
  427. try:
  428. if data.anchor is not None and data.anchor.value is not None:
  429. return False
  430. except AttributeError:
  431. pass
  432. return SafeRepresenter.ignore_aliases(self, data)
  433. def represent_none(self, data: Any) -> ScalarNode:
  434. if len(self.represented_objects) == 0 and not self.serializer.use_explicit_start:
  435. # this will be open ended (although it is not yet)
  436. return self.represent_scalar('tag:yaml.org,2002:null', 'null')
  437. return self.represent_scalar('tag:yaml.org,2002:null', "")
  438. def represent_literal_scalarstring(self, data: Any) -> ScalarNode:
  439. tag = None
  440. style = '|'
  441. anchor = data.yaml_anchor(any=True)
  442. tag = 'tag:yaml.org,2002:str'
  443. return self.represent_scalar(tag, data, style=style, anchor=anchor)
  444. represent_preserved_scalarstring = represent_literal_scalarstring
  445. def represent_folded_scalarstring(self, data: Any) -> ScalarNode:
  446. tag = None
  447. style = '>'
  448. anchor = data.yaml_anchor(any=True)
  449. for fold_pos in reversed(getattr(data, 'fold_pos', [])):
  450. if (
  451. data[fold_pos] == ' '
  452. and (fold_pos > 0 and not data[fold_pos - 1].isspace())
  453. and (fold_pos < len(data) and not data[fold_pos + 1].isspace())
  454. ):
  455. data = data[:fold_pos] + '\a' + data[fold_pos:]
  456. tag = 'tag:yaml.org,2002:str'
  457. return self.represent_scalar(tag, data, style=style, anchor=anchor)
  458. def represent_single_quoted_scalarstring(self, data: Any) -> ScalarNode:
  459. tag = None
  460. style = "'"
  461. anchor = data.yaml_anchor(any=True)
  462. tag = 'tag:yaml.org,2002:str'
  463. return self.represent_scalar(tag, data, style=style, anchor=anchor)
  464. def represent_double_quoted_scalarstring(self, data: Any) -> ScalarNode:
  465. tag = None
  466. style = '"'
  467. anchor = data.yaml_anchor(any=True)
  468. tag = 'tag:yaml.org,2002:str'
  469. return self.represent_scalar(tag, data, style=style, anchor=anchor)
  470. def represent_plain_scalarstring(self, data: Any) -> ScalarNode:
  471. tag = None
  472. style = ''
  473. anchor = data.yaml_anchor(any=True)
  474. tag = 'tag:yaml.org,2002:str'
  475. return self.represent_scalar(tag, data, style=style, anchor=anchor)
  476. def insert_underscore(
  477. self, prefix: Any, s: Any, underscore: Any, anchor: Any = None,
  478. ) -> ScalarNode:
  479. if underscore is None:
  480. return self.represent_scalar('tag:yaml.org,2002:int', prefix + s, anchor=anchor)
  481. if underscore[0]:
  482. sl = list(s)
  483. pos = len(s) - underscore[0]
  484. while pos > 0:
  485. sl.insert(pos, '_')
  486. pos -= underscore[0]
  487. s = "".join(sl)
  488. if underscore[1]:
  489. s = '_' + s
  490. if underscore[2]:
  491. s += '_'
  492. return self.represent_scalar('tag:yaml.org,2002:int', prefix + s, anchor=anchor)
  493. def represent_scalar_int(self, data: Any) -> ScalarNode:
  494. if data._width is not None:
  495. s = f'{data:0{data._width}d}'
  496. else:
  497. s = format(data, 'd')
  498. anchor = data.yaml_anchor(any=True)
  499. return self.insert_underscore("", s, data._underscore, anchor=anchor)
  500. def represent_binary_int(self, data: Any) -> ScalarNode:
  501. if data._width is not None:
  502. # cannot use '{:#0{}b}', that strips the zeros
  503. s = f'{data:0{data._width}b}'
  504. else:
  505. s = format(data, 'b')
  506. anchor = data.yaml_anchor(any=True)
  507. return self.insert_underscore('0b', s, data._underscore, anchor=anchor)
  508. def represent_octal_int(self, data: Any) -> ScalarNode:
  509. if data._width is not None:
  510. # cannot use '{:#0{}o}', that strips the zeros
  511. s = f'{data:0{data._width}o}'
  512. else:
  513. s = format(data, 'o')
  514. anchor = data.yaml_anchor(any=True)
  515. prefix = '0o'
  516. if getattr(self.serializer, 'use_version', None) == (1, 1):
  517. prefix = '0'
  518. return self.insert_underscore(prefix, s, data._underscore, anchor=anchor)
  519. def represent_hex_int(self, data: Any) -> ScalarNode:
  520. if data._width is not None:
  521. # cannot use '{:#0{}x}', that strips the zeros
  522. s = f'{data:0{data._width}x}'
  523. else:
  524. s = format(data, 'x')
  525. anchor = data.yaml_anchor(any=True)
  526. return self.insert_underscore('0x', s, data._underscore, anchor=anchor)
  527. def represent_hex_caps_int(self, data: Any) -> ScalarNode:
  528. if data._width is not None:
  529. # cannot use '{:#0{}X}', that strips the zeros
  530. s = f'{data:0{data._width}X}'
  531. else:
  532. s = format(data, 'X')
  533. anchor = data.yaml_anchor(any=True)
  534. return self.insert_underscore('0x', s, data._underscore, anchor=anchor)
  535. def represent_scalar_float(self, data: Any) -> ScalarNode:
  536. """ this is way more complicated """
  537. value = None
  538. anchor = data.yaml_anchor(any=True)
  539. if data != data or (data == 0.0 and data == 1.0):
  540. value = '.nan'
  541. elif data == self.inf_value:
  542. value = '.inf'
  543. elif data == -self.inf_value:
  544. value = '-.inf'
  545. if value:
  546. return self.represent_scalar('tag:yaml.org,2002:float', value, anchor=anchor)
  547. if data._exp is None and data._prec > 0 and data._prec == data._width - 1:
  548. # no exponent, but trailing dot
  549. value = f'{data._m_sign if data._m_sign else ""}{abs(int(data)):d}.'
  550. elif data._exp is None:
  551. # no exponent, "normal" dot
  552. prec = data._prec
  553. ms = data._m_sign if data._m_sign else ""
  554. if prec < 0:
  555. value = f'{ms}{abs(int(data)):0{data._width - len(ms)}d}'
  556. else:
  557. # -1 for the dot
  558. value = f'{ms}{abs(data):0{data._width - len(ms)}.{data._width - prec - 1}f}'
  559. if prec == 0 or (prec == 1 and ms != ""):
  560. value = value.replace('0.', '.')
  561. while len(value) < data._width:
  562. value += '0'
  563. else:
  564. # exponent
  565. (
  566. m,
  567. es,
  568. ) = f'{data:{data._width}.{data._width + (1 if data._m_sign else 0)}e}'.split('e')
  569. w = data._width if data._prec > 0 else (data._width + 1)
  570. if data < 0:
  571. w += 1
  572. m = m[:w]
  573. e = int(es)
  574. m1, m2 = m.split('.') # always second?
  575. while len(m1) + len(m2) < data._width - (1 if data._prec >= 0 else 0):
  576. m2 += '0'
  577. if data._m_sign and data > 0:
  578. m1 = '+' + m1
  579. esgn = '+' if data._e_sign else ""
  580. if data._prec < 0: # mantissa without dot
  581. if m2 != '0':
  582. e -= len(m2)
  583. else:
  584. m2 = ""
  585. while (len(m1) + len(m2) - (1 if data._m_sign else 0)) < data._width:
  586. m2 += '0'
  587. e -= 1
  588. value = m1 + m2 + data._exp + f'{e:{esgn}0{data._e_width}d}'
  589. elif data._prec == 0: # mantissa with trailing dot
  590. e -= len(m2)
  591. value = m1 + m2 + '.' + data._exp + f'{e:{esgn}0{data._e_width}d}'
  592. else:
  593. if data._m_lead0 > 0:
  594. m2 = '0' * (data._m_lead0 - 1) + m1 + m2
  595. m1 = '0'
  596. m2 = m2[: -data._m_lead0] # these should be zeros
  597. e += data._m_lead0
  598. while len(m1) < data._prec:
  599. m1 += m2[0]
  600. m2 = m2[1:]
  601. e -= 1
  602. value = m1 + '.' + m2 + data._exp + f'{e:{esgn}0{data._e_width}d}'
  603. if value is None:
  604. value = repr(data).lower()
  605. return self.represent_scalar('tag:yaml.org,2002:float', value, anchor=anchor)
  606. def represent_sequence(
  607. self, tag: Any, sequence: Any, flow_style: Any = None,
  608. ) -> SequenceNode:
  609. value: List[Any] = []
  610. # if the flow_style is None, the flow style tacked on to the object
  611. # explicitly will be taken. If that is None as well the default flow
  612. # style rules
  613. try:
  614. flow_style = sequence.fa.flow_style(flow_style)
  615. except AttributeError:
  616. flow_style = flow_style
  617. try:
  618. anchor = sequence.yaml_anchor()
  619. except AttributeError:
  620. anchor = None
  621. if isinstance(tag, str):
  622. tag = Tag(suffix=tag)
  623. node = SequenceNode(tag, value, flow_style=flow_style, anchor=anchor)
  624. if self.alias_key is not None:
  625. self.represented_objects[self.alias_key] = node
  626. best_style = True
  627. try:
  628. comment = getattr(sequence, comment_attrib)
  629. node.comment = comment.comment
  630. # reset any comment already printed information
  631. if node.comment and node.comment[1]:
  632. for ct in node.comment[1]:
  633. ct.reset()
  634. item_comments = comment.items
  635. for v in item_comments.values():
  636. if v and v[1]:
  637. for ct in v[1]:
  638. ct.reset()
  639. item_comments = comment.items
  640. if node.comment is None:
  641. node.comment = comment.comment
  642. else:
  643. # as we are potentially going to extend this, make a new list
  644. node.comment = comment.comment[:]
  645. try:
  646. node.comment.append(comment.end)
  647. except AttributeError:
  648. pass
  649. except AttributeError:
  650. item_comments = {}
  651. for idx, item in enumerate(sequence):
  652. node_item = self.represent_data(item)
  653. self.merge_comments(node_item, item_comments.get(idx))
  654. if not (isinstance(node_item, ScalarNode) and not node_item.style):
  655. best_style = False
  656. value.append(node_item)
  657. if flow_style is None:
  658. if len(sequence) != 0 and self.default_flow_style is not None:
  659. node.flow_style = self.default_flow_style
  660. else:
  661. node.flow_style = best_style
  662. return node
  663. def merge_comments(self, node: Any, comments: Any) -> Any:
  664. if comments is None:
  665. assert hasattr(node, 'comment')
  666. return node
  667. if getattr(node, 'comment', None) is not None:
  668. for idx, val in enumerate(comments):
  669. if idx >= len(node.comment):
  670. continue
  671. nc = node.comment[idx]
  672. if nc is not None:
  673. assert val is None or val == nc
  674. comments[idx] = nc
  675. node.comment = comments
  676. return node
  677. def represent_key(self, data: Any) -> Any:
  678. if isinstance(data, CommentedKeySeq):
  679. self.alias_key = None
  680. return self.represent_sequence('tag:yaml.org,2002:seq', data, flow_style=True)
  681. if isinstance(data, CommentedKeyMap):
  682. self.alias_key = None
  683. return self.represent_mapping('tag:yaml.org,2002:map', data, flow_style=True)
  684. return SafeRepresenter.represent_key(self, data)
  685. def represent_mapping(self, tag: Any, mapping: Any, flow_style: Any = None) -> MappingNode:
  686. value: List[Any] = []
  687. try:
  688. flow_style = mapping.fa.flow_style(flow_style)
  689. except AttributeError:
  690. flow_style = flow_style
  691. try:
  692. anchor = mapping.yaml_anchor()
  693. except AttributeError:
  694. anchor = None
  695. if isinstance(tag, str):
  696. tag = Tag(suffix=tag)
  697. node = MappingNode(tag, value, flow_style=flow_style, anchor=anchor)
  698. if self.alias_key is not None:
  699. self.represented_objects[self.alias_key] = node
  700. best_style = True
  701. # no sorting! !!
  702. try:
  703. comment = getattr(mapping, comment_attrib)
  704. if node.comment is None:
  705. node.comment = comment.comment
  706. else:
  707. # as we are potentially going to extend this, make a new list
  708. node.comment = comment.comment[:]
  709. if node.comment and node.comment[1]:
  710. for ct in node.comment[1]:
  711. ct.reset()
  712. item_comments = comment.items
  713. if self.dumper.comment_handling is None:
  714. for v in item_comments.values():
  715. if v and v[1]:
  716. for ct in v[1]:
  717. ct.reset()
  718. try:
  719. node.comment.append(comment.end)
  720. except AttributeError:
  721. pass
  722. else:
  723. # NEWCMNT
  724. pass
  725. except AttributeError:
  726. item_comments = {}
  727. merge_list = [m[1] for m in getattr(mapping, merge_attrib, [])]
  728. try:
  729. merge_pos = getattr(mapping, merge_attrib, [[0]])[0][0]
  730. except IndexError:
  731. merge_pos = 0
  732. item_count = 0
  733. if bool(merge_list):
  734. items = mapping.non_merged_items()
  735. else:
  736. items = mapping.items()
  737. for item_key, item_value in items:
  738. item_count += 1
  739. node_key = self.represent_key(item_key)
  740. node_value = self.represent_data(item_value)
  741. item_comment = item_comments.get(item_key)
  742. if item_comment:
  743. # assert getattr(node_key, 'comment', None) is None
  744. # issue 351 did throw this because the comment from the list item was
  745. # moved to the dict
  746. node_key.comment = item_comment[:2]
  747. nvc = getattr(node_value, 'comment', None)
  748. if nvc is not None: # end comment already there
  749. nvc[0] = item_comment[2]
  750. nvc[1] = item_comment[3]
  751. else:
  752. node_value.comment = item_comment[2:]
  753. if not (isinstance(node_key, ScalarNode) and not node_key.style):
  754. best_style = False
  755. if not (isinstance(node_value, ScalarNode) and not node_value.style):
  756. best_style = False
  757. value.append((node_key, node_value))
  758. if flow_style is None:
  759. if ((item_count != 0) or bool(merge_list)) and self.default_flow_style is not None:
  760. node.flow_style = self.default_flow_style
  761. else:
  762. node.flow_style = best_style
  763. if bool(merge_list):
  764. # because of the call to represent_data here, the anchors
  765. # are marked as being used and thereby created
  766. if len(merge_list) == 1:
  767. arg = self.represent_data(merge_list[0])
  768. else:
  769. arg = self.represent_data(merge_list)
  770. arg.flow_style = True
  771. value.insert(
  772. merge_pos, (ScalarNode(Tag(suffix='tag:yaml.org,2002:merge'), '<<'), arg),
  773. )
  774. return node
  775. def represent_omap(self, tag: Any, omap: Any, flow_style: Any = None) -> SequenceNode:
  776. value: List[Any] = []
  777. try:
  778. flow_style = omap.fa.flow_style(flow_style)
  779. except AttributeError:
  780. flow_style = flow_style
  781. try:
  782. anchor = omap.yaml_anchor()
  783. except AttributeError:
  784. anchor = None
  785. if isinstance(tag, str):
  786. tag = Tag(suffix=tag)
  787. node = SequenceNode(tag, value, flow_style=flow_style, anchor=anchor)
  788. if self.alias_key is not None:
  789. self.represented_objects[self.alias_key] = node
  790. best_style = True
  791. try:
  792. comment = getattr(omap, comment_attrib)
  793. if node.comment is None:
  794. node.comment = comment.comment
  795. else:
  796. # as we are potentially going to extend this, make a new list
  797. node.comment = comment.comment[:]
  798. if node.comment and node.comment[1]:
  799. for ct in node.comment[1]:
  800. ct.reset()
  801. item_comments = comment.items
  802. for v in item_comments.values():
  803. if v and v[1]:
  804. for ct in v[1]:
  805. ct.reset()
  806. try:
  807. node.comment.append(comment.end)
  808. except AttributeError:
  809. pass
  810. except AttributeError:
  811. item_comments = {}
  812. for item_key in omap:
  813. item_val = omap[item_key]
  814. node_item = self.represent_data({item_key: item_val})
  815. # node_item.flow_style = False
  816. # node item has two scalars in value: node_key and node_value
  817. item_comment = item_comments.get(item_key)
  818. if item_comment:
  819. if item_comment[1]:
  820. node_item.comment = [None, item_comment[1]]
  821. assert getattr(node_item.value[0][0], 'comment', None) is None
  822. node_item.value[0][0].comment = [item_comment[0], None]
  823. nvc = getattr(node_item.value[0][1], 'comment', None)
  824. if nvc is not None: # end comment already there
  825. nvc[0] = item_comment[2]
  826. nvc[1] = item_comment[3]
  827. else:
  828. node_item.value[0][1].comment = item_comment[2:]
  829. # if not (isinstance(node_item, ScalarNode) \
  830. # and not node_item.style):
  831. # best_style = False
  832. value.append(node_item)
  833. if flow_style is None:
  834. if self.default_flow_style is not None:
  835. node.flow_style = self.default_flow_style
  836. else:
  837. node.flow_style = best_style
  838. return node
  839. def represent_set(self, setting: Any) -> MappingNode:
  840. flow_style = False
  841. tag = Tag(suffix='tag:yaml.org,2002:set')
  842. # return self.represent_mapping(tag, value)
  843. value: List[Any] = []
  844. flow_style = setting.fa.flow_style(flow_style)
  845. try:
  846. anchor = setting.yaml_anchor()
  847. except AttributeError:
  848. anchor = None
  849. node = MappingNode(tag, value, flow_style=flow_style, anchor=anchor)
  850. if self.alias_key is not None:
  851. self.represented_objects[self.alias_key] = node
  852. best_style = True
  853. # no sorting! !!
  854. try:
  855. comment = getattr(setting, comment_attrib)
  856. if node.comment is None:
  857. node.comment = comment.comment
  858. else:
  859. # as we are potentially going to extend this, make a new list
  860. node.comment = comment.comment[:]
  861. if node.comment and node.comment[1]:
  862. for ct in node.comment[1]:
  863. ct.reset()
  864. item_comments = comment.items
  865. for v in item_comments.values():
  866. if v and v[1]:
  867. for ct in v[1]:
  868. ct.reset()
  869. try:
  870. node.comment.append(comment.end)
  871. except AttributeError:
  872. pass
  873. except AttributeError:
  874. item_comments = {}
  875. for item_key in setting.odict:
  876. node_key = self.represent_key(item_key)
  877. node_value = self.represent_data(None)
  878. item_comment = item_comments.get(item_key)
  879. if item_comment:
  880. assert getattr(node_key, 'comment', None) is None
  881. node_key.comment = item_comment[:2]
  882. node_key.style = '?'
  883. node_value.style = '-' if flow_style else '?'
  884. if not (isinstance(node_key, ScalarNode) and not node_key.style):
  885. best_style = False
  886. if not (isinstance(node_value, ScalarNode) and not node_value.style):
  887. best_style = False
  888. value.append((node_key, node_value))
  889. best_style = best_style
  890. return node
  891. def represent_dict(self, data: Any) -> MappingNode:
  892. """write out tag if saved on loading"""
  893. try:
  894. _ = data.tag
  895. except AttributeError:
  896. tag = Tag(suffix='tag:yaml.org,2002:map')
  897. else:
  898. if data.tag.trval:
  899. if data.tag.startswith('!!'):
  900. tag = Tag(suffix='tag:yaml.org,2002:' + data.tag.trval[2:])
  901. else:
  902. tag = data.tag
  903. else:
  904. tag = Tag(suffix='tag:yaml.org,2002:map')
  905. return self.represent_mapping(tag, data)
  906. def represent_list(self, data: Any) -> SequenceNode:
  907. try:
  908. _ = data.tag
  909. except AttributeError:
  910. tag = Tag(suffix='tag:yaml.org,2002:seq')
  911. else:
  912. if data.tag.trval:
  913. if data.tag.startswith('!!'):
  914. tag = Tag(suffix='tag:yaml.org,2002:' + data.tag.trval[2:])
  915. else:
  916. tag = data.tag
  917. else:
  918. tag = Tag(suffix='tag:yaml.org,2002:seq')
  919. return self.represent_sequence(tag, data)
  920. def represent_datetime(self, data: Any) -> ScalarNode:
  921. inter = 'T' if data._yaml['t'] else ' '
  922. _yaml = data._yaml
  923. if _yaml['delta']:
  924. data += _yaml['delta']
  925. value = data.isoformat(inter)
  926. else:
  927. value = data.isoformat(inter)
  928. if _yaml['tz']:
  929. value += _yaml['tz']
  930. return self.represent_scalar('tag:yaml.org,2002:timestamp', value)
  931. def represent_tagged_scalar(self, data: Any) -> ScalarNode:
  932. try:
  933. if data.tag.handle == '!!':
  934. tag = f'{data.tag.handle} {data.tag.suffix}'
  935. else:
  936. tag = data.tag
  937. except AttributeError:
  938. tag = None
  939. try:
  940. anchor = data.yaml_anchor()
  941. except AttributeError:
  942. anchor = None
  943. return self.represent_scalar(tag, data.value, style=data.style, anchor=anchor)
  944. def represent_scalar_bool(self, data: Any) -> ScalarNode:
  945. try:
  946. anchor = data.yaml_anchor()
  947. except AttributeError:
  948. anchor = None
  949. return SafeRepresenter.represent_bool(self, data, anchor=anchor)
  950. def represent_yaml_object(
  951. self, tag: Any, data: Any, cls: Any, flow_style: Optional[Any] = None,
  952. ) -> MappingNode:
  953. if hasattr(data, '__getstate__'):
  954. state = data.__getstate__()
  955. else:
  956. state = data.__dict__.copy()
  957. anchor = state.pop(Anchor.attrib, None)
  958. res = self.represent_mapping(tag, state, flow_style=flow_style)
  959. if anchor is not None:
  960. res.anchor = anchor
  961. return res
  962. RoundTripRepresenter.add_representer(type(None), RoundTripRepresenter.represent_none)
  963. RoundTripRepresenter.add_representer(
  964. LiteralScalarString, RoundTripRepresenter.represent_literal_scalarstring,
  965. )
  966. RoundTripRepresenter.add_representer(
  967. FoldedScalarString, RoundTripRepresenter.represent_folded_scalarstring,
  968. )
  969. RoundTripRepresenter.add_representer(
  970. SingleQuotedScalarString, RoundTripRepresenter.represent_single_quoted_scalarstring,
  971. )
  972. RoundTripRepresenter.add_representer(
  973. DoubleQuotedScalarString, RoundTripRepresenter.represent_double_quoted_scalarstring,
  974. )
  975. RoundTripRepresenter.add_representer(
  976. PlainScalarString, RoundTripRepresenter.represent_plain_scalarstring,
  977. )
  978. # RoundTripRepresenter.add_representer(tuple, Representer.represent_tuple)
  979. RoundTripRepresenter.add_representer(ScalarInt, RoundTripRepresenter.represent_scalar_int)
  980. RoundTripRepresenter.add_representer(BinaryInt, RoundTripRepresenter.represent_binary_int)
  981. RoundTripRepresenter.add_representer(OctalInt, RoundTripRepresenter.represent_octal_int)
  982. RoundTripRepresenter.add_representer(HexInt, RoundTripRepresenter.represent_hex_int)
  983. RoundTripRepresenter.add_representer(HexCapsInt, RoundTripRepresenter.represent_hex_caps_int)
  984. RoundTripRepresenter.add_representer(ScalarFloat, RoundTripRepresenter.represent_scalar_float)
  985. RoundTripRepresenter.add_representer(ScalarBoolean, RoundTripRepresenter.represent_scalar_bool)
  986. RoundTripRepresenter.add_representer(CommentedSeq, RoundTripRepresenter.represent_list)
  987. RoundTripRepresenter.add_representer(CommentedMap, RoundTripRepresenter.represent_dict)
  988. RoundTripRepresenter.add_representer(
  989. CommentedOrderedMap, RoundTripRepresenter.represent_ordereddict,
  990. )
  991. RoundTripRepresenter.add_representer(
  992. collections.OrderedDict, RoundTripRepresenter.represent_ordereddict,
  993. )
  994. RoundTripRepresenter.add_representer(CommentedSet, RoundTripRepresenter.represent_set)
  995. RoundTripRepresenter.add_representer(
  996. TaggedScalar, RoundTripRepresenter.represent_tagged_scalar,
  997. )
  998. RoundTripRepresenter.add_representer(TimeStamp, RoundTripRepresenter.represent_datetime)