def recognize(self, node: yaml.Node, expected_type: Type) -> RecResult: """Figure out how to interpret this node. This is not quite a type check. This function makes a list of \ all types that match the expected type and also the node, and \ returns that list. The goal here is not to test validity, but \ to determine how to process this node further. That said, it will recognize built-in types only in case of \ an exact match. Args: node: The YAML node to recognize. expected_type: The type we expect this node to be, based \ on the context provided by our type definitions. Returns: A list of matching types. """ logger.debug('Recognizing {} as a {}'.format(node, expected_type)) recognized_types = None if expected_type in [ str, int, float, bool, bool_union_fix, datetime, None, type(None) ]: recognized_types, message = self.__recognize_scalar( node, expected_type) elif is_generic_union(expected_type): recognized_types, message = self.__recognize_union( node, expected_type) elif is_generic_list(expected_type): recognized_types, message = self.__recognize_list( node, expected_type) elif is_generic_dict(expected_type): recognized_types, message = self.__recognize_dict( node, expected_type) elif expected_type in self.__registered_classes.values(): recognized_types, message = self.__recognize_user_classes( node, expected_type) if recognized_types is None: raise RecognitionError( ('Could not recognize for type {},' ' is it registered?').format(expected_type)) logger.debug('Recognized types {} matching {}'.format( recognized_types, expected_type)) return recognized_types, message
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_list(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_dict(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) else: return isinstance(obj, type_)
def __type_to_tag(self, type_: Type) -> str: """Convert a type to the corresponding YAML tag. Args: type_: The type to convert Returns: A string containing the YAML tag. """ if type_ in scalar_type_to_tag: return scalar_type_to_tag[type_] if is_generic_list(type_): return 'tag:yaml.org,2002:seq' if is_generic_dict(type_): return 'tag:yaml.org,2002:map' if type_ in self._registered_classes.values(): return '!{}'.format(type_.__name__) raise RuntimeError(( 'Unknown type {} in type_to_tag,' # pragma: no cover ' please report a YAtiML bug.').format(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