def test_aoh_is_inconsistent(self): assert False == Nodes.node_is_aoh([{"key": "value"}, None])
def test_aoh_node_is_not_list(self): assert False == Nodes.node_is_aoh({"key": "value"})
def test_aoh_node_is_none(self): assert False == Nodes.node_is_aoh(None)
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
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)
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))