Exemplo n.º 1
0
 def test_aoh_is_inconsistent(self):
     assert False == Nodes.node_is_aoh([{"key": "value"}, None])
Exemplo n.º 2
0
 def test_aoh_node_is_not_list(self):
     assert False == Nodes.node_is_aoh({"key": "value"})
Exemplo n.º 3
0
 def test_aoh_node_is_none(self):
     assert False == Nodes.node_is_aoh(None)
Exemplo n.º 4
0
    def min(data: Any, invert: bool, parameters: List[str],
            yaml_path: YAMLPath,
            **kwargs: Any) -> Generator[NodeCoords, None, None]:
        """
        Find whichever nodes/elements have a minimum value.

        Parameters:
        1. data (Any) The data to evaluate
        2. invert (bool) Invert the evaluation
        3. parameters (List[str]) Parsed parameters
        4. yaml_path (YAMLPath) YAML Path begetting this operation

        Keyword Arguments:
        * parent (ruamel.yaml node) The parent node from which this query
          originates
        * parentref (Any) The Index or Key of data within parent
        * relay_segment (PathSegment) YAML Path segment presently under
          evaluation
        * translated_path (YAMLPath) YAML Path indicating precisely which node
          is being evaluated
        * ancestry (List[AncestryEntry]) Stack of ancestors preceding the
          present node under evaluation

        Returns:  (Generator[NodeCoords, None, None]) each result as it is
            generated
        """
        parent: Any = kwargs.pop("parent", None)
        parentref: Any = kwargs.pop("parentref", None)
        translated_path: YAMLPath = kwargs.pop("translated_path", YAMLPath(""))
        ancestry: List[AncestryEntry] = kwargs.pop("ancestry", [])
        relay_segment: PathSegment = kwargs.pop("relay_segment", None)

        # There may be 0 or 1 parameters
        param_count = len(parameters)
        if param_count > 1:
            raise YAMLPathException(
                ("Invalid parameter count to {}([NAME]); up to {} permitted, "
                 " got {} in YAML Path").format(PathSearchKeywords.MIN, 1,
                                                param_count), str(yaml_path))

        scan_node = parameters[0] if param_count > 0 else None
        match_value: Any = None
        match_nodes: List[NodeCoords] = []
        discard_nodes: List[NodeCoords] = []
        unwrapped_data: Any = NodeCoords.unwrap_node_coords(data)
        if Nodes.node_is_aoh(unwrapped_data, accept_nulls=True):
            # A named child node is mandatory
            if scan_node is None:
                raise YAMLPathException((
                    "The {}([NAME]) Search Keyword requires a key name to scan"
                    " when evaluating an Array-of-Hashes in YAML Path").format(
                        PathSearchKeywords.MIN), str(yaml_path))

            for idx, wrapped_ele in enumerate(data):
                ele = NodeCoords.unwrap_node_coords(wrapped_ele)
                next_path = translated_path + "[{}]".format(idx)
                next_ancestry = ancestry + [(data, idx)]
                if ele is not None and scan_node in ele:
                    eval_val = ele[scan_node]
                    if (match_value is None or Searches.search_matches(
                            PathSearchMethods.LESS_THAN, match_value,
                            eval_val)):
                        match_value = eval_val
                        discard_nodes.extend(match_nodes)
                        match_nodes = [
                            NodeCoords(ele, data, idx, next_path,
                                       next_ancestry, relay_segment)
                        ]
                        continue

                    if (match_value is None or Searches.search_matches(
                            PathSearchMethods.EQUALS, match_value, eval_val)):
                        match_nodes.append(
                            NodeCoords(ele, data, idx, next_path,
                                       next_ancestry, relay_segment))
                        continue

                discard_nodes.append(
                    NodeCoords(ele, data, idx, next_path, next_ancestry,
                               relay_segment))

        elif isinstance(data, dict):
            # A named child node is mandatory
            if scan_node is None:
                raise YAMLPathException((
                    "The {}([NAME]) Search Keyword requires a key name to scan"
                    " when comparing Hash/map/dict children in YAML Path"
                ).format(PathSearchKeywords.MIN), str(yaml_path))

            for key, val in data.items():
                next_ancestry = ancestry + [(data, key)]
                next_path = (translated_path + YAMLPath.escape_path_section(
                    key, translated_path.seperator))
                if isinstance(val, dict):
                    if val is not None and scan_node in val:
                        eval_val = val[scan_node]
                        if (match_value is None or Searches.search_matches(
                                PathSearchMethods.LESS_THAN, match_value,
                                eval_val)):
                            match_value = eval_val
                            discard_nodes.extend(match_nodes)
                            match_nodes = [
                                NodeCoords(val, data, key, next_path,
                                           next_ancestry, relay_segment)
                            ]
                            continue

                        if (match_value is None or Searches.search_matches(
                                PathSearchMethods.EQUALS, match_value,
                                eval_val)):
                            match_nodes.append(
                                NodeCoords(val, data, key, next_path,
                                           next_ancestry, relay_segment))
                            continue

                elif scan_node in data:
                    # The user probably meant to operate against the parent
                    raise YAMLPathException(
                        ("The {}([NAME]) Search Keyword operates against"
                         " collections of data which share a common attribute"
                         " yet there is only a single node to consider.  Did"
                         " you mean to evaluate the parent of the selected"
                         " node?  Please review your YAML Path").format(
                             PathSearchKeywords.MIN), str(yaml_path))

                discard_nodes.append(
                    NodeCoords(val, data, key, next_path, next_ancestry,
                               relay_segment))

        elif isinstance(data, list):
            # A named child node is useless
            if scan_node is not None:
                raise YAMLPathException(
                    ("The {}([NAME]) Search Keyword cannot utilize a key name"
                     " when comparing Array/sequence/list elements to one"
                     " another in YAML Path").format(PathSearchKeywords.MIN),
                    str(yaml_path))

            for idx, ele in enumerate(data):
                next_path = translated_path + "[{}]".format(idx)
                next_ancestry = ancestry + [(data, idx)]
                if (ele is not None
                        and (match_value is None or Searches.search_matches(
                            PathSearchMethods.LESS_THAN, match_value, ele))):
                    match_value = ele
                    discard_nodes.extend(match_nodes)
                    match_nodes = [
                        NodeCoords(ele, data, idx, next_path, next_ancestry,
                                   relay_segment)
                    ]
                    continue

                if (ele is not None and Searches.search_matches(
                        PathSearchMethods.EQUALS, match_value, ele)):
                    match_nodes.append(
                        NodeCoords(ele, data, idx, next_path, next_ancestry,
                                   relay_segment))
                    continue

                discard_nodes.append(
                    NodeCoords(ele, data, idx, next_path, next_ancestry,
                               relay_segment))

        else:
            # Non-complex data is always its own maximum and does not invert
            match_value = data
            match_nodes = [
                NodeCoords(data, parent, parentref, translated_path, ancestry,
                           relay_segment)
            ]

        yield_nodes = discard_nodes if invert else match_nodes
        for node_coord in yield_nodes:
            yield node_coord
Exemplo n.º 5
0
    def _has_anchored_child(
            data: Any, invert: bool, parameters: List[str],
            yaml_path: YAMLPath,
            **kwargs: Any) -> Generator[NodeCoords, None, None]:
        """
        Indicate whether data has an anchored child.

        Parameters:
        1. data (Any) The data to evaluate
        2. invert (bool) Invert the evaluation
        3. parameters (List[str]) Parsed parameters
        4. yaml_path (YAMLPath) YAML Path begetting this operation

        Keyword Arguments:
        * parent (ruamel.yaml node) The parent node from which this query
          originates
        * parentref (Any) The Index or Key of data within parent
        * relay_segment (PathSegment) YAML Path segment presently under
          evaluation
        * translated_path (YAMLPath) YAML Path indicating precisely which node
          is being evaluated
        * ancestry (List[AncestryEntry]) Stack of ancestors preceding the
          present node under evaluation

        Returns:  (Generator[NodeCoords, None, None]) each result as it is
            generated
        """
        parent: Any = kwargs.pop("parent", None)
        parentref: Any = kwargs.pop("parentref", None)
        translated_path: YAMLPath = kwargs.pop("translated_path", YAMLPath(""))
        ancestry: List[AncestryEntry] = kwargs.pop("ancestry", [])
        relay_segment: PathSegment = kwargs.pop("relay_segment", None)

        match_key = parameters[0]
        anchor_name = match_key[1:] if match_key[0] == "&" else match_key

        if isinstance(data, CommentedMap):
            # Look for YAML Merge Keys by the Anchor name
            all_data = ancestry[0][0] if len(ancestry) > 0 else data
            all_anchors: Dict[str, Any] = {}
            Anchors.scan_for_anchors(all_data, all_anchors)
            compare_node = (all_anchors[anchor_name]
                            if anchor_name in all_anchors else None)
            is_ymk_anchor = (compare_node is not None
                             and isinstance(compare_node, dict))

            if is_ymk_anchor:
                child_present = False
                if hasattr(data, "merge") and len(data.merge) > 0:
                    # Ignore comparision if there is no source
                    for (idx, merge_node) in data.merge:
                        if merge_node == compare_node:
                            child_present = True
                            break

                if ((invert and not child_present)
                        or (child_present and not invert)):
                    yield NodeCoords(data, parent, parentref, translated_path,
                                     ancestry, relay_segment)
                    return

            # Look for Anchored keys; include merged nodes
            else:
                child_present = False
                for (key, val) in data.items():
                    key_anchor = Anchors.get_node_anchor(key)
                    val_anchor = Anchors.get_node_anchor(val)
                    if key_anchor and key_anchor == anchor_name:
                        child_present = True
                        break
                    if val_anchor and val_anchor == anchor_name:
                        child_present = True
                        break

                if ((invert and not child_present)
                        or (child_present and not invert)):
                    yield NodeCoords(data, parent, parentref, translated_path,
                                     ancestry, relay_segment)

        elif Nodes.node_is_aoh(data, accept_nulls=True):
            for idx, ele in enumerate(data):
                if ele is None:
                    continue

                next_path = translated_path.append("[{}]".format(str(idx)))
                next_ancestry = ancestry + [(data, idx)]
                for aoh_match in KeywordSearches._has_anchored_child(
                        ele,
                        invert,
                        parameters,
                        yaml_path,
                        parent=data,
                        parentref=idx,
                        translated_path=next_path,
                        ancestry=next_ancestry):
                    yield aoh_match

        elif isinstance(data, list):
            child_present = False
            for ele in data:
                ele_anchor = Anchors.get_node_anchor(ele)
                if ele_anchor and ele_anchor == anchor_name:
                    child_present = True
                    break

            if ((invert and not child_present)
                    or (child_present and not invert)):
                yield NodeCoords(data, parent, parentref, translated_path,
                                 ancestry, relay_segment)
Exemplo n.º 6
0
    def _has_concrete_child(
            data: Any, invert: bool, parameters: List[str],
            yaml_path: YAMLPath,
            **kwargs: Any) -> Generator[NodeCoords, None, None]:
        """
        Indicate whether data has a named child.

        Parameters:
        1. data (Any) The data to evaluate
        2. invert (bool) Invert the evaluation
        3. parameters (List[str]) Parsed parameters
        4. yaml_path (YAMLPath) YAML Path begetting this operation

        Keyword Arguments:
        * parent (ruamel.yaml node) The parent node from which this query
          originates
        * parentref (Any) The Index or Key of data within parent
        * relay_segment (PathSegment) YAML Path segment presently under
          evaluation
        * translated_path (YAMLPath) YAML Path indicating precisely which node
          is being evaluated
        * ancestry (List[AncestryEntry]) Stack of ancestors preceding the
          present node under evaluation

        Returns:  (Generator[NodeCoords, None, None]) each result as it is
            generated
        """
        parent: Any = kwargs.pop("parent", None)
        parentref: Any = kwargs.pop("parentref", None)
        translated_path: YAMLPath = kwargs.pop("translated_path", YAMLPath(""))
        ancestry: List[AncestryEntry] = kwargs.pop("ancestry", [])
        relay_segment: PathSegment = kwargs.pop("relay_segment", None)

        match_key = parameters[0]

        # Against a map, this will return nodes which have an immediate
        # child key exactly named as per parameters.  When inverted, only
        # parents with no such key are yielded.
        if isinstance(data, dict):
            child_present = data is not None and match_key in data
            if ((invert and not child_present)
                    or (child_present and not invert)):
                yield NodeCoords(data, parent, parentref, translated_path,
                                 ancestry, relay_segment)

        # Against a list, this will merely require an exact match between
        # parameters and any list elements.  When inverted, every
        # non-matching element is yielded.
        elif isinstance(data, list):
            # Against an AoH, this will scan each element's immediate children,
            # treating and yielding as if this search were performed directly
            # against each map in the list.
            if Nodes.node_is_aoh(data):
                for idx, ele in enumerate(data):
                    next_path = translated_path.append("[{}]".format(str(idx)))
                    for aoh_match in KeywordSearches._has_concrete_child(
                            ele,
                            invert,
                            parameters,
                            yaml_path,
                            parent=data,
                            parentref=idx,
                            translated_path=next_path):
                        yield aoh_match
                return

            child_present = match_key in data
            if ((invert and not child_present)
                    or (child_present and not invert)):
                yield NodeCoords(data, parent, parentref, translated_path,
                                 ancestry, relay_segment)

        elif data is None:
            if invert:
                yield NodeCoords(data, parent, parentref, translated_path,
                                 ancestry, relay_segment)

        else:
            raise YAMLPathException(
                ("{} data has no child nodes in YAML Path").format(type(data)),
                str(yaml_path))