Skip to content

parser

Simplify creation of custom parsers for pydantic models.

BaseParser

Parsers that work with the ParserMixin must inherit from this class.

Source code in src/metador_core/schema/parser.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class BaseParser:
    """Parsers that work with the ParserMixin must inherit from this class."""

    schema_info: Dict[str, Any] = {}
    strict: bool = True

    @classmethod
    def parse(cls, target: Type[T], v: Any) -> T:
        """Override and implement this method for custom parsing.

        The default implementation will simply pass through
        any instances of `tcls` unchanged and fail on anything else.

        Make sure that the parser can also handle any object that itself
        produces as an input.

        By default, parsers are expected to normalize the input,
        i.e. produce an instance of `tcls`, any other returned type
        will lead to an exception.

        If you know what you are doing, set `strict=False` to
        disable this behavior.

        Args:
            target: class the value should be parsed into
            v: value to be parsed
        """
        if target is not None and not isinstance(v, target):
            raise TypeError(f"Expected {target.__name__}, but got {type(v).__name__}!")
        return v

parse classmethod

parse(target: Type[T], v: Any) -> T

Override and implement this method for custom parsing.

The default implementation will simply pass through any instances of tcls unchanged and fail on anything else.

Make sure that the parser can also handle any object that itself produces as an input.

By default, parsers are expected to normalize the input, i.e. produce an instance of tcls, any other returned type will lead to an exception.

If you know what you are doing, set strict=False to disable this behavior.

Parameters:

Name Type Description Default
target Type[T]

class the value should be parsed into

required
v Any

value to be parsed

required
Source code in src/metador_core/schema/parser.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@classmethod
def parse(cls, target: Type[T], v: Any) -> T:
    """Override and implement this method for custom parsing.

    The default implementation will simply pass through
    any instances of `tcls` unchanged and fail on anything else.

    Make sure that the parser can also handle any object that itself
    produces as an input.

    By default, parsers are expected to normalize the input,
    i.e. produce an instance of `tcls`, any other returned type
    will lead to an exception.

    If you know what you are doing, set `strict=False` to
    disable this behavior.

    Args:
        target: class the value should be parsed into
        v: value to be parsed
    """
    if target is not None and not isinstance(v, target):
        raise TypeError(f"Expected {target.__name__}, but got {type(v).__name__}!")
    return v

ParserMixin

Mixin class to simplify creation of custom pydantic field types.

Can also be mixed into arbitrary classes, not just pydantic models, that is why it is kept separately from the top level base model we use.

Also, we avoid using a custom metaclass for the mixin itself, to increase compatibility with various classes.

Source code in src/metador_core/schema/parser.py
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
class ParserMixin:
    """Mixin class to simplify creation of custom pydantic field types.

    Can also be mixed into arbitrary classes, not just pydantic models,
    that is why it is kept separately from the top level base model we use.

    Also, we avoid using a custom metaclass for the mixin itself,
    to increase compatibility with various classes.
    """

    Parser: ClassVar[Type[BaseParser]]

    @classmethod
    def __get_validators__(cls):
        pfunc = cls.__dict__.get("__parser_func__")
        if pfunc is None:
            if parser := get_parser(cls):

                def wrapper_func(cls, value, values=None, config=None, field=None):
                    return run_parser(parser, field.type_, value)

                pfunc = wrapper_func
            else:
                pfunc = NoParserDefined
            cls.__parser_func__ = pfunc  # cache it

        if pfunc is not NoParserDefined:  # return cached parser function
            yield pfunc

        # important: if no parser is given
        # and class is a model,
        # also return the default validate function of the model!
        if issubclass(cls, BaseModel):
            yield cls.validate

    @classmethod
    def __modify_schema__(cls, schema):
        if parser := get_parser(cls):
            if schema_info := parser.schema_info:
                schema.update(**schema_info)

run_parser

run_parser(
    cls: Type[BaseParser], target: Type[T], value: Any
)

Parse and validate passed value.

Source code in src/metador_core/schema/parser.py
41
42
43
44
45
46
47
48
49
def run_parser(cls: Type[BaseParser], target: Type[T], value: Any):
    """Parse and validate passed value."""
    # print("call parser", cls, "from", tcls, "on", value, ":", type(value))
    ret = cls.parse(target, value)
    if cls.strict and not isinstance(ret, target):
        msg = f"Parser returned: {type(ret).__name__}, "
        msg += f"expected: {target.__name__} (strict=True)"
        raise RuntimeError(msg)
    return ret

get_parser

get_parser(cls)

Return inner Parser class, or None.

If the inner Parser class is not a subclass of BaseParser, will raise an exception, as this is most likely an error.

Source code in src/metador_core/schema/parser.py
52
53
54
55
56
57
58
59
60
61
62
def get_parser(cls):
    """Return inner Parser class, or None.

    If the inner Parser class is not a subclass of `BaseParser`,
    will raise an exception, as this is most likely an error.
    """
    if parser := cls.__dict__.get("Parser"):
        if not issubclass(parser, BaseParser):
            msg = f"{cls}: {cls.Parser.__name__} must be a subclass of {BaseParser.__name__}!"
            raise TypeError(msg)
        return parser