예제 #1
0
    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], ''
예제 #2
0
    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], ''
예제 #3
0
    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, ''
예제 #4
0
    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_)
예제 #5
0
    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