def yaml_map_node(): # A yaml.MappingNode representing a mapping of mappings tag1 = 'tag:yaml.org,2002:map' item1_key1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'price') item1_value1_node = yaml.ScalarNode('tag:yaml.org,2002:float', '100.0') value1 = [(item1_key1_node, item1_value1_node)] item1 = yaml.MappingNode(tag1, value1) key1 = yaml.ScalarNode('tag:yaml.org,2002:str', 'item1') tag2 = 'tag:yaml.org,2002:map' item2_key1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'price') item2_value1_node = yaml.ScalarNode('tag:yaml.org,2002:float', '200.0') value2 = [(item2_key1_node, item2_value1_node)] item2 = yaml.MappingNode(tag2, value2) key2 = yaml.ScalarNode('tag:yaml.org,2002:str', 'item2') item3 = yaml.ScalarNode('tag:yaml.org,2002:float', '150.0') key3 = yaml.ScalarNode('tag:yaml.org,2002:str', 'item3') outer_map_value = [(key1, item1), (key2, item2), (key3, item3)] outer_tag = 'tag:yaml.org,2002:map' outer_map = yaml.MappingNode(outer_tag, outer_map_value) return outer_map
def yaml_seq_node(): # A yaml.SequenceNode representing a sequence of mappings tag1 = 'tag:yaml.org,2002:map' item1_key1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item_id') item1_value1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item1') item1_key2_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'price') item1_value2_node = yaml.ScalarNode('tag:yaml.org,2002:float', '100.0') value1 = [(item1_key1_node, item1_value1_node), (item1_key2_node, item1_value2_node)] item1 = yaml.MappingNode(tag1, value1) tag2 = 'tag:yaml.org,2002:map' item2_key1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item_id') item2_value1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item2') item2_key2_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'price') item2_value2_node = yaml.ScalarNode('tag:yaml.org,2002:float', '200.0') item2_key3_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'on_sale') item2_value3_node = yaml.ScalarNode('tag:yaml.org,2002:bool', 'True') value2 = [(item2_key1_node, item2_value1_node), (item2_key2_node, item2_value2_node), (item2_key3_node, item2_value3_node)] item2 = yaml.MappingNode(tag2, value2) return yaml.SequenceNode('tag:yaml.org,2002:seq', [item1, item2])
def represent_odict(self, mapping, flow_style=None): # pragma: no cover """https://gist.github.com/miracle2k/3184458 Make PyYAML output an OrderedDict. It will do so fine if you use yaml.dump(), but that generates ugly, non-standard YAML code. To use yaml.safe_dump(), you need the following. """ tag = YAML_MAP_TAG value = [] node = yaml.MappingNode(tag, value, flow_style=flow_style) if self.alias_key is not None: self.represented_objects[self.alias_key] = node best_style = True if hasattr(mapping, "items"): mapping = mapping.items() for item_key, item_value in mapping: node_key = self.represent_data(item_key) node_value = self.represent_data(item_value) if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style): best_style = False if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style): best_style = False value.append((node_key, node_value)) if flow_style is None: if self.default_flow_style is not None: node.flow_style = self.default_flow_style else: node.flow_style = best_style return node
def yaml_node(yaml_seq_node: yaml.Node, yaml_map_node: yaml.Node, yaml_index_node: yaml.Node) -> yaml.Node: tag = 'tag:yaml.org,2002:map' attr1_key_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'attr1') attr1_value_node = yaml.ScalarNode('tag:yaml.org,2002:int', '42') null_attr_key_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'null_attr') null_attr_value_node = yaml.ScalarNode('tag:yaml.org,2002:null', '') list1_key_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'list1') dict1_key_node = yaml.ScalarNode('tag:yaml.org,2002:map', 'dict1') index1_key_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'index1') dashed_key_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'dashed-attr') dashed_value_node = yaml.ScalarNode('tag:yaml.org,2002:int', '13') undered_key_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'undered_attr') undered_value_node = yaml.ScalarNode('tag:yaml.org,2002:float', '13.0') value = [(attr1_key_node, attr1_value_node), (null_attr_key_node, null_attr_value_node), (list1_key_node, yaml_seq_node), (dict1_key_node, yaml_map_node), (index1_key_node, yaml_index_node), (dashed_key_node, dashed_value_node), (undered_key_node, undered_value_node)] return yaml.MappingNode(tag, value)
def yaml_index_node() -> yaml.Node: # A yaml.MappingNode representing a mapping of mappings indexed # by item id tag1 = 'tag:yaml.org,2002:map' item1_key1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item_id') item1_value1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item1') item1_key2_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'price') item1_value2_node = yaml.ScalarNode('tag:yaml.org,2002:float', '100.0') value1 = [(item1_key1_node, item1_value1_node), (item1_key2_node, item1_value2_node)] item1 = yaml.MappingNode(tag1, value1) key1 = yaml.ScalarNode('tag:yaml.org,2002:str', 'item1') tag2 = 'tag:yaml.org,2002:map' item2_key1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item_id') item2_value1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item2') item2_key2_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'price') item2_value2_node = yaml.ScalarNode('tag:yaml.org,2002:float', '200.0') value2 = [(item2_key1_node, item2_value1_node), (item2_key2_node, item2_value2_node)] item2 = yaml.MappingNode(tag2, value2) key2 = yaml.ScalarNode('tag:yaml.org,2002:str', 'item2') tag3 = 'tag:yaml.org,2002:map' item3_key1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item_id') item3_value1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item3') item3_key2_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'price') item3_value2_node = yaml.ScalarNode('tag:yaml.org,2002:float', '150.0') item3_key3_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'on_sale') item3_value3_node = yaml.ScalarNode('tag:yaml.org,2002:bool', 'true') value3 = [(item3_key1_node, item3_value1_node), (item3_key2_node, item3_value2_node), (item3_key3_node, item3_value3_node)] item3 = yaml.MappingNode(tag3, value3) key3 = yaml.ScalarNode('tag:yaml.org,2002:str', 'item3') outer_map_value = [(key1, item1), (key2, item2), (key3, item3)] outer_tag = 'tag:yaml.org,2002:map' outer_map = yaml.MappingNode(outer_tag, outer_map_value) return outer_map
def make_mapping(self) -> None: """Replaces the node with a new, empty mapping. Note that this will work on the Node object that is passed to \ a yatiml_savorize() or yatiml_sweeten() function, but not on \ any of its attributes or items. If you need to set an attribute \ to a complex value, build a yaml.Node representing it and use \ set_attribute with that. """ start_mark = StreamMark('generated node', 0, 0, 0) end_mark = StreamMark('generated node', 0, 0, 0) self.yaml_node = yaml.MappingNode('tag:yaml.org,2002:map', list(), start_mark, end_mark)
def class_node_dup_key(): # A Node wrapping a yaml.SequenceNode representing a sequence of # mappings with a duplicate key. tag1 = 'tag:yaml.org,2002:map' item1_key1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item_id') item1_value1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item') value1 = [(item1_key1_node, item1_value1_node)] item1 = yaml.MappingNode(tag1, value1) tag2 = 'tag:yaml.org,2002:map' item2_key1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item_id') item2_value1_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'item') value2 = [(item2_key1_node, item2_value1_node)] item2 = yaml.MappingNode(tag2, value2) seq_node = yaml.SequenceNode('tag:yaml.org,2002:seq', [item1, item2]) list1_key_node = yaml.ScalarNode('tag:yaml.org,2002:str', 'dup_list') value = [(list1_key_node, seq_node)] map_node = yaml.MappingNode('tag:yaml.org,2002:map', value) return yatiml.Node(map_node)
def seq_attribute_to_map(self, attribute: str, key_attribute: str, value_attribute: Optional[str] = None, strict: Optional[bool] = True) -> None: """Converts a sequence attribute to a map. This function takes an attribute of this Node that is \ a sequence of mappings and turns it into a mapping of mappings. \ It assumes that each of the mappings in the original sequence \ has an attribute containing a unique value, which it will use \ as a key for the new outer mapping. An example probably helps. If you have a Node representing \ this piece of YAML:: items: - item_id: item1 description: Basic widget price: 100.0 - item_id: item2 description: Premium quality widget price: 200.0 and call seq_attribute_to_map('items', 'item_id'), then the \ Node will be modified to represent this:: items: item1: description: Basic widget price: 100.0 item2: description: Premium quality widget price: 200.0 which is often more intuitive for people to read and write. If the attribute does not exist, or is not a sequence of \ mappings, this function will silently do nothing. If the keys \ are not unique and strict is False, it will also do nothing. If \ the keys are not unique and strict is True, it will raise an \ error. With thanks to the makers of the Common Workflow Language for \ the idea. Args: attribute: Name of the attribute whose value to modify. key_attribute: Name of the attribute in each item to use \ as a key for the new mapping. strict: Whether to give an error if the intended keys are \ not unique. Raises: SeasoningError: If the keys are not unique and strict is \ True. """ if not self.has_attribute(attribute): return attr_node = self.get_attribute(attribute) if not attr_node.is_sequence(): return start_mark = attr_node.yaml_node.start_mark end_mark = attr_node.yaml_node.end_mark # check that all list items are mappings and that the keys are unique # strings seen_keys = set() # type: Set[str] for item in attr_node.seq_items(): key_attr_node = item.get_attribute(key_attribute) if not key_attr_node.is_scalar(str): raise SeasoningError( ('Attribute names must be strings in' 'YAtiML, {} is not a string.').format(key_attr_node)) if key_attr_node.get_value() in seen_keys: if strict: raise SeasoningError( ('Found a duplicate key {}: {} when' ' converting from sequence to mapping'.format( key_attribute, key_attr_node.get_value()))) return seen_keys.add(key_attr_node.get_value()) # type: ignore # construct mapping mapping_values = list() for item in attr_node.seq_items(): # we've already checked that it's a SequenceNode above key_node = item.get_attribute(key_attribute).yaml_node item.remove_attribute(key_attribute) if value_attribute is not None: value_node = item.get_attribute(value_attribute).yaml_node if len(item.yaml_node.value) == 1: # no other attributes, use short form mapping_values.append((key_node, value_node)) else: mapping_values.append((key_node, item.yaml_node)) else: mapping_values.append((key_node, item.yaml_node)) # create mapping node mapping = yaml.MappingNode('tag:yaml.org,2002:map', mapping_values, start_mark, end_mark) self.set_attribute(attribute, mapping)
def map_attribute_to_index(self, attribute: str, key_attribute: str, value_attribute: Optional[str] = None) -> None: """Converts a mapping attribute to an index . It is often convenient to represent a collection of objects by a dict mapping a name of each object to that object (let's call that an *index*). If each object knows its own name, then the name is stored twice, which is not nice to have to type and keep synchronised in YAML. In YAML, such an index is a mapping of mappings, where the key of the outer mapping matches the value of one of the items in the corresponding inner mapping. This function enables a short-hand notation for the above, where the name of the object is mentioned only in the key of the mapping and not again in the values, and, if ``value_attribute`` is specified, converting any entries with a scalar value (rather than a mapping) to a mapping with ''value_attribute'' as the key and the original value as the value. An example probably helps. Let's say we have a class ``Employee`` and a ``Company`` which has employees: .. code-block:: python class Employee: def __init__( self, name: str, role: str, hours: int = 40 ) -> None: ... class Company: def __init__( self, employees: Dict[str, Employee] ) -> None: ... my_company = Company({ 'Mary': Employee('Mary', 'Director'), 'Vishnu': Employee('Vishnu', 'Sales'), 'Susan': Employee('Susan', 'Engineering')}) By default, to load this from YAML, you have to write: .. code-block:: yaml employees: Mary: name: Mary role: Director Vishnu: name: Vishnu role: Sales Susan: name: Susan role: Engineering If you call ``node.map_attribute_to_index('employees', 'name')`` in ``Company._yatiml_savorize()``, then the following will also work: .. code-block:: yaml employees: Mary: role: Director Vishnu: role: Sales Susan: role: Engineering And if you call ``node.map_attribute_to_index('employees', 'name', 'role')`` then you can also write: .. code-block:: yaml employees: Mary: Director Vishnu: Sales Susan: Engineering If the attribute does not exist, or is not a mapping of mappings, this function will silently do nothing. See :meth:`index_attribute_to_map` for the reverse. With thanks to the makers of the Common Workflow Language for the idea. Args: attribute: Name of the attribute whose value to modify. key_attribute: Name of the attribute in each item to add, with the value of the key. value_attribute: Name of the attribute in each item to use for the value in the new mapping, if only a key and value have been given. """ if not self.has_attribute(attribute): return attr_node = self.get_attribute(attribute) if not attr_node.is_mapping(): return new_value = list() for key_node, value_node in attr_node.yaml_node.value: if not isinstance(value_node, yaml.MappingNode): if value_attribute is not None: new_key = yaml.ScalarNode('tag:yaml.org,2002:str', value_attribute, value_node.start_mark, value_node.end_mark) new_mapping = yaml.MappingNode('tag:yaml.org,2002:map', [(new_key, value_node)], value_node.start_mark, value_node.end_mark) else: new_mapping = value_node if isinstance(new_mapping, yaml.MappingNode): key_key = yaml.ScalarNode('tag:yaml.org,2002:str', key_attribute, key_node.start_mark, key_node.end_mark) new_mapping.value.append((key_key, copy(key_node))) new_value.append((key_node, new_mapping)) attr_node.yaml_node.value = new_value