• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LeanderCS / flask-inputfilter / #386

24 May 2025 12:48PM UTC coverage: 93.386% (-0.3%) from 93.644%
#386

push

coveralls-python

LeanderCS
48 | Updated IsDataclassValidator to also check against their types, including nested dataclasses, lists, and dictionaries

47 of 56 new or added lines in 4 files covered. (83.93%)

87 existing lines in 18 files now uncovered.

1878 of 2011 relevant lines covered (93.39%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.07
/flask_inputfilter/validators/is_dataclass_validator.py
1
from __future__ import annotations
1✔
2

3
import dataclasses
1✔
4
from typing import Any, Optional, Type, TypeVar, Union
1✔
5

6
from typing_extensions import get_args, get_origin
1✔
7

8
from flask_inputfilter.exceptions import ValidationError
1✔
9
from flask_inputfilter.validators import BaseValidator
1✔
10

11
T = TypeVar("T")
1✔
12

13

14
class IsDataclassValidator(BaseValidator):
1✔
15
    """
16
    Validates that the provided value conforms to a specific
17
    dataclass type.
18

19
    **Parameters:**
20

21
    - **dataclass_type** (*Type[dict]*): The expected dataclass type.
22
    - **error_message** (*Optional[str]*): Custom error message if validation fails.
23

24
    **Expected Behavior:**
25

26
    Ensures the input is a dictionary and, that all expected keys are present. Raises a ``ValidationError`` if the structure does not match.
27
    All fields in the dataclass are validated against their types, including nested dataclasses, lists, and dictionaries.
28

29
    **Example Usage:**
30

31
    .. code-block:: python
32

33
        from dataclasses import dataclass
34

35
        @dataclass
36
        class User:
37
            id: int
38
            name: str
39

40
        class UserInputFilter(InputFilter):
41
            def __init__(self):
42
                super().__init__()
43

44
                self.add('user', validators=[
45
                    IsDataclassValidator(dataclass_type=User)
46
                ])
47
    """
48

49
    __slots__ = ("dataclass_type", "error_message")
1✔
50

51
    def __init__(
1✔
52
        self,
53
        dataclass_type: Type[T],
54
        error_message: Optional[str] = None,
55
    ) -> None:
56
        self.dataclass_type = dataclass_type
1✔
57
        self.error_message = error_message
1✔
58

59
    def validate(self, value: Any) -> None:
1✔
60
        if not isinstance(value, dict):
1✔
61
            raise ValidationError(
1✔
62
                self.error_message
63
                or "The provided value is not a dict instance."
64
            )
65

66
        if not dataclasses.is_dataclass(self.dataclass_type):
1✔
UNCOV
67
            raise ValidationError(
×
68
                self.error_message
69
                or f"'{self.dataclass_type}' is not a valid dataclass."
70
            )
71

72
        for field in dataclasses.fields(self.dataclass_type):
1✔
73
            field_name = field.name
1✔
74
            field_type = field.type
1✔
75
            has_default = (
1✔
76
                field.default is not dataclasses.MISSING
77
                or field.default_factory is not dataclasses.MISSING
78
            )
79

80
            if field_name not in value:
1✔
81
                if not has_default:
1✔
NEW
82
                    raise ValidationError(
×
83
                        self.error_message
84
                        or f"Missing required field '{field_name}' in value '{value}'."
85
                    )
NEW
86
                continue
×
87

88
            field_value = value[field_name]
1✔
89

90
            origin = get_origin(field_type)
1✔
91
            args = get_args(field_type)
1✔
92

93
            if origin is not None:
1✔
94
                if origin is list:
1✔
NEW
95
                    if not isinstance(field_value, list) or not all(
×
96
                        isinstance(item, args[0]) for item in field_value
97
                    ):
NEW
98
                        raise ValidationError(
×
99
                            self.error_message
100
                            or f"Field '{field_name}' in value '{value}' is not a valid list of '{args[0]}'."
101
                        )
102
                elif origin is dict:
1✔
NEW
103
                    if not isinstance(field_value, dict) or not all(
×
104
                        isinstance(k, args[0]) and isinstance(v, args[1])
105
                        for k, v in field_value.items()
106
                    ):
NEW
107
                        raise ValidationError(
×
108
                            self.error_message
109
                            or f"Field '{field_name}' in value '{value}' is not a valid dict with keys of type '{args[0]}' and values of type '{args[1]}'."
110
                        )
111
                elif origin is Union and type(None) in args:
1✔
112
                    if field_value is not None and not isinstance(
1✔
113
                        field_value, args[0]
114
                    ):
NEW
115
                        raise ValidationError(
×
116
                            self.error_message
117
                            or f"Field '{field_name}' in value '{value}' is not of type '{args[0]}'."
118
                        )
119
                else:
NEW
120
                    raise ValidationError(
×
121
                        self.error_message
122
                        or f"Unsupported type '{field_type}' for field '{field_name}'."
123
                    )
124
            elif dataclasses.is_dataclass(field_type):
1✔
125
                IsDataclassValidator(field_type).validate(field_value)
1✔
126
            else:
127
                if not isinstance(field_value, field_type):
1✔
128
                    raise ValidationError(
1✔
129
                        self.error_message
130
                        or f"Field '{field_name}' in value '{value}' is not of type '{field_type}'."
131
                    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc