コード例 #1
0
ファイル: recognizer.py プロジェクト: yatiml/yatiml
    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  # type: Any
        if expected_type in (str, int, float, bool, bool_union_fix, date, None,
                             type(None)):
            recognized_types, message = self.__recognize_scalar(
                node, expected_type)
        elif expected_type in self.__additional_classes:
            recognized_types, message = self.__recognize_additional(
                node, expected_type)
        elif is_generic_union(expected_type):
            recognized_types, message = self.__recognize_union(
                node, expected_type)
        elif is_generic_sequence(expected_type):
            recognized_types, message = self.__recognize_list(
                node, expected_type)
        elif is_generic_mapping(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)
        elif expected_type in (Any, ):
            recognized_types, message = [Any], ''

        if recognized_types is None:
            raise RecognitionError(
                ('Could not recognize for type {},'
                 ' is it registered?').format(expected_type.__name__))
        logger.debug('Recognized types {} matching {}'.format(
            recognized_types, expected_type))
        return recognized_types, message
コード例 #2
0
ファイル: constructors.py プロジェクト: yatiml/yatiml
    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_)
コード例 #3
0
ファイル: loader.py プロジェクト: yatiml/yatiml
def load_function(result=_AnyYAML, *args):  # type: ignore
    """Create a load function for the given type.

    This function returns a callable object which takes an input
    (``str`` with YAML input, ``pathlib.Path``, or an open stream) and
    tries to load an object of the type given as the first argument.
    Any user-defined classes needed by the result must be passed as
    the remaining arguments.

    Note that mypy will give an error if you try to pass some of the
    special type-like objects from ``typing``. ``typing.Dict`` and
    ``typing.List`` seem to be okay, but ``typing.Union``,
    ``typing.Optional``, and abstract containers
    ``typing.Sequence``, ``typing.Mapping``,
    ``typing.MutableSequence`` and ``typing.MutableMapping`` will
    give an error. They are supported however, and work fine,
    there is just no way presently to explain to mypy that they
    are okay.

    So, if you want to tell YAtiML that your YAML file may contain
    either a string or an int, you can use ``Union[str, int]`` for the
    first argument, but you'll have to add a ``# type: ignore`` or two
    to tell mypy to ignore the issue. The resulting Callable will have
    return type ``Any`` in this case.

    Examples:

        .. code-block:: python

          load_int_dict = yatiml.load_function(Dict[str, int])
          my_dict = load_int_dict('x: 1')

        .. code-block:: python

          load_config = yatiml.load_function(Config, Setting)
          my_config = load_config(Path('config.yaml'))

          # or

          with open('config.yaml', 'r') as f:
              my_config = load_config(f)

        Here, ``Config`` is the top-level class, and ``Setting`` is
        another class that is used by ``Config`` somewhere.

        .. code-block:: python

          # Needs an ignore, on each line if split over two lines
          load_int_or_str = yatiml.load_function(     # type: ignore
                  Union[int, str])                    # type: ignore

    Args:
        result: The top level type, return type of the function.
        *args: Any other (custom) types needed.

    Returns:
        A function that can load YAML input from a string, Path or
        stream and convert it to an object of the first type given.
    """
    class UserLoader(Loader):
        pass

    # add loaders for additional types
    if UserLoader._additional_classes is None:
        UserLoader._additional_classes = dict()
    UserLoader.add_constructor('!Path', PathConstructor())
    UserLoader._additional_classes[Path] = '!Path'

    additional_types = (Path, )

    # add loaders for user types
    user_classes = list(args)
    if not (is_generic_mapping(result) or is_generic_sequence(result)
            or is_generic_union(result) or result is Any):
        if result not in additional_types and result not in user_classes:
            user_classes.append(result)

    add_to_loader(UserLoader, user_classes)
    if result is _AnyYAML:
        set_document_type(UserLoader, Any)  # type: ignore
    else:
        set_document_type(UserLoader, result)

    class LoadFunction:
        """Validates YAML input and constructs objects."""
        def __init__(self, loader: Type[Loader]) -> None:
            """Create a LoadFunction."""
            self.loader = loader

        def __call__(self, source: Union[str, Path, IO[AnyStr]]) -> T:
            """Load a YAML document from a source.

            The source can be a string containing YAML, a pathlib.Path
            containing a path to a file to load, or a stream (e.g. an
            open file handle returned by open()).

            Args:
                source: The source to load from.

            Returns:
                An object loaded from the file.

            Raises:
                yatiml.RecognitionError: If the input is invalid.
            """

            if isinstance(source, Path):
                with source.open('r') as f:
                    return cast(T, yaml.load(f, Loader=self.loader))
            else:
                return cast(T, yaml.load(source, Loader=self.loader))

    return LoadFunction(UserLoader)