def __recognize_dict(self, node: yaml.Node, expected_type: Type) -> RecResult: """Recognize a node that we expect to be a dict of some kind. Args: node: The node to recognize. expected_type: Dict[str, ...something...] Returns: expected_type if it was recognized, [] otherwise. """ logger.debug('Recognizing as a dict') if not issubclass(generic_type_args(expected_type)[0], str): raise RuntimeError( 'YAtiML only supports dicts with strings as keys') if not isinstance(node, yaml.MappingNode): message = '{}{}Expected a dict/mapping here'.format( node.start_mark, os.linesep) return [], message value_type = generic_type_args(expected_type)[1] for _, value in node.value: recognized_value_types, message = self.recognize(value, value_type) if len(recognized_value_types) == 0: return [], message if len(recognized_value_types) > 1: return [ Dict[str, t] # type: ignore for t in recognized_value_types ], message # type: ignore return [expected_type], ''
def __recognize_list(self, node: yaml.Node, expected_type: Type) -> RecResult: """Recognize a node that we expect to be a list of some kind. Args: node: The node to recognize. expected_type: List[...something...] Returns expected_type and the empty string if it was recognized, [] and an error message otherwise. """ logger.debug('Recognizing as a list') if not isinstance(node, yaml.SequenceNode): message = '{}{}Expected a list here.'.format( node.start_mark, os.linesep) return [], message item_type = generic_type_args(expected_type)[0] for item in node.value: recognized_types, message = self.recognize(item, item_type) if len(recognized_types) == 0: return [], message if len(recognized_types) > 1: recognized_types = [ List[t] # type: ignore for t in recognized_types ] return recognized_types, message return [expected_type], ''
def __recognize_union(self, node: yaml.Node, expected_type: Type) -> RecResult: """Recognize a node that we expect to be one of a union of types. Args: node: The node to recognize. expected_type: Union[...something...] Returns: The specific type that was recognized, multiple, or none. """ logger.debug('Recognizing as a union') recognized_types = [] message = '' union_types = generic_type_args(expected_type) logger.debug('Union types {}'.format(union_types)) for possible_type in union_types: recognized_type, msg = self.recognize(node, possible_type) if len(recognized_type) == 0: message += msg recognized_types.extend(recognized_type) recognized_types = list(set(recognized_types)) if bool in recognized_types and bool_union_fix in recognized_types: recognized_types.remove(bool_union_fix) if len(recognized_types) == 0: return recognized_types, message elif len(recognized_types) > 1: message = ('{}{}Could not determine which of the following types' ' this is: {}').format(node.start_mark, os.linesep, recognized_types) return recognized_types, message return recognized_types, ''
def __type_matches(self, obj: Any, type_: Type) -> bool: """Checks that the object matches the given type. Like isinstance(), but will work with union types using Union, Dict and List. Args: obj: The object to check type_: The type to check against Returns: True iff obj is of type type_ """ if is_generic_union(type_): for t in generic_type_args(type_): if self.__type_matches(obj, t): return True return False elif is_generic_sequence(type_): if not isinstance(obj, list): return False for item in obj: if not self.__type_matches(item, generic_type_args(type_)[0]): return False return True elif is_generic_mapping(type_): if not isinstance(obj, OrderedDict): return False for key, value in obj.items(): if not isinstance(key, generic_type_args(type_)[0]): return False if not self.__type_matches(value, generic_type_args(type_)[1]): return False return True elif type_ is bool_union_fix: return isinstance(obj, bool) elif type_ is Any: return True else: return isinstance(obj, type_)
def __process_node(self, node: yaml.Node, expected_type: Type) -> yaml.Node: """Processes a node. This is the main function that implements yatiml's \ functionality. It figures out how to interpret this node \ (recognition), then applies syntactic sugar, and finally \ recurses to the subnodes, if any. Args: node: The node to process. expected_type: The type we expect this node to be. Returns: The transformed node, or a transformed copy. """ logger.info('Processing node {} expecting type {}'.format( node, expected_type)) # figure out how to interpret this node recognized_types, message = self.__recognizer.recognize( node, expected_type) if len(recognized_types) != 1: raise RecognitionError(message) recognized_type = recognized_types[0] # remove syntactic sugar logger.debug('Savorizing node {}'.format(node)) if recognized_type in self._registered_classes.values(): node = self.__savorize(node, recognized_type) logger.debug('Savorized, now {}'.format(node)) # process subnodes logger.debug('Recursing into subnodes') if is_generic_list(recognized_type): if node.tag != 'tag:yaml.org,2002:seq': raise RecognitionError('{}{}Expected a {} here'.format( node.start_mark, os.linesep, type_to_desc(expected_type))) for item in node.value: self.__process_node(item, generic_type_args(recognized_type)[0]) elif is_generic_dict(recognized_type): if node.tag != 'tag:yaml.org,2002:map': raise RecognitionError('{}{}Expected a {} here'.format( node.start_mark, os.linesep, type_to_desc(expected_type))) for _, value_node in node.value: self.__process_node(value_node, generic_type_args(recognized_type)[1]) elif recognized_type in self._registered_classes.values(): if (not issubclass(recognized_type, enum.Enum) and not issubclass(recognized_type, str) and not issubclass(recognized_type, UserString)): for attr_name, type_, _ in class_subobjects(recognized_type): cnode = Node(node) if cnode.has_attribute(attr_name): subnode = cnode.get_attribute(attr_name) new_subnode = self.__process_node( subnode.yaml_node, type_) cnode.set_attribute(attr_name, new_subnode) else: logger.debug('Not a generic class or a user-defined class, not' ' recursing') node.tag = self.__type_to_tag(recognized_type) logger.debug('Finished processing node {}'.format(node)) return node