def __check_no_missing_attributes(self, node: yaml.Node, mapping: CommentedMap) -> None: """Checks that all required attributes are present. Also checks that they're of the correct type. Args: mapping: The mapping with subobjects of this object. Raises: RecognitionError: if an attribute is missing or the type is incorrect. """ logger.debug('Checking presence of required attributes') for name, type_, required in class_subobjects(self.class_): if required and name not in mapping: raise RecognitionError(('{}{}Missing attribute "{}" needed for' ' constructing a {}').format( node.start_mark, os.linesep, name, self.class_.__name__)) if name in mapping and not self.__type_matches( mapping[name], type_): raise RecognitionError(('{}{}Attribute "{}" has incorrect type' ' {}, expecting a {}').format( node.start_mark, os.linesep, name, type(mapping[name]), type_))
def __recognize_user_class(self, node: yaml.Node, expected_type: Type) -> RecResult: """Recognize a user-defined class in the node. This tries to recognize only exactly the specified class. It \ recurses down into the class's attributes, but not to its \ subclasses. See also __recognize_user_classes(). Args: node: The node to recognize. expected_type: A user-defined class. Returns: A list containing the user-defined class, or an empty list. """ logger.debug('Recognizing as a user-defined class') loc_str = '{}{}'.format(node.start_mark, os.linesep) if hasattr(expected_type, 'yatiml_recognize'): try: unode = UnknownNode(self, node) expected_type.yatiml_recognize(unode) return [expected_type], '' except RecognitionError as e: if len(e.args) > 0: message = ('Error recognizing a {}\n{}because of the' ' following error(s): {}').format( expected_type.__class__, loc_str, indent(e.args[0], ' ')) else: message = 'Error recognizing a {}\n{}'.format( expected_type.__class__, loc_str) return [], message else: if issubclass(expected_type, enum.Enum): if (not isinstance(node, yaml.ScalarNode) or node.tag != 'tag:yaml.org,2002:str'): message = 'Expected an enum value from {}\n{}'.format( expected_type.__class__, loc_str) return [], message elif (issubclass(expected_type, UserString) or issubclass(expected_type, str)): if (not isinstance(node, yaml.ScalarNode) or node.tag != 'tag:yaml.org,2002:str'): message = 'Expected a string matching {}\n{}'.format( expected_type.__class__, loc_str) return [], message else: # auto-recognize based on constructor signature if not isinstance(node, yaml.MappingNode): message = 'Expected a dict/mapping here\n{}'.format( loc_str) return [], message for attr_name, type_, required in class_subobjects( expected_type): cnode = Node(node) # try exact match first, dashes if that doesn't match for name in [attr_name, attr_name.replace('_', '-')]: if cnode.has_attribute(name): subnode = cnode.get_attribute(name) recognized_types, message = self.recognize( subnode.yaml_node, type_) if len(recognized_types) == 0: message = ('Failed when checking attribute' ' {}:\n{}').format( name, indent(message, ' ')) return [], message break else: if required: message = ( 'Error recognizing a {}\n{}because it' ' is missing an attribute named {}').format( expected_type.__name__, loc_str, attr_name) if '_' in attr_name: message += ' or maybe {}.\n'.format( attr_name.replace('_', '-')) else: message += '.\n' return [], message return [expected_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